天天看點

Android Weekly Notes Issue #223

Android Weekly Issue #223 中文筆記, 本期内容包括:

Offline時間戳處理; Accessibility的安全問題可能并不是個問題; 如何在單元測試和UI測試之間共享代碼; Android中的指紋認證; 編譯時間Kotlin vs Java; MVP結合RxJava, 讓View來處理生命周期; RxJava2預覽; 記憶體洩露處理; Gradle相關等等.

Android Weekly Issue #223

September 18th, 2016

本期内容包括:

ARTICLES & TUTORIALS

Offline First: Introducing TrueTime for Android

TrueTime是一個NTP library for Swift and Android.

其中NTP是Network Time Protocol.

作者他們有一個購物app, 但是時斷時續的網絡降低了使用者體驗, 是以他們進行了離線遷移, 準備出一系列文章分享相關的想法和在此過程中學到的東西.

本文是第一篇, 關于時間.

由于在設定裡可以設定裝置的日期和時間, 是以裝置的時間并不一定是真實的時間, 我們在程式裡

new Date()

得到的其實是裝置時間.

關于真實時間的計算, 他們開源了TrueTime庫, Android和iOS都能用.

TrueTime如何計算真實時間的呢? 它其實是向NTP的server發了請求, 然後計算出的.

文中和庫都說明了用法.

Android Security and Accessibility

之前有一個文章說Accessiblity存在安全隐患, 這個服務可能可以通路到一些隐私資訊, 比如密碼.

但是這篇文章的作者覺得前一篇文章作者的解決方案不是很好.

因為當使用者開啟Accessibility權限的時候, Android就已經給出了警告, 說明敏感資訊可能會被觀察到. 第三方的keyboard也可以通路這些資訊, Android也是在開啟的時候給出了警告.

另外對于前一篇文章作者提出的解決方案:

View.IMPORTANT_FOR_ACCESSIBILITY_NO

這樣真正有視覺障礙的那部分使用者也無法看到密碼, 可能就無法登陸了.

是以本文作者建議的解決方案是, 可以彈一個對話框來提醒使用者, 如果使用者允許了, 再繼續輸入.

Sharing code between UI & unit tests

Android的測試分兩種:

一種是Unit tests. 單元測試, 在JVM上跑.

另一種是UI測試, 需要Android裝置.

在Android Studio中對應

test

androidTest

檔案夾.

這兩個測試檔案夾之間是不共享代碼的, 即一個檔案夾裡不能通路另一個裡面的代碼.

但是如果我們想要共用一些代碼, 是有辦法解決的.

首先在app/src下建立一個檔案夾, 比如叫

testShared

. 裡面添加要共享的代碼.

然後在

app/build.gradle

裡面添加這個:

android.sourceSets {
    test {
        java.srcDirs += "$projectDir/src/testShared"
    }

    androidTest {
        java.srcDirs += "$projectDir/src/testShared"
    }
}
           

就可以在UI測試和單元測試中共享同一份代碼了.

Synchronously Animating Colors on Android

作者想做的一個效果是, 在切換tab的時候, 把

Toolbar

,

TabLayout

FloatingActionButton

還有

StatusBar

的顔色都動畫地改變到另一個顔色.

實作很簡單, 首先用目前顔色和目标顔色建立一個

ValueAnimator

, 然後

addUpdateListener()

在更新的過程中把值set給相應的控件:

colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animator) {
        int color = (int) animator.getAnimatedValue();

        toolbar.setBackgroundColor(color);
        tabLayout.setBackgroundColor(color);
        floatingActionButton.setBackgroundTintList(ColorStateList.valueOf(color));

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            getWindow().setStatusBarColor(color);
        }
    }

});
colorAnimation.start();

           

其中FloatingActionButton要用

setBackgroundTintList()

.

StatusBar在21及以上才支援

getWindow().setStatusBarColor(color);

Android Fingerprint Authentication

其實使用者都不喜歡驗證, 因為使用者都比較懶, 不喜歡一次又一次地輸入密碼或者手勢pattern, 但是不鎖屏又不安全.

