March 5th, 2017
Android Weekly Issue #248.
本期內(nèi)容包括: 為什么有時(shí)候應(yīng)該讓你的應(yīng)用崩潰(而不是一味保護(hù)); Trello離線模式實(shí)現(xiàn)中兩個(gè)id的問(wèn)題; 如何讓Dagger的component按照scope保存, 在屏幕旋轉(zhuǎn)時(shí)不重建; 用Dagger構(gòu)建Realm的數(shù)據(jù)庫(kù)遷移邏輯;
利用各種mock工具寫單元測(cè)試; Map上markers的動(dòng)畫實(shí)現(xiàn); JUnit5中@DisplayName的使用; RxJava中的Single和Completable使用; 舉例說(shuō)明如何給FindBugs寫自定義的探測(cè)器; Android中靜態(tài)代碼分析工具的使用; Trello離線實(shí)現(xiàn)中sync失敗情況的處理.

ARTICLES & TUTORIALS

Why your app should crash

作者認(rèn)為有時(shí)候讓應(yīng)用崩潰反而是有好處的.

以NPE為例, 有時(shí)候我們會(huì)習(xí)慣性地加很多null判斷, 有的是多余的, 有的防御型的代碼反而會(huì)掩蓋了真實(shí)問(wèn)題所在. 比如當(dāng)一個(gè)不合理的情況發(fā)生時(shí), 讓用戶看到一個(gè)不可理解的頁(yè)面, 比如空白, 然后我們開發(fā)者根本不知道這種情況的發(fā)生.

與其這樣掩蓋錯(cuò)誤, 不如讓應(yīng)用崩潰, 讓開發(fā)者立即知道問(wèn)題的原因.

實(shí)用建議:

  • 永遠(yuǎn)讓應(yīng)用對(duì)外來(lái)輸入(比如service的響應(yīng), UI的輸入, 進(jìn)來(lái)的intents)保持健壯性.

  • 在程序的入口點(diǎn)保證數(shù)據(jù)的完整性. 這樣不合理的數(shù)據(jù)就不會(huì)到處都是, 所以你不用到處檢查.

  • 如果你不確定某個(gè)錯(cuò)誤是否會(huì)在某個(gè)地方發(fā)生, 先假裝它不會(huì)發(fā)生, 在測(cè)試階段再驗(yàn)證.

  • 如果某個(gè)方法在產(chǎn)品環(huán)境不能被調(diào)用, 或者只能被調(diào)用一次等, 拋出IllegalStateException.

  • 永遠(yuǎn)在發(fā)布之前進(jìn)行完整測(cè)試, 這樣你就會(huì)在用戶之前, catch住可怕的崩潰.

The Two ID Problem

還是Trello開發(fā)離線模式的系列文章, 本篇講他們遇到的一個(gè)很tricky的問(wèn)題: id問(wèn)題.

在他們的項(xiàng)目里, 所有的models都有一個(gè)id, 用以和server通信, 以及定義model之間的關(guān)系.

如果是在離線模式下, 就不能依靠server來(lái)提供這個(gè)id, 客戶端需要自己生成.

所以離線模式下有兩種id, 一種是本地生成的, 一種是用來(lái)和server通信的.

他們想過(guò)幾個(gè)辦法, 比如在sync的時(shí)候?qū)ocal的id轉(zhuǎn)化為server id; 或者干脆存儲(chǔ)一個(gè)id的Pair類, 但是都有難以維護(hù)或者性能缺陷等種種問(wèn)題.

最后他們提出了一個(gè)叫local-server barrier的解決方案. 基本的原則就是, 在app中, 只使用local的id, 同server通信時(shí), 使用server id. 好處: 首先保證了客戶端代碼的簡(jiǎn)潔, 只有網(wǎng)絡(luò)通信層需要考慮到server id; 重構(gòu)代碼量小.

Retaining Dagger components

如果你用dagger創(chuàng)建了component, scope是Activity或者Fragment, 那么你可能遇到過(guò)這個(gè)問(wèn)題: 旋轉(zhuǎn)屏幕之后, 所有的依賴都重建了, 因?yàn)槟銊?chuàng)建了一個(gè)新的component.

如果你想要在configuration變化的時(shí)候不重建, 你就需要把component存儲(chǔ)在一個(gè)全局的地方, 但是這樣的話, 當(dāng)你真的結(jié)束你的Activity和Fragment的時(shí)候, 你如何釋放這些component呢?

你需要分層地(hierarchical)存儲(chǔ), service-tree就是用來(lái)做分層存儲(chǔ)東西的一個(gè)工具.

