天天看點

Android Weekly Notes Issue #222

Android Weekly Issue #222, 中文筆記. 本期文章包括:

Okio中的三個方法, 檢測測試覆寫率對build時間的影響, Android Support Annotations, Espresso測試相關, 一個新的MVP庫ThirtyInch的介紹, Android學習資源和有用的工具等.

Android Weekly Issue #222

September 11th, 2016

ARTICLES & TUTORIALS

Forcing bytes download in Okio

這是Jake Wharton的文章, 關于Okio的

BufferedSink

.

okio 是一個java io庫, 包裝了一套API用來讀寫和處理資料. 文檔見: okio doc.

很多庫都是在其之上寫的, 比如okhttp, Retrofit, Moshi. 這裡有個視訊: A Few OK libraries

有三個方法可以強制把bytes放入底層的

Sink

flush(), emit() and emitCompleteSegments()

flush()

調用這個方法會使得所有緩存的位元組移動到Sink, 然後Sink立即flush自己. 是以調用

flush()

方法傳回後, 你可以保證所有的位元組都到了目的地Sink.

當用多級buffer時,

flush()

會清除所有level的buffer. Okio中多級buffer的花銷很小, 一個flush會讓每一級把自己的segments移到下一級.

java io中的多級buffer每一級都自己管理自己的位元組, 是以flush操作會讓每一級都拷貝資料到下一級.

流類型的

close()

flush()

的行為類似, 在關閉流之前把所有的位元組都寫了.

emit()

發射位元組的行為和flush類似, 但是不是遞歸的. 調用

emit()

會使得所有緩存的位元組移動到Sink, 但是與

flush()

不同, 此時Sink并不會做其他的操作.

emitCompleteSegments()

調用這個

emitCompleteSegments()

方法僅僅移動那些完整

segment

的位元組移動到Sink.

segment

是okio的一個概念.

原文中有動圖圖解, 看起來更清楚一些.

Use cases

Writing messages to a WebSocket ->

flush()

Encoding a video to a file ->

emitCompleteSegments()

+

emit()

/

flush()

at the end

Serializing an object to JSON ->

emit()

Using Android Studio's Performance Monitors

作者他的音樂應用遇到了不響應的問題, 然後他打開Android Studio的performance monitor看到了是記憶體問題, 然後他track了一下出問題的時候的memory allocation, 最後發現是有一張要下載下傳的圖太大了.

The hidden cost of code coverage

測試覆寫率是一個很好的方法來激勵你和你的團隊多寫一些測試, 但是你知道嗎, 打開測試覆寫率的檢測會讓你的build變慢.

要測量build時間:

./gradlew clean assembleDebug --profile

然後作者發現其中

:app:transformClassesWithJacocoForDailyDebug

占用的時間達到了build時間的14%.

就是因為這句:

buildTypes {
    debug {
        ...
        testCoverageEnabled true
    }
}
           

解決辦法

隻在需要測試覆寫率的時候用它, 是以加一個變量:

/gradlew -Pcoverage clean connectedDebugAndroidTest

gradle裡這樣寫:

buildTypes {
    debug {
        ...
        testCoverageEnabled (project.hasProperty('coverage') ? true : false)
    }
}
           

當你不加這個flag的時候就不會檢查測試覆寫率.

Android Development Useful Tools

methodscount 可以統計library的方法數, 因為Android有64k的方法數限制.

Stetho

可以看網絡請求, 資料庫, shared preferences等.

LeakCanary 檢測記憶體洩露的庫.

apk-method-count 這個網站可以檢測apk中的方法數, 根據包分别顯示.

Android Asset Studio

生成圖檔, 9-patch等的一系列工具.

Buck Facebook開發的一套build系統.

Gradle, please 輸入你要的庫, 然後就可以找到對應的dependencies裡應該寫的 compile xxx.

Proguard或DexGuard

Genymotion

Material Design Icons

Introduction to Automated Android Testing - Part 6

系列文章的最後一篇, 作者講用Espresso寫UI測試.

如果資料是動态的, 那麼要測試View包含指定的資訊是很難的. 資料有可能變化, 但是我們的測試不能是以而失敗. 是以為了測試可靠和可重複, 我們不應該調用production的API.

是以第一步是mock API, 有幾種方法:

    1. 用WireMock (需要server).
    1. 用OkHttp的MockWebServer, 在你的裝置上跑一個webserver.
    1. 建立一個Retrofit REST接口, 傳回一些dummy對象.