指紋驗證Fingerprint Authentication是Android M (Android 6.0, API 23)引入的. 它就是為了解決這個問題, 提升使用者體驗. 這種non-disturbing和easy的方式, 讓我們不用在安全和使用者體驗之間做出妥協.

如果你的應用需要做一些關鍵操作, 比如支付, 你需要使用者在操作前授權, 那麼指紋驗證會很有幫助.

然後作者介紹了實作的細節.

最後作者附上了自己的相關庫: fingerlock.

Kotlin vs Java: Compilation Speed

這是作者關于Kotlin的第三篇文章, 作者在這篇文章裡測試了Kotlin和Java的編譯時間.

Clean build with No Gradle daemon

Java編譯比Kotlin快17%.

Clean build + Gradle daemon

org.gradle.daemon=true

Java編譯比Kotlin快13%.

Incremental builds

kotlin.incremental=true

在clean build的時候, Java可能快10-15%, 但是在增量build + gradle daemon時, kotlin和Java一樣快, 甚至可能比Java更快一些.

Let the view handle the lifecycle in MVP by using RxJava

問題:

作者舉了一個例子, 在Fragment作為View的MVP中, 如果P從service取一些資料, 然後調用View的顯示方法, 則還需要知道

onViewCreated()

是不是已經調用過了.

解決方案:

首先建立一個Lifecycle的BehaviorSubject, 在

onViewCreated()

的時候調用

onNext(null)

把View的方法改成傳回一個Observable, presenter的方法調用View的方法時實際上是subscribe了一下:

class ProductsFragment implements ProductsView {
  private ProductsPresenter presenter;
  //Lifecycle subject. It is BehaviourSubject because it can be subscribed after onViewCreated call.
  private final BehaviorSubject<Void> onViewCreatedSubject = BehaviorSubject.create();

  @Override
  public Observable<Void> showProducts(List<Product> productList) {
    return onViewCreatedSubject. // Wait for onViewCreated
        doOnNext(new Action1<Object>() {
          @Override
          public void call(Object o) {
            //Updates recyclerview adapter items
          }
        });
  }

  @Override
  public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    onViewCreatedSubject.onNext(null);
  }
}
           

Presenter:

class ProductsFragmentPresenter implements ProductsPresenter {
  private ProductsView view;

  public void loadProducts(){
    productsService.getProducts()
      .flatMap(new Func1<Object, Observable<Void>>() {
          @Override
          public Observable<Void> call(List<Product> productList) {
            //Return the view's observable to show products.
            //No need to check if the view is created!
            return view.showProducts(productList);
          }
        })
      .subscribe();
  }
}
           

當然這并不是一個完整的例子, 完整的例子還需要考慮

onDestroyView()

還有登出等情況的處理.

Nougat - GCM Network Manager

作者搞了一個message app來研究Android 7的新特性.

他用到了AutoValue.

關于Android 7的另一篇文章: Random Musings on the N Developer Preview

他們的應用首先需要周期性地生産一些消息, 關于生産消息的實作, 作者沒有用

AlarmManager

, 也沒有用

JobScheduler

(因為隻支援API 21及以上), 而是選用了

GCMNetworkManager

具體實作見原文, 有詳細說明.

另: 代碼

這隻是系列文章的第一篇, 後續應該會寫更多.

TransactionTooLargeException crashes on Nougat

作者自己的應用在Activity轉換的時候遇到了一個crash:

java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 700848 bytes.

之前應用裡有相關的Warning log, 但是

Android 7 Nougat (API 24)把它作為異常抛出來了.

産生這個問題的原因是在

onSaveInstanceState()

裡面存了太多資料. 作者做了一個測試, 想看看這個限制大概是多少, 大概是500K左右.

是以這裡是不應該用來存儲太多資料的, 應該隻存狀态.

底下回複說每個程序都有1M的buffer來接收transactions, 但是是在沒有任何其他IPC的情況下. 是以建議存儲的狀态資料少于100K或者50K, 當然越少越好.

Building a blazing fast ETC2 compressor

作者是Google的, 以前做遊戲的, 是以緻力于Performances, GPU, 資料壓縮等内容.

作者關注VR, 但是VR中要提升體驗, 必定會增加圖像的大小和品質.

ETC textures 是OpenGLES 3.0的一種标準格式.