文中基本思想是把Application的component作為根節(jié)點(diǎn), 然后Activity和Fragment的component作為樹形結(jié)構(gòu)的葉子節(jié)點(diǎn)逐級(jí)存儲(chǔ). Activity和Fragment的節(jié)點(diǎn)什么時(shí)候移除, 有一些判斷條件和時(shí)機(jī)選擇, 詳見原文代碼.

The Burden of Knowledge

鼓勵(lì)在team里分享知識(shí).

Realm Migrations Supercharged with Dagger

使用Dagger2可以大幅度改善Realm中的數(shù)據(jù)遷移處理. 具體的做法是把每一步的遷移處理都放在一個(gè)統(tǒng)一接口的實(shí)現(xiàn)類里, 然后注入它們.

@Modulepublic class MigrationsModule {    @Provides
    @IntoMap
    @IntKey(1)    static VersionMigration provideVersion1Migration() {        return new Version1Migration();
    }    @Provides
    @IntoMap
    @IntKey(2)    static VersionMigration provideVersion2Migration() {        return new Version2Migration();
    }
}

所以最后的遷移類看起來(lái)就是這樣:

@Reusablepublic class Migration implements RealmMigration {    private Map<Integer, Provider<VersionMigration>> versionMigrations;    @Inject
    Migration(Map<Integer, Provider<VersionMigration>> versionMigrations) {        this.versionMigrations = versionMigrations;
    }    @Override
    public void migrate(final DynamicRealm realm, long oldVersion, long newVersion) {        for (int i = (int) oldVersion; i < newVersion; i++) {            final Provider<VersionMigration> provider = versionMigrations.get(i);            if (provider != null) {
                VersionMigration versionMigration = provider.get();
                versionMigration.migrate(realm, i);
            }
        }
    }
}

這樣做的好處:

  • 1.以后再有數(shù)據(jù)庫(kù)升級(jí)也不需要再改這個(gè)類了.

  • 2.不需要逐個(gè)check每個(gè)版本號(hào), 自動(dòng)只從需要的版本號(hào)開始做遷移.

  • 3.每個(gè)遷移模塊都變成了可測(cè)試的單元.

我覺(jué)得作者的這種處理結(jié)構(gòu)很好, 不僅僅限于Realm數(shù)據(jù)庫(kù)的遷移, 其他的數(shù)據(jù)庫(kù)遷移也可以用類似的結(jié)構(gòu)來(lái)處理.

How to be a Mock-Star…

介紹如何用mockito來(lái)測(cè)試一個(gè)MVP的程序.

首先測(cè)試Presenter的部分, 這里Mock了數(shù)據(jù)源和各種錯(cuò)誤響應(yīng).

測(cè)試Repository, 需要用到MockWebServer, 來(lái)模擬測(cè)試環(huán)境下的響應(yīng).

Animating Markers with MapOverlayLayout

作者App的動(dòng)畫實(shí)現(xiàn)討論第二發(fā), 如何讓地圖上的markers帶縮放和漸變動(dòng)畫 -> 用MapOverlayLayout.

文中有詳細(xì)的實(shí)現(xiàn)代碼, 基本思路就是在這個(gè)MapOverlayLayout中保存一個(gè)View的列表, 然后在自定義View中實(shí)現(xiàn)每個(gè)marker在相應(yīng)動(dòng)作時(shí)的動(dòng)畫.

What Unit Tests are Trying to Tell us About Activities Pt 2

以Activity/Fragment作為基本構(gòu)建單元, 讓程序難以測(cè)試, 本文舉例說(shuō)明了這一點(diǎn).

JUnit 5: DisplayName

JUnit 5提供了@DisplayName, 這樣測(cè)試報(bào)告里case顯示的名字將是@DisplayName定義的字符串.

相比原先的方法名來(lái)說(shuō), 這個(gè)字符串是可以帶空格的, 所以比之前的可讀性增強(qiáng)了.

Clearer RxJava intentions with Single and Completable

RxJava中我們經(jīng)常用到的類就是Observable, 然后處理三個(gè)事件: onNext()onError()onCompleted().

但是有些時(shí)候我們并不需要關(guān)心全部這三個(gè)事件, 這時(shí)候我們就可以用Single<T>Completable.

Single<T>返回一個(gè)值或者一個(gè)error.
它和Observable之間可以互相轉(zhuǎn)換: 用toObservable()singleOrError()方法.

Completable, 只有onCompleted()onError(). 它不發(fā)射任何值, 可以在它之后用andThen()來(lái)添加另一個(gè)Observable, 進(jìn)行后續(xù)其他操作.

Observable不能直接轉(zhuǎn)換為Completable, 因?yàn)椴恢?code style="font-family: "Courier New", sans-serif !important; line-height: 1.8; margin: 1px 5px; vertical-align: middle; display: inline-block; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; padding: 0px 5px !important; border-radius: 3px !important;">Observable到底會(huì)不會(huì)停止. 可以把Single轉(zhuǎn)換為Completable, 用toCompletable()方法.

