I/O’19 Android Architecture Component 那一些新東西

Jast Lai
Jastzeonic
Published in
15 min readMay 13, 2019

本來是想用 Build a Modular Android App Architecture 當作起頭文章的,不過看了該影片其中包含了不少 Dynamic Delivery ,因為涉及了 Android app Bundles,偶沒怎麼玩過,實際寫起文章會有點跳痛,所以先擱著一段研究研究再來寫。

那老樣的,我這回先以 AAC 的新玩意做開頭唄。

本篇文章基於本影片所轉譯撰寫,有興趣了解更多的人可以參考這影片

Kotlin first

開頭便強調了,這次會以 Kotlin 為優先,所以 AAC 會對此有兩個發展:

  • 不只 KTX 還有 Kotlin 專屬的功能
  • 專為 Kotlin 設計的 API

Data Binding

我對 Data binding 的情感非常複雜,我算是推廣他的人,但是我推廣他的過程中也一直強調他是一把雙面刃,使用它時要非常謹慎,因為使用它很容易不經意地踩坑而沒有自覺。

這回 Data binding 也有一些升級,Annotation 建構的速度變快了,也開始支援分散式 cache ,所以使用起來應該會有感速度變快了,那這些主要更新是在 Android Studio 3.5(目前是 Carnary 金絲雀版本)。

Live Class Generation

那比較明顯的改變是 binding item 建成的時間:

其實可以看到 binding item 變得快很多。

同樣要 bind 在 xml variable 也新增了 auto complite,更新 binding item 的速度也快上不少。

Refactoring support

此外還增加了雙向 Refactoring 的功能,過往若是在 code 這邊重新命名,要手動去 xml 更改,現在則不用了,同樣也可以直接從 xml 對 code 這邊的命名做 refactoring

另外以前最常發生的是移動 class 的 path ,結果忘記去改 xml ,結果噴了一堆錯誤找了老半天,現在因為 refactoring 的改善,所以不用太擔心這個問題。

Better Error Messages

這個經驗如果使用 Data binding 一段時間後都會碰到,就是每次使用不小心 error message 總是會異常的大量異常的難讀,即便只是打錯一個字,或者只是 Data package name 路徑不對。

錯誤訊息量會隨著你 binding 的 layout 數量增加,通常只是寫錯字,但錯誤訊息量讓人絕望

這次更新多了一個 Data Binding compiler 的 error message ,讓錯誤更容易被找到了。

~~~(ノ゚▽゚)ノ♪

View Binding

類似於 Data binding ,呃…在 Android 裡頭有數種方法可以讓 code 存取 layout 的物件。

findViewById 應該是最基礎也是最常見的方式,但是他並不優雅、也沒有 compile time safety ,唯一的好處是他花在 Build 的時間很短。

Databinding 或許是個好方式,他看起來也優雅,也有編譯安全檢查,唯一的缺點是要花不少時間在 Build 上。

Butterknife 或許也是個辦法,他看來很優雅,但是沒有編譯安全檢查,而且也得花時間在 Build 上頭。

Kotlin Synthetic 或許也是個辦法,他看來很優雅,Build 的速度也快,但是他並沒有編譯安全檢查。

那有沒有看起來既優雅,又有編譯時型別的安全檢查,然後 Build 的時間又短的選擇?

來自 Data binding 的靈感,就叫 View binding 。

具體用法其實跟 Data binding 類似,只是使用它不用在 xml 另外加 layout 的 tag

Coming soon in Android Studio 3.6 (′・ω・‵)。

總之使用 View binding 的好處是:

  • 這些 Binding classes (例如上圖 ProfileBinding)是由 Gradle plugin 所建構的。所以不用太管他,除非 layout 有被變動,不過那也會立即被修正(懷疑中…)
  • 100% compile time safety (在想能不能解決在 fragment 使用時機的問題)
  • 與 Android Studio 完美整合
  • 與 Data binding 兼容。意思是,假如像我之前一樣,單純使用 Data binding 去存取 xml 的 View ,那可以把 Data binding 關掉,直接使用 View Binding,那理論上會跟原先一樣正常運作。