Mock資料的時候利用了Gradle的flavor, 可以建立一個mockDebug flavor, 然後在src目錄下建一個mock目錄, 放一些mock的代碼進去, 然後再建一個prod目錄, 把真實的實作移動到那裡去.

Espresso基礎

用Espresso寫測試, 基本格式是這樣:

onView(withId(R.id.menu_search))      // withId(R.id.menu_search) is a ViewMatcher
  .perform(click())               // click() is a ViewAction
  .check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
           

ViewMatchers

是用來定位View的 有withId, withText, withTag等.

ViewActions

是用來跟View互動的, 比如點選, 輸入等.

ViewAssertations

是用來做出斷言的.

這裡有個cheat-sheet可以查: android-expresso-testing.pdf

Writing Espresso UI Tests

這部分具體講了作者例子的UI測試寫法, 見原文.

最後作者還看了一下測試覆寫率.

What 2 years of Android Development Have Taught Me

作者講了他兩年來的心路曆程, 以及他總結的一些To do和Not to do.

  1. 不要重複造輪子.

    可以用android-arsenal來查庫.

  2. 明智地選擇庫.

    選擇庫的時候看看星多不多, 作者有沒有什麼其他流行的庫. 再看看issues, 有時間的話可以看代碼.

    Dryrun可以用來跑sample.

  3. 坐下來看代碼. 我們應該花費多數的時間閱讀别人的代碼, 而不是寫自己的代碼.

    你能寫出來的代碼是你已經知道的東西的一個反映, 要不斷成長和提高自己, 隻能不斷閱讀和學習别人的代碼.

    這裡有Library清單和app清單.

  4. 保持一個好的代碼風格. 這裡有一些參考: code-style, android-guidelines.
  5. 用Proguard, 這樣release版本不但縮小了代碼也做了混淆. 這裡還有一個DexGuard.
  6. 使用一個合理的架構. 可以用MVP來解耦, 這裡有個demo: Android-CleanArchitecture, 這裡是它相關的Guide文章: Guide on Clean Architecture.

    更多資源:

    androidmvp,

    mosby,

    android-architecture.

  7. 對于獨立工作的開發, 還需要注意一些UI/UX相關的原則. 可以上Dribble和MaterialUp多看看.
  8. 分析是你的好朋友. 分析包括了crash報告和app使用記錄, 可以用Firebase.
  9. 做一個市場忍者. 對于獨立開發者來說, 需要marketing. 這是一個市場分析工具: sensor tower.
  10. 優化你的App. 推薦檢查記憶體洩露的工具: leakcanary.
  11. 優化gradle的build時間. 這裡有兩個Guides: speeding up gradle build, making gradle builds faster
  12. 多做測試. 釋出前花時間測試, 不要急.
  13. 兼顧多種機型, 包括螢幕尺寸, API level, 不同廠商的OS等.
  14. 用Git. 這是一個Git branch model. 如果你不能負擔github上的private repo, 你可以試試BitBucket
  15. 讓黑客覺得難處理. Android很容易被攻擊, app可以被輕易地反編譯和分析. 你需要知道如何處理你的API keys, 如果你需要處理使用者的敏感資訊, 你應該知道如何加密. 秘鑰也要妥善存儲. 任何存儲在資料庫中的敏感資訊也都要加密. 相關的資料可以看Adding Tampering Detection to Your app和Hiding Secrets in Android Apps.
  16. 在低級裝置上開發. 低級的裝置容易暴露問題.
  17. 學習設計模式. 這裡有一個repo講了所有的Java中的設計模式: java-design-patterns. 另外還推薦書籍: GoF的設計模式, Martin Fowler的重構, Joshua Bloch的Effective Java.
  18. 貢獻自己的力量. StackOverflow, Github, blog posts...

Android Support Annotations

使用Annotation library:

compile 'com.android.support:support-annotations:<latest-library-version>'
           

如果你已經用了

appcompat library

, 你就已經可以用annotations了, 因為appcompat自己就用了它.

annotations按照用法和功能來分組:

  • Nullness Annotations
  • Resource Annotations
  • Thread Annotations
  • Value Constraints Annotations
  • Others : Permissions Annotations, CheckResults Annotations and CallSuper Annotations.

@Nullalbe

@NonNull