編碼一個高品質的ETC2 texture會花費很多時間.

以在遊戲界最流行的壓縮工具Mali GPU Texture Compression tool為例, 作者做了實驗, 證明确實要花費很多時間(平均10分鐘)來encode一個圖.

是以作者他們開發了一個新的庫: etc2comp, 一個很快的texture encoder.

然後和之前的工具做了比較, 平均時間提高到了10秒.

後來他說的技術細節我就看不懂了. 文後還有其他圖像格式(JPG, PNG, WebP)相關的文章連結.

Low Coupling With Rx and Dagger2 in Android

作者舉例展示Android程式的解耦.

首先, 他展示一個高度耦合的Android程式, 然後加入Rx, 最後加入Dagger2, 進而一步一步地解耦這個項目.

項目的内容是發現Network中的Services. 這裡有官方的Training: Network Service Discovery.

RxJava2: An Early Preview

最近RxJava2有了第一個Release Candidate. 是以作者在這裡先預覽一下有哪些有趣的更新和新加的功能:

New Dependency:

添加了依賴: ReactiveStreams.

Imports:

RxJava2放在了一個不同的package下:

RxJava:

compile ‘io.reactivex:rxjava:1.0.y-SNAPSHOT’

RxJava2:

compile ‘io.reactivex.rxjava2:rxjava:x.y.z’

這意味着, 你可以同時用兩個版本的庫. 如果你要完全遷移的話, 你需要把所有的import都改到新包.

Null Emissions No Longer Permitted:

不允許再發送null值了, 會直接抛出空指針異常.

Observable.just(null); //don’t do this
subject.onNext(null); //don’t do this either
           

Under(Back)Pressure:

Backpressure

是當

Observable

發射值的速度比

Observer

能處理的速度快時發生的.

RxJava2引入了一個新的Observable類

Flowable

, with backpressure support.

Single Old and New:

訂閱一個Single現在可以用這個:

SingleObserver<T>

Hit Me Maybe One More Type:

一個新的類型叫

Maybe

, 它是

Single

Completable

的混合體. 用來發射0或1個值.

New BackPressured Subject: Processor:

引入了一個新類型,

Processor

, 它是一個有backpressure support的

Subject

New Names for Function and Action:

  • Func1

    ->

    Function

  • Func2

    BiFunction

  • FuncN

    Function<Object[], R>

  • Func1<T, Boolean>

    Predicate<T>

  • Action0

    Consumer

  • Action1

    BiConsumer

  • ActionN

    Consumer<Object[]>

Subscriber is Now Disposable:

因為和Reactive-Streams的命名沖突, 是以

Subscriber

改名為

Disposable

. 它有一個

.dispose()

方法, 類似于

Subscription

.unsubscribe()

方法.

onCompleted()

也将變為

onComplete()

Composite Subscriptions Changes:

CompositeSubscription

+

subscribe()

CompositeDisposable

subscribeWith()

Blocking Calls:

RxJava2加了一些新的操作符來變異步為同步.

.toBlocking.first()

.blockingFirst()

Better Hooks for Plugins:

plugin系統被重寫了. 現在你可以覆寫内置schedulers傳回的值了. 這樣你就可以在做單元測試的時候覆寫

Schedulers.io()

來傳回同步的值, 甚至debug Schedulers.

Summary

目标Release日期: October 29.

Retrofit已經支援RxJava2了:

retrofit-rxjava2-adapter

這裡還有一個Library用來把RxJava1轉換到RxJava2: RxJava2Interop

Sources:

RxJava 2.x javadoc,

Github Wiki: What's different in 2.0,

Stackoverflow

Eight Ways Your Android App Can STOP Leaking Memory

之前作者有個文章叫Eight Ways Your Android App Can Leak Memory, 講的是Android應用中8種記憶體洩露的原因, 主要是洩露了Activity.

這篇文章主要講解決方法:

Static Activities

錯誤原因: 把Activity存在一個靜态引用裡, Activity生命周期結束後仍然持有.

解決方法:

使用WeakReference.

Static Views

錯誤原因: 靜态引用了View, 因為attached View引用了Activity, 是以等于間接引用了Activity.

  1. 使用WeakReference;
  2. 在onDestroy()裡面把引用置為null.