Lifecycles

在影片中 Sergey 提到他在網路上看到文章,說是 ViewModel break SavedStatus,他不認同這樣的說法,他認為這是範圍定義上的問題。

SavedState 會在 process 被回收時,藉由 IPC 儲存至系統裡頭,而在 process restart 時,會藉由多種方式將需要的資料重新讀取進來。

而 ViewModel 則是跟著 Memory 的,會在 Process 被回收時跟著被回收。

因此 Sergey 簡單地說了一下兩者的範圍定義:

ViewModel 會負責保留著使用中的連線、DB 資料等,同時也會保留著一些 cache 或是相對大的物件。

SavedState 則是會保留一些 UI 資訊,使用者輸入資訊、選項、滑動位置等。

那是說這兩者也並非沒有重疊,所以有沒有可能兩者可以疊合在一塊?所以這邊介紹了一個東西叫:SavedStatusHandle

這東西看來目前還在 alpha 階段,所以要另外 implement

implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha01"

這東西可以透過 ViewModelProviders 傳入 SavedStateVMFactory() 去產生:

val vm = ViewModelProviders.of(this, SavedStateVMFactory(this))
.get(SavedStateViewModel::class.java)

具體用法其實跟 Bundle 類似:

class SavedStateViewModel(private val handle: SavedStateHandle) : ViewModel() {

val status: Int = handle.get<Int>("key") ?: 0
val text: String = handle.get<String>("text_name") ?: ""

val liveData:LiveData<String> = handle.getLiveData("live_data_key")


fun whenTheTimeIsRight(){
handle.set("key",status)
handle.set("text_name",text)
handle.set("live_data_key",liveData)
}
}

那其實最後有特別強調,SavedStatus 不是 Database ,請不要把它當成 Database 來使用ww。

ViewModel in kotlin ext

這次加了一點 ext 的支援,比如說

LiveData 在 Observe 的時候一定要加 Observer 不然沒辦法推斷。

這樣看起來也不像 Kotlin,所以這裡提供了一個 ktx

implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01'

加上之後就可以做推斷了

類似的情況還有 Transformations 的 map ,每次都要把 Transformations打出來很麻煩:

implement 了這個 dependency 後就可以寫成這樣:

WorkManager

WorkManger 是指處理一個沒有必要被馬上執行的 Background processing ,而他也是持續性的,意思就是他可以在 app 被關閉後重新使用,也有可能當裝置重啟後能再使用,而他也是約定性觸發的,意思就是有可能當網路連結時、有可能充電時要觸發這個 processing。

On-demand initializtion

有用過 WorkManager 應該都會知道 WorkManager 的樣子是這樣:

WorkManager.getInstance().... 

不過當你需要自定 WorkManager 諸如 Logger Level,JobRanger 之類的設定,得寫成這樣:

// provide custom configuration
val myConfig = Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.build()

// initialize WorkManager
WorkManager.initialize(this, myConfig)

由於不知道會在什麼時候用到,所以多半會把這段寫在 Application 裏頭,那意味著每次 App 打開不管有沒有用到都得 init。

這個在 Workmanager 2.1 之後 getInstance() 被 deprecate 了,取而代之的是 getInstance(Context),此外這次更新後可以使用 Configuration.Provider 去提供那些 configuration

class AppApplication : Application(), Configuration.Provider {
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
.setExecutor(Executors.newFixedThreadPool(8))
.setJobSchedulerJobIdRange(1, 10)
.setMaxSchedulerLimit(20)
.setMinimumLoggingLevel(android.util.Log.ERROR)
.build()

}
}

那 WorkManager 本身就會寫成這樣:

WorkManager.getInstance(context).enqueue(SomeWorkRequest)

透過提供的 context 就可以去找到 Application 找到 Configuration。這樣就可以達到需要使用該 WorkManger 得時候才 init 的動作了。

Google Play Service integration

Coming soon ~

其實這個目的是針對棉花糖之前的版本有更好的支援,然後也可以選擇要不要與 Google Play Service 做整合。

