本期内容包括: 經典導航模式Master/Detail的設計和實作; APK的大小讨論和增量下載下傳大小的預估工具; Model-View-Intent模式的讨論和實作; 分多個modules對build時間的影響; 測試中能夠利用的一些Android特有的接縫設計(manifest, build config, resource).
Android Weekly Issue #241
January 22nd, 2017
ARTICLES & TUTORIALS
Case Study. Master/Detail Pattern Revisited
Master/Detail是一種經典的導航流, master屏包含一個list, detail顯示某一項的詳細資訊. Android Doc.
作者講了他适配多種螢幕(包括平闆)的設計, 以及簡單的實作.
Tracking app update sizes
以前作者有一系列的文章講過apk的組成以及如何減少apk的大小.
事實上app的大小可以分下面四種:
- 送出到Google Play的APK檔案大小.
- 初始的下載下傳大小.
- 在裝置上的安裝大小.
- 更新下載下傳大小.
之前的一些文章可能都在讨論如何減少初始的大小, 但是大多數情況使用者可能隻安裝你的應用一次, 之後就隻是從Play Store更新, 是以應用的更新大小也是很重要的.
事實上Android Studio(2.2+)改善了打包apk的機制, (apk packaging), 使得每一個build都盡可能地相似, 這樣Play Store就能計算出一個較小的delta更新. 另外, Play Store也引入了新的算法, 比如最近的File-By-File patching, 同樣也有效減小了更新的大小.
是以我們要注意的就是不要介入和幹擾目前Android Studio和Play Store的這些優化, 比如不要用自定義的ZIP加密設定來自己壓縮APK. 也不要用Zopfli來再次壓縮APK.
Play Store上會顯示應用的下載下傳大小, 如果使用者已經安裝了, 則顯示的是更新大小.
對于開發者來說, 如果能在釋出前知道這些資訊就更好了, 是以作者他們開源了這個庫: apk-patch-size-estimator
這是一個指令行的工具, 可以內建到CI裡, 也可以手動比較兩個apk檔案.
這個工具實作了目前Play Store的算法, 可以幫你估計出初始的apk下載下傳大小和更新下載下傳大小.
(注意下載下傳大小和apk檔案大小不同因為Play Store可能會做進一步壓縮.)
同樣, Android Studio中也有一個圖形化的 APK Analyzer工具, 可以做apk的比較, 讓你看到到底是哪一部分的尺寸增長了.
Reactive Apps with Model-View-Intent - Part 2
上一篇文章讨論了一個好的Model層可以解決很多問題. 這篇來介紹
Model-View-Intent
模式.
Model-View-Intent模式
Model-View-Intent模式是在一個JavaScript的framework
cycle.js
中提出的.