Custom FindBugs detectors in Android

Android中有兩種工具可以做進(jìn)一步的編譯期檢查: Android LintFindBugs.

FindBugs是一個(gè)靜態(tài)的分析工具. 本文的主要任務(wù)是講解如何實(shí)現(xiàn)一個(gè)自定義的檢測(cè)器來(lái)檢測(cè)一種特定的錯(cuò)誤.

作者的例子是try-with-resources模式的代碼.
這是Java 7新加的模式try-with-resources:

try (Cursor c = db.query(...)) {
    c.moveToFirst();    while (!c.isAfterLast()) {        return new Foo(
                c.getString(c.getColumnIndex(...))
                ...
        );
    }
}

try語(yǔ)句中聲明的資源將會(huì)在這個(gè)block結(jié)束的時(shí)候自動(dòng)close.

但是這個(gè)特性最低需要API 19.
所以為了兼容舊版本, 我們不得不使用finally來(lái)自己close:

Cursor c = db.query(...);try {
    c.moveToFirst();    while (!c.isAfterLast()) {        return new Foo(
                c.getString(c.getColumnIndex(...))
                ...
        );
    }
} finally {
    c.close();
}

但是有時(shí)候我們會(huì)忘記close導(dǎo)致了泄露, 所以需要實(shí)現(xiàn)一個(gè)自定義的findbugs檢測(cè)器來(lái)檢查這種錯(cuò)誤. 具體實(shí)現(xiàn)步驟和討論見原文.

Static Code Analysis Tools

Android中流行的靜態(tài)代碼檢測(cè)工具:

  • Lint

  • PMD

  • FindBugs

本文介紹它們?nèi)绾闻渲煤褪褂?

Sync Failure Handling

Trello離線模式文章, sync失敗的處理.

在發(fā)請(qǐng)求的時(shí)候可能會(huì)發(fā)生各種各樣的錯(cuò)誤, 分為暫時(shí)性的和永久性的兩類.
對(duì)于永久性的錯(cuò)誤, 我們可以直接放棄delta; 但是對(duì)于暫時(shí)性的錯(cuò)誤, 我們需要重試. 這里就需要考慮重試的時(shí)間和重試的次數(shù).

另外還有一種情況是客戶端發(fā)了請(qǐng)求, server也收到了, 但是客戶端在收響應(yīng)的時(shí)候失敗了, 所以客戶端可能會(huì)找機(jī)會(huì)重新發(fā)請(qǐng)求, 為了保證冪等性, 我們的每一個(gè)請(qǐng)求都有一個(gè)唯一的id, 如果server發(fā)現(xiàn)同樣的id, 只處理第一個(gè).

對(duì)于多個(gè)用戶編輯的沖突處理, 當(dāng)前用的是簡(jiǎn)單的以后者為準(zhǔn)的方式.

撤銷本地不合理數(shù)據(jù), 以server數(shù)據(jù)為準(zhǔn), 更新本地?cái)?shù)據(jù), 這就需要在sync開始的時(shí)候先講本地改動(dòng)上傳.

DESIGN

LottieFiles

免費(fèi)的Lottie動(dòng)畫.

LIBRARIES & CODE

DiscreteScrollView

可滾動(dòng)的列表, 中間的項(xiàng)目放大. (基于RecyclerView, 長(zhǎng)得有點(diǎn)像ViewPager.)

SimpleRatingBar

五星評(píng)價(jià)View, 用kotlin實(shí)現(xiàn)的.

InstaCropper

剪切圖像的View, 類似于Instagram的crop.

GuildWars2_APIViewer

一個(gè)app, 用來(lái)查看Guild Wars 2的API響應(yīng).
用了Dagger2, Retrofit2, RxJava2, MVVM架構(gòu).

here-be-dragons

一個(gè)Intellij/Android Studio插件, 你可以在一個(gè)方法上標(biāo)記@SideEffect, 之后你調(diào)用這個(gè)方法的代碼行左邊會(huì)顯示出一個(gè)龍的圖標(biāo).

RoboGif

一個(gè)python的小工具, 可以把Android設(shè)備上的錄屏生成一個(gè)GIF圖.

service-tree

一個(gè)存儲(chǔ)service的樹形結(jié)構(gòu). (本期文章Retaining Dagger components有講.)

分類: Android Weekly

標(biāo)簽: AndroidAndroid WeeklyCrashDaggerDagger2RealmMockitoMockWebServerGoogle MapJUnit5

http://www.cnblogs.com/mengdd/p/android-weekly-notes-issue-248.html