支援 Robolectric

現在可以利用他撰寫 Worker 的 unit Test 了,雖然詳細怎麼使用其實我沒特別去研究。

此外搭配 Robolectric ,Worker 本身可以進行 Unit Test 了,使用 TestWorkerBuilder 以及 TestListenableWorkerBuilder 可以辦到,具體方法大致如下

首先要在 Gradle 加入 implementation:

testImplementation 'androidx.work:work-testing:2.1.0-alpha01'

測試的寫起來大概是這樣:

val controller = Robolectric
.buildActivity<MainActivity>(MainActivity::class.java).setup()


val request = OneTimeWorkRequestBuilder<RunWorker>().build()
val worker = TestWorkerBuilder.from(controller.get(),request, Executors.newFixedThreadPool(8)).build()
val result = worker.doWork()

assertThat(result,`is`(androidx.work.ListenableWorker.Result.success()) )

或者用 TestListenableWorkerBuilder

val controller = Robolectric
.buildActivity<MainActivity>(MainActivity::class.java).setup()


val request = OneTimeWorkRequestBuilder<RunWorker>().build()
val worker = TestListenableWorkerBuilder.from(controller.get(),request).build()
val result = worker.startWork().get()

assertThat(result,`is`(androidx.work.ListenableWorker.Result.success()) )

Room

Coroutines 的支援

在 Room 2.0 ,如果直接在 function 前面加上 suspend ,他會不知道你在幹嘛,Build 時應該會炸掉,所以過去的作法得另外再生一個 method 去 suspend 它,在 Room 2.1 ,可以直接用 suspend 了,Room 現在可以辨別你想做什麼了。

這邊寫起來實在有點麻煩,我直接用影片上的

Full Text Search

原先 Room 在搜尋字串方面很麻煩,在 Sql 語法上要加上每一個欄位。

Room 2.1 後支援 Fts ,只要在該 Entity 加上 Fts4 這個 Annotation 後便可以使用 Full text search。

此舉可以大幅簡化 SQL 語法撰寫的長度。

Database Views

過往在 Room 裡頭麻煩的是,當有來自不同 table 物件要合併在一塊的時候,得一個一個取出來,一個一個去合併,這很麻煩,何不在 query 階段就搞定呢?

因此,在 Room 2.1 支援 SQLite 的 Database View 了,意思就是可以將 Query 出來的東西封裝進去一個 data class 裡面了。

那這邊首先要建立的是一個 DatabaseView 的 class:

建立好之後就可以直接在 annotation 的 query 上使用該 class 名稱去 query 了:

更全面的 Rx 支援

印象之前可以回傳的 Rx 類型有限,算上各式 Annotation 的回傳量也有限,現在可以在各類型的 Annotation 回傳各類型的 Reactive Source 了。

Navigation

Navigation 似乎是這次上邊這列唯一有獨立 Session 的項目,有機會可能我會在對那個 Session 再寫一篇,有機會的話。所以這邊不會講太多東西(像是這回 Paging 只有講未來展望惹w)。

  • ViewModels scoped to Navigation Graphs.

意思是例如說有一個註冊流程,這樣不用針對每一個畫面都雕一個 ViewModel ,可以包成一個讓他在 Navigation 中流動即可。

  • Navigate by URI

意思是,navigate 的對象不僅限於有 id 的 destinations,現在可以 navigate 去 graph 上沒有甚至 graph 之外的地方了。

  • Dialog destinations

終於能將 Dialog 當成目的了(之前僅限於 fragment)

結語

總覺得這次文章化的難度有點高,應該是因為相較於去年很多項目是新出的,甚至是有單獨 Session 去介紹的,算是從頭開始,今年則是已更新加上新功能居多,若是沒有一直接觸,可能直接去聽也不太曉得和過往的差別在哪裡,結果就會增加理解的難度。

如果有任何問題,或是看到寫錯字,請不要吝嗇對我發問或對我糾正,您的回覆和迴響會是我邊緣人我寫文章最大的動力。

--

--

Jast Lai
Jastzeonic

A senior who happened to be an Android engineer.