天天看點

Android Weekly Notes Issue #227

Android Weekly中文筆記, Issue #227. 本期内容包括: Google的Mobile Vision API 人臉檢測; Firebase的Remote Config; 與HashMap有關的優化; 提高RecyclerView幀率的優化; 使用AutoValue生成model代碼; 開源庫中抽象類和接口的使用讨論; Bottom Sheet的使用; Android Studio中的版本控制系統; ConstraintLayout的使用; 應用換Bottom Navigation; Nougat的Messaging Style Notification; 自定義字型; Reductor的使用等.

October 16th, 2016

Android Weekly Issue #227.

本期内容包括: Google的Mobile Vision API 人臉檢測; Firebase的Remote Config; 與HashMap有關的優化; 提高RecyclerView幀率的優化; 使用AutoValue生成model代碼; 開源庫中抽象類和接口的使用讨論; Bottom Sheet的使用; Android Studio中的版本控制系統; ConstraintLayout的使用; 應用換Bottom Navigation; Nougat的Messaging Style Notification; 自定義字型; Reductor的使用等.

這篇文章來自Mobile Vision, 講人臉檢測及相關概念.

API使用Tutorial.

Sample.

Remote config是Firebase提供的一個feature, 讓我們可以定義參數, 在firebase的console管理, 進而在server端控制應用的UI或者行為, 并且可以選擇生效的使用者範圍.

之前還有這個文章是關于Firebase Analytics的.

本篇文章介紹了Firebase的Remote Config可以幹什麼, 以及怎麼做, 解說很詳細.

參數

我們用Remote Config定義的鍵值對叫參數(parameters). 它提供了這個參數相關的what資訊(key, the identifier), 和how資訊(value, the configuration).

條件

條件值(conditional value)也是一個鍵值對, 其中condition指定了需要滿足的條件, value指定了滿足條件時需要傳回的值.

優先級

如果單個條件被滿足, 那麼傳回對應的值; 如果多個條件都被滿足, 那麼傳回主導條件(list上方的條件)對應的值; 如果沒有條件滿足, 則傳回預設值; 如果沒有定義預設值, 則什麼也不傳回.

文中還詳細介紹了Android和iOS端的實作, 以及console的配置.

當我們需要存儲鍵值對的時候, 我們總是首先想到用<code>HashMap</code>, 然而IDE(Android Studio)有時候會警告提醒你, 應該用<code>ArrayMap</code>或<code>SparseArray</code>.

<code>ArrayMap</code>比傳統的<code>HashMap</code>更節省記憶體, 因為它把自己的映射放在數組結構中: 一個整型數組放每一個item的hash code, 一個Object數組放key/value對. 這樣避免了為每一個entry建立額外的對象, 而且數組增長也好控制.

注意<code>ArrayMap</code>并不是為很大的資料集設計的, 并且它會比<code>HashMap</code>慢一些, 以為查找需要二分查找, 增删需要在數組中操作.

<code>HashMap</code>是一個<code>HashMap.Entry</code>的數組, 其組成是key, value, HashCode, 還有一個指針.

當進行插入時: 首先計算出key的HashCode, 然後用這個hashCode找到對應的bucket, 如果已經存了元素, 則把舊元素的指針指向新元素, 即把bucket變為一個<code>LinkedList</code>.

當進行查詢時: 複雜度為O(1), 但是這樣是犧牲了更多的空間複雜度得到的.

HashMap的缺點:

因為key和value都不能是原生類型, 是以插入時可能會有自動裝箱, 導緻建立額外的對象.

<code>HashMap.Entry</code>本身就是一層額外的對象.

每次HashMap的收縮或者擴張, Buckets都要重新排列, 随着對象變多, 這個操作變得越發昂貴.

<code>ArrayMap</code>使用兩個數組:

<code>int[] mHashes</code>用來存哈希值; <code>Object[] mArray</code>來存對象.

當插入鍵值對時: Key/Value被自動裝箱, Key被插入到<code>mArray[]</code>數組的下一個位置, Value也被插入到<code>mArray[]</code>, 在Key的下一個位置.

計算出的哈希值被放在<code>mHashes[]</code>的下一個位置.

當查詢一個Key時: 首先計算出Key的哈希值, 在<code>mHashes</code>中二分查找這個hashCode(時間複雜度(logN)), 當得到hash的index之後, 我們就知道<code>mArray</code>中<code>2*index</code>和<code>2*index+1</code>的位置對應的是查找的key和value.

雖然時間複雜度提升了, 但是這樣卻更省空間.

<code>ArrayMap&lt;K,V&gt;</code> in place of <code>HashMap&lt;K,V&gt;</code>

<code>ArraySet&lt;K,V&gt;</code> in place of <code>HashSet&lt;K,V&gt;</code>

<code>SparseArray&lt;V&gt;</code> in place of <code>HashMap&lt;Integer,V&gt;</code>

<code>SparseBooleanArray</code> in place of <code>HashMap&lt;Integer,Boolean&gt;</code>

