Android Weekly Issue #223 中文筆記, 本期內容包括: Offline時間戳處理; Accessibility的安全問題可能並不是個問題; 如何在單元測試和UI測試之間共用代碼; Android中的指紋認證; 編譯時間Kotlin vs Java; MVP結合RxJava,... ...
Android Weekly Issue #223
September 18th, 2016
Android Weekly Issue #223
本期內容包括:
Offline時間戳處理; Accessibility的安全問題可能並不是個問題; 如何在單元測試和UI測試之間共用代碼; Android中的指紋認證; 編譯時間Kotlin vs Java; MVP結合RxJava, 讓View來處理生命周期; RxJava2預覽; 記憶體泄露處理; Gradle相關等等.
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.
解決方法:
- 使用WeakReference;
- 在onDestroy()裡面把引用置為null.
Inner Classes
內部類分兩種, 靜態內部類和非靜態內部類: Nested Class
錯誤原因: 在Activity里有一個內部類(非靜態), 創建內部類的對象, 然後靜態引用之. 因為內部類持有外部類的應用, 所以會造成記憶體泄露.
解決方法:
儘量不要存static引用.
匿名內部類 AsyncTask, Handler, Thread, TimerTask
錯誤原因:
如果你不在超出生命周期的地方引用它, 匿名內部類的對象是無害的.
但是上面的這些內部類對象全都是用來產生一些線程的, 這些線程是app全局的, 而且會引用創建它們的對象.
解決方法:
- 把上面的這些類改成靜態內部類, 靜態的內部類對象不會引用外部類的對象.
- 如果你堅持使用匿名內部類, 可以在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
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
一個統一的錯誤處理器. 為每一種錯誤建立全局預設的處理方式.