-
: 這個方法接收使用者輸入, 然後輸出将會作為參數傳給intent()
.model()
-
: 接收model()
的輸出作為自己的輸入, 來操縱Model, 這個方法的輸出是一個新的Model(狀态變化). 是以它不應該更新一個已經存在的Model. 因為我們想要不可變性. 注意這裡是唯一一個允許建立新Model的地方.intent()
-
view()
方法傳回的model作為輸入, 然後将其展示出來.model()
用RxJava連接配接
我們希望資料流是單向的, 于是我們用了RxJava, 它很适合這種基于事件的程式設計, 在這裡主要是UI事件.
作者之後舉了一個實作的例子, 在這個例子中他們的Model層用了ViewState字尾.
SearchInteractor
用來執行搜尋, 傳回的結果是
Observable<SearchViewState>
這個模式中定義的View接口裡包含了
render()
方法, 根據傳入的狀态model顯示UI; 這個View接口其實還包含了
intent()
的方法, 傳回的是一個
Observable
, UI中用了RxBinding.
最後一步就是, 如何将View的intent和業務邏輯聯系起來呢? 這裡用到了一個額外的元件:
Presenter
這個Presenter看起來像這樣:
public class SearchPresenter extends MviBasePresenter<SearchView, SearchViewState> {
private final SearchInteractor searchInteractor;
@Override protected void bindIntents() {
Observable<SearchViewState> search =
intent(SearchView::searchIntent)
.switchMap(searchInteractor::search) // I have used flatMap() in the video above, but switchMap() makes more sense here
.observeOn(AndroidSchedulers.mainThread());
subscribeViewState(search, SearchView::render);
}
}
MviBasePresenter
是mosby中的一個類.
這個類做的事情就是當View第一次attach到Prensenter上時, 調用
bindIntent()
方法将來自view的intent綁定到業務邏輯上, 隻有第一次會綁定, 當View再次attach時不會發生.
而
subscribeViewState()
方法則處理了定于管理, 避免記憶體洩露(具體原因見原文).
How modularization affects build time of an Android application
一個Android應用至少有一個application module, build這個module之後得到一個.apk檔案.
application module之間不能互相依賴, 它隻能依賴于library, build library module的結果是得到一個.aar(Android Archive Library)檔案.
build的過程可以粗略分為5個階段:
- 1.準備依賴.
- 2.Merge資源和manifest.
- 3.編譯. 從annotation processors開始, 把源碼編譯成位元組碼.
- 4.後處理. 所有以
開頭的gradle tasks都屬于這個階段. 其中最重要的是transform
和transformClassesWithMultidexlist
, 它們生成了.dex檔案.transformClassesWithDex
- 5.打包釋出. 對library來說是生成.aar, 對application來說是生成.apk.
我們都知道gradle隻有在輸入變化了的情況下才會重跑task. 而且如果一個module沒有變化, 也不會被重新build, 那麼就出現了一種假設: 多個module應用的增量build要比單個module的快, 因為隻有被改變了的module才會重新編譯.
作者想驗證這種假設是否正确.
他用的工具就是:
./gradle assembleDebug --profile
做了一系列實驗之後證明這個假設還是有道理的.
實驗過程中的一些發現:
1.當應用被拆分為多個modules之後, 改變application module中的代碼, build時間會減少; 但是library中的代碼, build時間反而會增加. 這是因為library build的時候debug和release的tasks都執行了(并不知道為什麼).
當library module被這樣添加的時候:
dependencies {
compile project(path: ':app2')
compile project(path: ':app3')
}
不管app目前的build type是什麼, app永遠依賴的是library的release版本.
這是一個Gradle目前的限制. 參見Library-Publication.
幸運的是, 我們可以改變這一行為:
首先在library中添加:
android {
defaultConfig {
defaultPublishConfig 'release'
publishNonDefault true
}
}
讓它也可以釋出debug版.
在app中依賴的時候:
dependencies {
debugCompile project(path: ':app2', configuration: "debug")
releaseCompile project(path: ':app2', configuration: "release")
debugCompile project(path: ':app3', configuration: "debug")
releaseCompile project(path: ':app3', configuration: "release")
}
這樣debug和release都隻依賴各自對應版本的library了.
2.不管我們改動的是library中的代碼還是application中的代碼, application module永遠都會被重新編譯, 是以減小app module的尺寸很有意義.
3.上面這些都是library之間互相獨立的情況, 如果library之間還有互相依賴, 那麼build時間也會變長.
4.如果應用超出了DEX的方法數限制, 用了multidex, 也會增加build時間, Android 5.0開始使用了一個叫做ART的runtime, 在這方面有一些優化, 可以減少build時間, 是以我們可以在開發的時候設定最小API是21: Optimize multidex in development builds.
Exploiting Android Seams for Testing and Flexibility
如何讓Android應用代碼可測試? 答案是建立一些接縫. 這篇文章中, 作者将一些Android特有的接縫, 來讓我們的應用更加靈活和易測.
Manifest接縫
使用Merge rule markers可以友善地更改manifest.
比如在build variant是mock的時候, 由于我們在src/mock/AndroidManifest.xml裡這樣寫:
<!-- src/mock/AndroidManifest.xml -->
<activity
android:name=".StubConfigActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".MainActivity">
<intent-filter tools:node="remove">
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
是以在build mock的時候, 啟動Activity會被替換成上面這個
StubConfigActivity
還有更多的可能值得探索, 比如你可以替換filter的内容, 進而改變預設intent啟動的Activity.
BuildConfig接縫
在gradle中可以根據不同的build variant來定義
BuildConfig
中的變量值.
預設情況下
BuildConfig
中會包含一些有用的變量比如
DEBUG
FLAVOR
我們可以建立更多額外的變量:
productFlavors {
mock {
buildConfigField('Boolean', 'MOCK', "true")
}
}
一個簡單的應用case是我們可以定義不同的base url:
defaultConfig {
buildConfigField('String', 'API_BASE', '\"api.awesomecompany.com\"')
}
productFlavors {
sandbox {
buildConfigField('String', 'API_BASE', '\"localhost:8080\"')
}
}
Resource接縫
不同build variants的資源就像manifest一樣, 最後會被merged. 但是對于資源我們沒有markers可以控制它們如何merge.
我們可以利用預設的merge行為: Resource merging.
優先級是這樣的:
build variant > build type > product flavor > main source set > library dependencies
是以我們可以把預設的資源放在main裡, 然後在特定的build variant再建立一份覆寫它們.
LIBRARIES & CODE
Reptar
RxJava2.x的有用的類的集合.
Toasty
前面加了一個icon的Toast, 帶背景顔色, 除了内置的error, info, success, warning等幾種形式, 還可以自定義.
Google-Actions-Java-SDK
非官方的Google Actions Java SDK.
作者: 聖騎士Wind
出處: 部落格園: 聖騎士Wind
Github: https://github.com/mengdd
微信公衆号: 聖騎士Wind