<code>SparseIntArray</code> in place of <code>HashMap&lt;Integer,Integer&gt;</code>

<code>SparseLongArray</code> in place of <code>HashMap&lt;Integer,Long&gt;</code>

<code>LongSparseArray&lt;V&gt;</code> in place of <code>HashMap&lt;Long,V&gt;</code>

我們經常會用RecyclerView來顯示一個list的資料.

作者他們做的是一個招聘應用: Workable, 其中會用list來顯示candidates.

他們還使用了DataBinding.

本文是作者他們關于RecyclerView的幀率所做的一些優化.

首先他們使用了Android Studio的Allocation Tracking, 然後上下滾動, 從報告發現, 他們布局中使用的<code>TableLayout</code>花費了很多資源, 于是後來他們改為<code>LinearLayout</code>權重重的方式來解決, 擺脫了耗費資源的<code>TableLayout</code>.

另一個引起很多資源配置設定的問題是, 對于需要大寫的文字, xml中的:

<code>TextView</code>的代碼中會為此生成一個對象:

這個在靜态的布局中可能沒有問題, 但是在一個滾動的list中可能會有些影響.

改進方法是改為用java String的<code>.toUpperCase()</code>.

然後他們使用了RecyclerView的<code>.onViewRecycled()</code>方法. 這個方法讓我們知道了RecyclerView中的一行何時被回收, 這樣我們就可以釋放一些不需要的資源.

他們使用了DataBinding, 是以這是一個合适的時機來删除ViewModel中的<code>OnPropertyChangedCallbacks</code>, 然後清理ViewModel自身, 我們還可以清理之前用Glide load到ImageView中的圖檔.

作者他們的應用還有一些cache設定:

之後作者測量了他們的FPS, 顯示是60 FPS, 并且發現去掉這些cache設定仍然是60.

測量幀率FPS的工具: TinyDancer.

在Java/Android程式設計中經常需要寫model對象來存放一些資料, 使用Google的庫AutoValue可以幫你自動生成這些類, 你需要做的就是定義你的字段, 然後給類加上注解.

在project的<code>build.gradle</code>中:

在app的<code>build.gradle</code>中:

比如要建立Film類, 你可以寫一個這樣的抽象類:

每一個字段都對應一個抽象方法.

build一下, <code>AutoValue_Film</code>類就會自動生成, 加上靜态工廠方法(上面的<code>create()</code>方法) 然後就可以使用工廠方法來得到model:

點進自動生成的類<code>AutoValue_Film</code>裡可以看到, 連<code>hashCode()</code>和<code>equals()</code>方法都生成了.

上面的例子随着字段的增多, <code>create()</code>方法的參數會變得很多, 用起來不友善, 那麼此時就需要用Builder模式:

這樣就可以很友善地加參數了:

有時候你需要在Activity之間傳資料, 需要你的model是<code>Parcelable</code>的, 此時你就可以用這個auto-value-parcel, 在代碼裡也隻需要實作這個接口:

還有很多的擴充庫: extensions for AutoValue, 比如AutoValue-Gson, AutoValue-Cursor, AutoValue-With, AutoValue-Redacted等.

這篇文章的作者說, 在library開發中, 應該考慮用抽象類而不是接口. 他的庫是AdapterDelegates.

作者先介紹了通用的概念比較:

class vs. interface

接口更解耦, 更靈活, 隻是定義了一個協定, 不限制實作.

interface vs. abstract class

抽象類會有繼承的問題, 基類和子類會共享一些實作, 是以子類的編寫者最好能清楚基類的實作, 這樣才不會在寫子類實作抽象方法的時候打破了基類作者的意圖. 另外就是基類作者仍然可能會更新基類, 是以得時刻檢查子類是否還是符合基類的設計意圖.

但是為什麼作者還是要把自己庫中的接口改為抽象類呢? 這是因為作者的庫依賴于Android的庫, Android的庫中相關代碼改了, 作者就得改自己的public接口, 加一個方法, 導緻所有新版的使用者也都必須實作這個方法.

還有一個情況就是比如一個開發者使用了2.1版本, 但是他項目裡依賴的另一個第三方庫使用了2.0版本. 編譯不會出錯, 最終的apk中打包的是2.1版本. 然後在這個第三方庫的元件裡調用2.1才有的新方法時就會抛出錯誤.

為了解決這個問題, Jake Wharton建議在庫的主要更新(major update)中更改釋出的package name和group id: http://jakewharton.com/java-interoperability-policy-for-major-version-updates/

作者覺得那每次Android RecyclerView的Adapter更新都會導緻自己的庫major update, 是以他決定把自己的<code>AdapterDelegate</code>接口改為抽象類. 這樣他就可以對新增的方法提供預設空實作.

這樣定義的抽象類隻有抽象方法和一些空實作的方法, 并沒有狀态和行為的共享可能會傳播給子類, 其實和接口是一樣的.

