天天看點

Android Weekly Notes Issue #241

本期内容包括: 經典導航模式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

中提出的.

Android Weekly Notes Issue #241
  • intent()

    : 這個方法接收使用者輸入, 然後輸出将會作為參數傳給

    model()

    .
  • model()

    : 接收

    intent()

    的輸出作為自己的輸入, 來操縱Model, 這個方法的輸出是一個新的Model(狀态變化). 是以它不應該更新一個已經存在的Model. 因為我們想要不可變性. 注意這裡是唯一一個允許建立新Model的地方.
  • 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.後處理. 所有以

    transform

    開頭的gradle tasks都屬于這個階段. 其中最重要的是

    transformClassesWithMultidexlist

    transformClassesWithDex

    , 它們生成了.dex檔案.
  • 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

Android Weekly Notes Issue #241