用來檢查變量, 參數和方法傳回值為null與否.

@NonNull

表示變量, 參數或傳回值不能為null, 如果為null了編譯器會給出警告.

@Nullalbe

表示可能為null, 用這個注解的時候表示代碼中應該加上null check.

因為資源号都是int值, 是以如果你把一個drawable的int傳給一個期待string resource的代碼, 編譯器是會接受的.

資源注解就是用來做這種情況的類型檢查的.

比如用

@StringRes

來标記參數, 如果你傳入一個drawable的id, IDE就會把它标記出來.

每一個Android的資源類型都有一個對應的資源注解, 比如類型是

Foo

, 那麼對應的資源類型注解就是

FooRes

有一個特殊的

@AnyRes

, 用來表示任意的資源類型.

這種注解用來檢查方法是不是在特定的線程調用的. 有:

  • @UiThread

  • @MainThread

  • @WorkerThread

  • @BinderThread

@UiThread

@MainThread

是可互換的.

如果一個類中的所有方法都是從同樣的線程調用搞得, 那麼可以直接把類給标記了.

@IntRange

,

@FloatRange

@Size

注解是用來驗證參數的值的. 比如

@IntRange

就驗證參數是在一個給定的int範圍之内.

比如下面的方法確定傳入的參數是0到255:

public void setAlpha(@IntRange(from=0, to=255) int alpha) {
    //set alpha
}
           

相應的

@FloatRange

檢查參數是在一個指定範圍内的浮點數.

@Size

注解是用來檢查集合的大小, 還有字元串的長度. 比如

@Size(min=1)

用來檢查集合不為空,

@Size(2)

檢查集合有兩個元素.

CheckResult Annotations

這個注解是用來檢查一個方法的傳回值确實被使用了.

一個比較好的例子是

String.trim

方法, 當這個方法被

@CheckResult

标注, 如果它的傳回值沒有被使用, IDE就會報錯.

另外一些比較值得看的注解有

@CallSuper

@Keep

@RequiresPermission

可以直接檢視support annotations的reference.

其他參考資料:

Improve code Inspection with Annotations

Support Annotation documentation

Improving your code with android support annotations

ActivityTestRule: Espresso's Test "Lifecycle"

作者這篇文章的目的是講講用Espresso的

ActivityTestRule

寫的測試中的操作順序, 讨論像

beforeActivityLaunched()

afterActivityLaunched()

, 和

afterActivityFinished()

這些方法相對于測試和Activity的生命周期都是什麼時候被調用的.

首先作者介紹了Espresso 2.0及之前的舊的

ActivityInstrumentationTestCase2

ActivityTestRule

的"生命周期".

新的寫法是這樣:

@RunWith(AndroidJUnit4.class)
public MyNewTest {
  @Rule
  public MyCustomRule<MyActivity> testRule = new MyCustomRule<>(MyActivity.class);
  
  @Test
  public void testStuff() {
    // Wow where's all the boilerplate code?
    
    // Verify Oscar Grouch is no longer grouchy.
  }
}
           

而其中的MyCustomRule:

public class MyCustomRule<A extends MyActivity> extends ActivityTestRule<A> {
  public MyCustomRule(Class<A> activityClass) {
    super(activityClass);
  }
  
  @Override
  protected void beforeActivityLaunched() {
    super.beforeActivityLaunched();
    // Maybe prepare some mock service calls
    // Maybe override some depency injection modules with mocks
  }
    
  @Override
  protected Intent getActivityIntent() {
    Intent customIntent = new Intent();
    // add some custom extras and stuff
    return customIntent;
  }
  
  @Override
  protected void afterActivityLaunched() {
    super.afterActivityLaunched();
   // maybe you want to do something here 
  }
    
  @Override
  protected void afterActivityFinished() {
    super.afterActivityFinshed();
    // Clean up mocks
  }
}
           

ActivityTestRule

: launchActivity=false;

ActivityTestRule

的第三個參數允許開發者明确指定對每一個test case啟動一個Activity.

public ActivityTestRule(Class activityClass, boolean initialTouchMode, boolean launchActivity)

把第三個參數設定為false, 就可以寫出這樣的測試:

@RunWith(AndroidJUnit4.class)
public class MultipleIntentsTest {
  @Rule
  public ActivityTestRule<MyActivity> testRule = new ActivityTestRule<>(MyActivity.class,
          false,    // initialTouchMode
          false);  // launchActivity. False to set intent per test);