Inner Classes

内部類分兩種, 靜态内部類和非靜态内部類: Nested Class

錯誤原因: 在Activity裡有一個内部類(非靜态), 建立内部類的對象, 然後靜态引用之. 因為内部類持有外部類的應用, 是以會造成記憶體洩露.

盡量不要存static引用.

匿名内部類 AsyncTask, Handler, Thread, TimerTask

錯誤原因:

如果你不在超出生命周期的地方引用它, 匿名内部類的對象是無害的.

但是上面的這些内部類對象全都是用來産生一些線程的, 這些線程是app全局的, 而且會引用建立它們的對象.

  1. 把上面的這些類改成靜态内部類, 靜态的内部類對象不會引用外部類的對象.
  2. 如果你堅持使用匿名内部類, 可以在Activity的onDestroy()裡面終止線程.

Sensor Manager

把Activity作為listener注冊給了系統服務, 但是在Activity生命周期結束之前沒有登出listener.

解決方法: 在生命周期結束前登出listener.

Auto rename Android versionName in Gradle

在應用release的時候, 版本号是确定的, 這沒問題. 在應用開發的時候, 如果每一個apk也有一個特定的版本号, 将會非常有幫助.

自定義Gradle Plugin:

com.android.application

就是一個gradle plugin.

有三種方式可以建立gradle plugin: doc.

本文作者選擇了

buildSrc

的方式, 因為這很容易, 而且可以被加到repo裡, 但是這樣将依附于你的project, 不能複用.

具體代碼見原文.

這麼做了之後, 每一次build的apk都自帶了分支資訊, Jira卡号, 或者任何你想帶的資訊.

Is your custom view interactive aware?

什麼是Interactive View?

當View是可見的, 即可以和使用者互動, 即為interactive.

當你的自定義View做一些很重的工作, 比如循環的動畫或者loading, 或者依賴于傳感器, 當這種View變為不可見時,你需要做一些工作來節約電量.

作者寫了一個輔助類: InteractiveViewHelper 來做這個.

具體利用了View的這幾個回調:

void View::onVisibilityChanged(View, int)
void View::onWindowVisibilityChanged()
void View::onAttachedToWindow()
void View::onDetachedFromWindow()
           

還有兩個ACTION:

Intent.ACTION_SCREEN_ON
Intent.ACTION_SCREEN_OFF
           

Beta Testing Your Android App With Build Variants

講了如何用Build Variants, 添加不同的Flavors.

Make your build.gradle great again

1. 把你的build.gradle分成小份, 更加子產品化, 用

apply

應用.

2. 在build file裡指明application id.

applicationId是apk最終會用的包名.

packageName是用來找代碼中的R, 和activity/service元件的相對路徑.

如果不在build檔案裡指明applicationId可能會有一些問題.

3. 給debug版使用一個不同的applicationId.

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
    // ...
}
           

好處是同一個機器上可以同時安裝debug和release版.

4. 統計build時間.

用--profile指令. 或Build Scans

還可以用build-time-tracker-plugin

5. 配置release.

Proguard在Java層面工作, 對于資源是不管的, 隻把R中的id删了.

如果想進一步處理不用的資源, 需要加:

shrinkResources true

更深一步的居然還可以拆分apk: config-apk-splits

6. 發現一些有用的tasks, 或者自己開發. Reddit page.

7. 把依賴的版本号抽出來.

8. 使用jcenter, 響應更快.

9. 在開發時把最小sdk設為21或以上, 會build得更快.

LIBRARIES & CODE

Android Amazing Open Source Apps

這篇文章列舉了一些好的開源app.

包括google/iosched, android-architecture, Telegram, Plaid, wire-android, ribot/ribot-app-android, PocketHub.

DoorSignView

一個自定義View, 顯示門牌. AnimatedDoorSignView可以根據傳感器進行動畫.

Java Error Handler

一個統一的錯誤處理器. 為每一種錯誤建立全局預設的處理方式.

作者: 聖騎士Wind

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

Github: https://github.com/mengdd

微信公衆号: 聖騎士Wind

Android Weekly Notes Issue #223