實作bottom sheet的時候, 有三種選擇: container view + <code>BottomSheetBehavior</code>, <code>BottomSheetDialogFragment</code>, <code>BottomSheetDialog</code>. 前兩種的例子比較多, 作者要介紹的是第三種.

如何選擇取決你的用途, container view + <code>BottomSheetBehavior</code> 适用于persistent bottom sheet, 而<code>BottomSheetDialogFragment</code>和<code>BottomSheetDialog</code>适用于Modal bottom sheets.

之後作者提供了實作代碼, 附有theme定制和狀态callback的設定.

這篇文章介紹Android Studio的版本控制系統.

在Android Studio 2.2開始, 加入了一個<code>Create command line launcher</code>, 這樣你就可以在指令行或者第三方的版本控制用戶端使用Android Studio的diff/merge tool了.

作者使用的用戶端是SourceTree.

用<code>cmd + shift + A</code>可以用來find action, 然後就可以找到<code>Compare with branch</code>: 可以比較目前檔案和某個分支上的檔案的diff;

另外還可以<code>Compare with...</code>, 來比較和之前某一個特定送出的diff; 以及<code>Compare with Clipboard</code>來和剪貼闆做比較.

還有一些其他有用的快捷鍵, 請看原文吧.

作者想做這樣一個UI, 左邊是一個icon, 右邊是兩行字, icon的top和bottom分别和第一行字的top和bottom對齊.

Android Weekly Notes Issue #227

怎麼做呢? 她想到了用ConstraintLayout.

代碼在這裡: iconlabeltext

作者他們的應用從burger menu改為bottom navigation, 此篇為心得分享和他們改版時設計中的一些細節讨論.

其中狀态儲存是一個最主要的技術問題.

改版之後, 作者他們的應用資料表明有以下幾個好處:

使用者參與度提升了;

在底部導航有入口的功能使用率提高了;

并沒有使用者回報說新的導航不好.

Messaging Style Notifications是為資訊應用特殊設計的, 提供了一個像對話一樣的view.

Messaging-style notifications和Bundled notifications的主要差別是, Bundled notifications中我們持續建立新的notification, 然後它們被grouped together. 但是用Messaging-style notifications的時候, 我們隻有一個notification, 然後我們把所有的資訊添加進去.

作者展示了實作代碼和效果, 注意這個Messaging style并不是後項相容的, 隻在Nougat及以後的版本才支援.

作者介紹了他的應用中對于Bottom sheet的使用.

Deep linking with bottom sheet Activity

作者用它處理Deep linking, 這樣使用者就不用每次都全屏打開, 隻先提供一個peek, 如果真的感興趣再打開.

實作是用一個透明的Activity, 還有狀态欄處理的細節.

Bottom sheet settings menu

關于Settings, 為了節省使用者的trip, 作者它們的應用用了options menu的彈出菜單. 後來他們改用bottom sheet來實作, 并且結合了<code>PreferenceFragmentCompat</code>, 省去了一些SharedPreferences的讀寫操作.

Supporting tablet users

bottom sheet在平闆上使用, 尤其是橫屏的時候, 看起來不太好.

是以作者定制了Bottom sheet的寬度, 在平闆上時是一個指定寬度, 在手機上維持原狀.

基于Google的Mobile Vision APIs現在Android開發者可以在應用裡用上機器學習了. 現在這個Mobile Vision API包括三種類型Face Detection API, Barcode Detection API和Text API.

本文主要講人臉檢測部分, 後面會講二維碼檢測和文字的API.

作者的demo展示了如何從一個靜态照片中檢測出人臉區域, 并且标記出landmark(眼睛, 鼻子, 嘴巴等), 之後可以根據這些特征位置加上一些覆寫标記.

sample code.

在Android中自定義字型的一個庫: Calligraphy.

如果你的輸入是html文字, 你想自動處理裡面的tag(比如<b>), 用另一種字型, 怎麼處理呢, 作者給出了代碼.</b>

Android Weekly Notes Issue #227

<b></b>

完整的例子代碼見: sample code.

之前這個文章介紹過Reductor, 在Android Weekly之前也出現過, 我的筆記: Android Weekly Notes Issue 224.

Reductor是一個狀态管理的庫, 用Java重新實作了JavaScript的庫Redux.

它的中心思想:

Android Weekly Notes Issue #227

之前的一篇文章做了一個TODO app, 然後作者發現這種mutable的資料會導緻失控的資料改變, 然後可能會出現無法預測的行為. 做了一些改動之後, 我們發現可以通過隻儲存一個immutable的對象和mutable的reference來避免這個問題.

這篇文章用Reductor來重新實作應用, 文中詳細說明了代碼實作.

一個很小的庫, Activity直接的shared element transition動畫, 把一個圓形的ImageView變換到下一個Activity的方形ImageView.

用Kotlin實作的設計模式.

作者: 聖騎士Wind

出處: 部落格園: 聖騎士Wind

Github: https://github.com/mengdd

微信公衆号: 聖騎士Wind

Android Weekly Notes Issue #227