  @Test
  public void testOscarGrouchy() {
    Intent grouchyIntent = new Intent();
    // intent stuff
    grouchyIntent.putExtra("EXTRA_IS_GROUCHY", true);
    testRule.launchActivity(grouchyIntent);
    // verify Oscar is grouchy
  }
  
  @Test
  public void testOscarNotGrouchy() {
    Intent happyIntent = new Intent();
    // intent stuff
    happyIntent.putExtra("EXTRA_IS_GROUCHY", false);
    testRule.launchActivity(happyIntent);
    // verify Oscar is not grouchy
  }
}
           

這個lauchActivity的值預設是為true. 設定為false之後我們在每一個test case裡面自己啟動activity. 對生命周期産生的影響作者也做了圖對比分析, 見原文.

作者最後還講了幾個點, 關于測試遷移, 以及Activity的生命周期方法中啟動Intent相關的需要注意的地方.

People and resources to learn Android programming from

作者分享了關于Android程式設計學習中他積累的人和資源.

Twitter

Chiu-Ki Chan

Donn Felker, 他的部落格: blog. 他和Kaushik Gopal一起弄了Fragmented Podcast. 這裡還有一個視訊教程的網站: Caster.io.

Jake Wharton, 這個大家都知道啦, 這是他的部落格: blog.

Kristin V Marsicano, 這裡有一個她的關于Activity生命周期的演講Activities in the Wild, 可能有一些你沒有想過的東西.

Ryan Harter

The Practical Dev

最後這有一個清單: Tweet Android List

Podcasts

Fragmented 兩個獨立開發者辦的.

Android Developers Backstage 寫Android的那些人辦的.

Material 這不是一個技術廣播, 講一些Google新聞.

Videos

Caster.io

Realm.io

Android Dialogs (YouTube)

Newsletters

Android Weekly

Kotline Weekly

General Reading

Medium: 标簽androiddev和android-app-development

Conferences

GoogleIO

360|AnDev

Droid Con NYC

ThirtyInch - a new MVP library for Android

近年來MVP已經變成了Android社群中很流行的一種設計模式, MVC和MVVM也有人用. 這些模式的共同點就是把業務邏輯從Activity中抽取來.

這樣做的好處首先是我們可以盡量把需要測試的邏輯用JVM上的單元測試測, 而不是用模拟器上的androidTests.

當然有些需要UI測試的地方仍然會用Espresso.

作者介紹了他們的MVP庫: ThirtyInch.

這個庫開始的時候mosby還沒有release, 建議讀一下mosby作者關于MVP的文章: mosby, 其中關于passive View的概念也在ThirtyInch中用到.

ThirtyInch:

ThirtyInch.

TiPresenter

TiView

TiView

是一個接口, 可以被attach和detach.

TiPresenter

有四個生命周期事件:

  • onCreate()

    : 初始化的時候調用一次, 此時view還沒有attach.
  • onWakeUp()

    : view attach了, 并且對使用者變為可見.
  • onSleep()

    : 在這個調用之後, view将被detach, 并且變為對使用者不可見.
  • onDestroy()

    : 在Activity/Fragment完全銷毀的時候調用一次.

onWakeUp()

onSleep()

對應了

onStart()

onStop()

, onResume/onPause沒有對應的回調支援, 因為這些生命周期回調應該在View層處理, 見: Presenters dont need lifecycle

ThirtyInch有什麼不同

  • 可配置. 可以傳

    TiConfiguration

    對象給TiPresenter, 去掉一些features.
  • 所有Presenter的生命周期都按照正确的順序調用,

    onCreate()

    onDestroy()

    隻調用一次.
  • 不依賴RxJava. 它有一個獨立的Rx module.
  • View接口的方法注解. 比如

    @CallOnMainThread

    @DistinctUntilChanged

  • Public API. 一些基層API可以被所有人利用起來.
  • 不用繼承

    TiActivity

    . 你可以利用CompositeAndrodi, 把plugin module作為你的依賴, 然後把

    TiActivityPlugin

    加到你的Activity.

之後作者舉了一個Hello World的例子, 附圖講解很好, 見原文.

不是嚴格的MVP, MVVM也可以

作者又列出了一個ViewModel的圖.

ViewModel存儲了目前UI的狀态資料.

當ViewModel中的資料變化時立即應用到View, 這裡

@DistinctUtilChanged

的使用避免了資料不變時候的重複操作.

測試, Keep Android At Arm's Length

MVP的初衷之一是為了友善寫測試, 因為Android SDK中的一些方法和類不好mock, 是以Presenter中應該是沒有Android相關的東西的, 比如Context和Fragment等, Presenter隻知道View接口和其中的方法, 是純java的.

這就是"Keep Android At Arm's Length."的意思, 不要把Android和邏輯代碼綁在一起, 庫的名字ThirtyInch也是來自于這個原則, 因為三十寸是人類手臂, 肩膀到手指的平均長度.

How does the Presenter survive the configuration change?

Activity在螢幕方向旋轉時會被重建. 此時沒有被序列化儲存的資訊就會丢失, 網絡請求要麼被取消, 要麼被忽略, 重新請求.

序列化資料會費時, 而且在這種情況下, 序列話的資料幾秒之後就要被反序列化.

Android Framework提供了兩個方法來避免這種不必要的序列化:

  • Fragment的

    setRetainInstance(true)

    , 之前的那個Fragment執行個體會被儲存.
  • 使用

    Activity#onRetainNonConfigurationInstance()

    Activity#getLastNonConfigurationInstance()

    來存儲和恢複對象. 這也是Android儲存上面retained Fragments的方法. 這個方法最近被廢棄了.

在Java中還有一個比較簡單粗暴的方法是儲存一個應用級别的單例.

ThirtyInch使用了上述的三種方法來確定TiPresenter在configuration變化的時候不死. 單例的解決方法盡在一些邊緣情況必須.

當使用

TiPresenter

的時候不需要再實作

onSaveInstanceState(Bundle)

方法了, 因為資料都存在Presenter中.

Firebase Analytics VS Google Analytics

作者之前對比過 Firebase Crash Reporting和Crashlytics.

在這篇文章裡, 作者對比Firebase和Google的分析工具, 下面簡稱FA和GA.

首先GA是2005年就推出了, 那時候根本沒有Android和iOS, 是以GA最開始是網站用的, FA是今年推出的, 從一開始目标就是移動應用.

然後作者總結了FA的優勢和目前存在的幾個不足.

最後的結論就是:

如果你隻有app, 用FA; 如果你隻有網站, 用GA; 如果你兩個都有, 則兩個都用.

VectorDrawable Fill Windings

作者有一個Sketch做的資源, 是一個空洞圖, 結果放在程式裡看的時候中間的洞沒有了, 變成實心的了.

作者分析并詳細解釋了出現這種問題的原因, 并提供了兩種解決方案.

DESIGN

Basic Patterns for Mobile Navigation

這篇文章作者分析了三種導航模式: hamburger menu, tab bar和gesture-base navigation.

Hamburger Menu

Pros: 導航選項多, 設計幹淨, 給主要内容留出了更多空間.

Cons: 不易被發現.

在iOS實作時, 和iOS的基本導航元素沖突.

hamburger的icon并沒有給出上下文.

需要點兩下才到目标頁面.

Tips: 給選項排列優先級. 如果你的高優先級選項不多, 可以考慮用tabs或者tab bar. 重審你的資訊結構, 有沒有必要劃分成多個簡單的app.

Tab Bar

Pros: Tab bar可以反映出目前在哪. 它們是永久存在的, 使用者可以單擊通路.

Cons: 有限的選項數. iOS和Android可能會有不同的設計規範.

Tips: 讓點選區域足夠大. icon要經過可用性測試. icon和label一起用.

Gesture-Based Navigation

Pros: 移除了UI雜項, 節約了螢幕空間. 自然的人機互動接口.

Cons: 導航不可見. 增加了使用者教育成本.

Tips: 確定不要必須教給使用者一種全新的互動方式, 設計相似的體驗. 使用過程動畫的形式教使用者如何使用.

Design Reviews: Going beyond the surface

關于設計的review.

LIBRARIES & CODE

green-coffee

一個Android庫, 讓你可以在instrumentation測試中跑Cucumber.

ThirtyInch

一個Android MVP庫.

Tools

Exynap

一個Android Studio插件, 可以生成實作代碼.

作者: 聖騎士Wind

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

Github: https://github.com/mengdd

微信公衆号: 聖騎士Wind

Android Weekly Notes Issue #222