Android Weekly Issue #223
September 18th, 2016
Android Weekly Issue #223
本期內(nèi)容包括:
Offline時(shí)間戳處理; Accessibility的安全問(wèn)題可能并不是個(gè)問(wèn)題; 如何在單元測(cè)試和UI測(cè)試之間共享代碼; Android中的指紋認(rèn)證; 編譯時(shí)間Kotlin vs Java; MVP結(jié)合RxJava, 讓View來(lái)處理生命周期; RxJava2預(yù)覽; 內(nèi)存泄露處理; Gradle相關(guān)等等.
ARTICLES & TUTORIALS
Offline First: Introducing TrueTime for Android
TrueTime是一個(gè)NTP library for Swift and Android.
其中NTP是Network Time Protocol.
作者他們有一個(gè)購(gòu)物app, 但是時(shí)斷時(shí)續(xù)的網(wǎng)絡(luò)降低了用戶(hù)體驗(yàn), 所以他們進(jìn)行了離線(xiàn)遷移, 準(zhǔn)備出一系列文章分享相關(guān)的想法和在此過(guò)程中學(xué)到的東西.
本文是第一篇, 關(guān)于時(shí)間.
由于在設(shè)置里可以設(shè)置設(shè)備的日期和時(shí)間, 所以設(shè)備的時(shí)間并不一定是真實(shí)的時(shí)間, 我們?cè)诔绦蚶?code>new Date()得到的其實(shí)是設(shè)備時(shí)間.
關(guān)于真實(shí)時(shí)間的計(jì)算, 他們開(kāi)源了TrueTime庫(kù), Android和iOS都能用.
TrueTime如何計(jì)算真實(shí)時(shí)間的呢? 它其實(shí)是向NTP的server發(fā)了請(qǐng)求, 然后計(jì)算出的.
文中和庫(kù)都說(shuō)明了用法.
Android Security and Accessibility
之前有一個(gè)文章說(shuō)Accessiblity存在安全隱患, 這個(gè)服務(wù)可能可以訪(fǎng)問(wèn)到一些隱私信息, 比如密碼.
但是這篇文章的作者覺(jué)得前一篇文章作者的解決方案不是很好.
因?yàn)楫?dāng)用戶(hù)開(kāi)啟Accessibility權(quán)限的時(shí)候, Android就已經(jīng)給出了警告, 說(shuō)明敏感信息可能會(huì)被觀(guān)察到. 第三方的keyboard也可以訪(fǎng)問(wèn)這些信息, Android也是在開(kāi)啟的時(shí)候給出了警告.
另外對(duì)于前一篇文章作者提出的解決方案: View.IMPORTANT_FOR_ACCESSIBILITY_NO
這樣真正有視覺(jué)障礙的那部分用戶(hù)也無(wú)法看到密碼, 可能就無(wú)法登陸了.
所以本文作者建議的解決方案是, 可以彈一個(gè)對(duì)話(huà)框來(lái)提醒用戶(hù), 如果用戶(hù)允許了, 再繼續(xù)輸入.
Sharing code between UI & unit tests
Android的測(cè)試分兩種:
一種是Unit tests. 單元測(cè)試, 在JVM上跑.
另一種是UI測(cè)試, 需要Android設(shè)備.
在Android Studio中對(duì)應(yīng)test
和androidTest
文件夾.
這兩個(gè)測(cè)試文件夾之間是不共享代碼的, 即一個(gè)文件夾里不能訪(fǎng)問(wèn)另一個(gè)里面的代碼.
但是如果我們想要共用一些代碼, 是有辦法解決的.
首先在app/src下新建一個(gè)文件夾, 比如叫testShared
. 里面添加要共享的代碼.
然后在app/build.gradle
里面添加這個(gè):
android.sourceSets {
test {
java.srcDirs += "$projectDir/src/testShared"
}
androidTest {
java.srcDirs += "$projectDir/src/testShared"
}
}
就可以在UI測(cè)試和單元測(cè)試中共享同一份代碼了.
Synchronously Animating Colors on Android
作者想做的一個(gè)效果是, 在切換tab的時(shí)候, 把Toolbar
, TabLayout
, FloatingActionButton
還有StatusBar
的顏色都動(dòng)畫(huà)地改變到另一個(gè)顏色.
實(shí)現(xiàn)很簡(jiǎn)單, 首先用當(dāng)前顏色和目標(biāo)顏色建立一個(gè)ValueAnimator
, 然后addUpdateListener()
在更新的過(guò)程中把值set給相應(yīng)的控件:
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
其實(shí)用戶(hù)都不喜歡驗(yàn)證, 因?yàn)橛脩?hù)都比較懶, 不喜歡一次又一次地輸入密碼或者手勢(shì)pattern, 但是不鎖屏又不安全.
指紋驗(yàn)證Fingerprint Authentication是Android M (Android 6.0, API 23)引入的. 它就是為了解決這個(gè)問(wèn)題, 提升用戶(hù)體驗(yàn). 這種non-disturbing和easy的方式, 讓我們不用在安全和用戶(hù)體驗(yàn)之間做出妥協(xié).
如果你的應(yīng)用需要做一些關(guān)鍵操作, 比如支付, 你需要用戶(hù)在操作前授權(quán), 那么指紋驗(yàn)證會(huì)很有幫助.
然后作者介紹了實(shí)現(xiàn)的細(xì)節(jié).
最后作者附上了自己的相關(guān)庫(kù): fingerlock.
Kotlin vs Java: Compilation Speed
這是作者關(guān)于Kotlin的第三篇文章, 作者在這篇文章里測(cè)試了Kotlin和Java的編譯時(shí)間.
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的時(shí)候, Java可能快10-15%, 但是在增量build + gradle daemon時(shí), kotlin和Java一樣快, 甚至可能比Java更快一些.
Let the view handle the lifecycle in MVP by using RxJava
問(wèn)題:
作者舉了一個(gè)例子, 在Fragment作為View的MVP中, 如果P從service取一些數(shù)據(jù), 然后調(diào)用View的顯示方法, 則還需要知道onViewCreated()
是不是已經(jīng)調(diào)用過(guò)了.
解決方案:
首先創(chuàng)建一個(gè)Lifecycle的BehaviorSubject, 在onViewCreated()
的時(shí)候調(diào)用onNext(null)
.
把View的方法改成返回一個(gè)Observable, presenter的方法調(diào)用View的方法時(shí)實(shí)際上是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();
}
}
當(dāng)然這并不是一個(gè)完整的例子, 完整的例子還需要考慮onDestroyView()
還有注銷(xiāo)等情況的處理.
Nougat - GCM Network Manager
作者搞了一個(gè)message app來(lái)研究Android 7的新特性.
他用到了AutoValue.
關(guān)于Android 7的另一篇文章: Random Musings on the N Developer Preview
他們的應(yīng)用首先需要周期性地生產(chǎn)一些消息, 關(guān)于生產(chǎn)消息的實(shí)現(xiàn), 作者沒(méi)有用AlarmManager
, 也沒(méi)有用JobScheduler
(因?yàn)橹恢С諥PI 21及以上), 而是選用了GCMNetworkManager
.
具體實(shí)現(xiàn)見(jiàn)原文, 有詳細(xì)說(shuō)明.
另: 代碼
這只是系列文章的第一篇, 后續(xù)應(yīng)該會(huì)寫(xiě)更多.
TransactionTooLargeException crashes on Nougat
作者自己的應(yīng)用在Activity轉(zhuǎn)換的時(shí)候遇到了一個(gè)crash: java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 700848 bytes.
之前應(yīng)用里有相關(guān)的Warning log, 但是
Android 7 Nougat (API 24)把它作為異常拋出來(lái)了.
產(chǎn)生這個(gè)問(wèn)題的原因是在onSaveInstanceState()
里面存了太多數(shù)據(jù). 作者做了一個(gè)測(cè)試, 想看看這個(gè)限制大概是多少, 大概是500K左右.
所以這里是不應(yīng)該用來(lái)存儲(chǔ)太多數(shù)據(jù)的, 應(yīng)該只存狀態(tài).
底下回復(fù)說(shuō)每個(gè)進(jìn)程都有1M的buffer來(lái)接收transactions, 但是是在沒(méi)有任何其他IPC的情況下. 所以建議存儲(chǔ)的狀態(tài)數(shù)據(jù)少于100K或者50K, 當(dāng)然越少越好.
Building a blazing fast ETC2 compressor
作者是Google的, 以前做游戲的, 所以致力于Performances, GPU, 數(shù)據(jù)壓縮等內(nèi)容.
作者關(guān)注VR, 但是VR中要提升體驗(yàn), 必定會(huì)增加圖像的大小和質(zhì)量.
ETC textures 是OpenGLES 3.0的一種標(biāo)準(zhǔn)格式.
編碼一個(gè)高質(zhì)量的ETC2 texture會(huì)花費(fèi)很多時(shí)間.
以在游戲界最流行的壓縮工具Mali GPU Texture Compression tool為例, 作者做了實(shí)驗(yàn), 證明確實(shí)要花費(fèi)很多時(shí)間(平均10分鐘)來(lái)encode一個(gè)圖.
所以作者他們開(kāi)發(fā)了一個(gè)新的庫(kù): etc2comp, 一個(gè)很快的texture encoder.
然后和之前的工具做了比較, 平均時(shí)間提高到了10秒.
后來(lái)他說(shuō)的技術(shù)細(xì)節(jié)我就看不懂了. 文后還有其他圖像格式(JPG, PNG, WebP)相關(guān)的文章鏈接.
Low Coupling With Rx and Dagger2 in Android
作者舉例展示Android程序的解耦.
首先, 他展示一個(gè)高度耦合的Android程序, 然后加入Rx, 最后加入Dagger2, 從而一步一步地解耦這個(gè)項(xiàng)目.
項(xiàng)目的內(nèi)容是發(fā)現(xiàn)Network中的Services. 這里有官方的Training: Network Service Discovery.
RxJava2: An Early Preview
最近RxJava2有了第一個(gè)Release Candidate. 所以作者在這里先預(yù)覽一下有哪些有趣的更新和新加的功能:
New Dependency:
添加了依賴(lài): ReactiveStreams.
Imports:
RxJava2放在了一個(gè)不同的package下:
RxJava:
compile ‘io.reactivex:rxjava:1.0.y-SNAPSHOT’
RxJava2:
compile ‘io.reactivex.rxjava2:rxjava:x.y.z’
這意味著, 你可以同時(shí)用兩個(gè)版本的庫(kù). 如果你要完全遷移的話(huà), 你需要把所有的import都改到新包.
Null Emissions No Longer Permitted:
不允許再發(fā)送null值了, 會(huì)直接拋出空指針異常.
Observable.just(null); //don’t do this
subject.onNext(null); //don’t do this either
Under(Back)Pressure:
Backpressure
是當(dāng)Observable
發(fā)射值的速度比Observer
能處理的速度快時(shí)發(fā)生的.
RxJava2引入了一個(gè)新的Observable類(lèi)Flowable
, with backpressure support.
Single Old and New:
訂閱一個(gè)Single現(xiàn)在可以用這個(gè):
SingleObserver<T>
.
Hit Me Maybe One More Type:
一個(gè)新的類(lèi)型叫Maybe
, 它是Single
和Completable
的混合體. 用來(lái)發(fā)射0或1個(gè)值.
New BackPressured Subject: Processor:
引入了一個(gè)新類(lèi)型, Processor
, 它是一個(gè)有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:
因?yàn)楹蚏eactive-Streams的命名沖突, 所以Subscriber
改名為Disposable
. 它有一個(gè).dispose()
方法, 類(lèi)似于Subscription
的.unsubscribe()
方法.
onCompleted()
也將變?yōu)?code>onComplete().
Composite Subscriptions Changes:
CompositeSubscription
+ subscribe()
-> CompositeDisposable
+ subscribeWith()
Blocking Calls:
RxJava2加了一些新的操作符來(lái)變異步為同步.
.toBlocking.first()
-> .blockingFirst()
Better Hooks for Plugins:
plugin系統(tǒng)被重寫(xiě)了. 現(xiàn)在你可以覆寫(xiě)內(nèi)置schedulers返回的值了. 這樣你就可以在做單元測(cè)試的時(shí)候覆寫(xiě)Schedulers.io()
來(lái)返回同步的值, 甚至debug Schedulers.
Summary
目標(biāo)Release日期: October 29.
Retrofit已經(jīng)支持RxJava2了:
retrofit-rxjava2-adapter
這里還有一個(gè)Library用來(lái)把RxJava1轉(zhuǎn)換到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
之前作者有個(gè)文章叫Eight Ways Your Android App Can Leak Memory, 講的是Android應(yīng)用中8種內(nèi)存泄露的原因, 主要是泄露了Activity.
這篇文章主要講解決方法:
Static Activities
錯(cuò)誤原因: 把Activity存在一個(gè)靜態(tài)引用里, Activity生命周期結(jié)束后仍然持有.
解決方法:
使用WeakReference.
Static Views
錯(cuò)誤原因: 靜態(tài)引用了View, 因?yàn)閍ttached View引用了Activity, 所以等于間接引用了Activity.
解決方法:
- 使用WeakReference;
- 在onDestroy()里面把引用置為null.
Inner Classes
內(nèi)部類(lèi)分兩種, 靜態(tài)內(nèi)部類(lèi)和非靜態(tài)內(nèi)部類(lèi): Nested Class
錯(cuò)誤原因: 在Activity里有一個(gè)內(nèi)部類(lèi)(非靜態(tài)), 創(chuàng)建內(nèi)部類(lèi)的對(duì)象, 然后靜態(tài)引用之. 因?yàn)閮?nèi)部類(lèi)持有外部類(lèi)的應(yīng)用, 所以會(huì)造成內(nèi)存泄露.
解決方法:
盡量不要存static引用.
匿名內(nèi)部類(lèi) AsyncTask, Handler, Thread, TimerTask
錯(cuò)誤原因:
如果你不在超出生命周期的地方引用它, 匿名內(nèi)部類(lèi)的對(duì)象是無(wú)害的.
但是上面的這些內(nèi)部類(lèi)對(duì)象全都是用來(lái)產(chǎn)生一些線(xiàn)程的, 這些線(xiàn)程是app全局的, 而且會(huì)引用創(chuàng)建它們的對(duì)象.
解決方法:
- 把上面的這些類(lèi)改成靜態(tài)內(nèi)部類(lèi), 靜態(tài)的內(nèi)部類(lèi)對(duì)象不會(huì)引用外部類(lèi)的對(duì)象.
- 如果你堅(jiān)持使用匿名內(nèi)部類(lèi), 可以在Activity的onDestroy()里面終止線(xiàn)程.
Sensor Manager
錯(cuò)誤原因:
把Activity作為listener注冊(cè)給了系統(tǒng)服務(wù), 但是在Activity生命周期結(jié)束之前沒(méi)有注銷(xiāo)listener.
解決方法: 在生命周期結(jié)束前注銷(xiāo)listener.
Auto rename Android versionName in Gradle
在應(yīng)用release的時(shí)候, 版本號(hào)是確定的, 這沒(méi)問(wèn)題. 在應(yīng)用開(kāi)發(fā)的時(shí)候, 如果每一個(gè)apk也有一個(gè)特定的版本號(hào), 將會(huì)非常有幫助.
自定義Gradle Plugin:
com.android.application
就是一個(gè)gradle plugin.
有三種方式可以創(chuàng)建gradle plugin: doc.
本文作者選擇了buildSrc
的方式, 因?yàn)檫@很容易, 而且可以被加到repo里, 但是這樣將依附于你的project, 不能復(fù)用.
具體代碼見(jiàn)原文.
這么做了之后, 每一次build的apk都自帶了分支信息, Jira卡號(hào), 或者任何你想帶的信息.
Is your custom view interactive aware?
什么是Interactive View?
當(dāng)View是可見(jiàn)的, 即可以和用戶(hù)交互, 即為interactive.
當(dāng)你的自定義View做一些很重的工作, 比如循環(huán)的動(dòng)畫(huà)或者loading, 或者依賴(lài)于傳感器, 當(dāng)這種View變?yōu)椴豢梢?jiàn)時(shí),你需要做一些工作來(lái)節(jié)約電量.
作者寫(xiě)了一個(gè)輔助類(lèi): InteractiveViewHelper 來(lái)做這個(gè).
具體利用了View的這幾個(gè)回調(diào):
void View::onVisibilityChanged(View, int)
void View::onWindowVisibilityChanged()
void View::onAttachedToWindow()
void View::onDetachedFromWindow()
還有兩個(gè)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
應(yīng)用.
2. 在build file里指明application id.
applicationId是apk最終會(huì)用的包名.
packageName是用來(lái)找代碼中的R, 和activity/service組件的相對(duì)路徑.
如果不在build文件里指明applicationId可能會(huì)有一些問(wèn)題.
3. 給debug版使用一個(gè)不同的applicationId.
buildTypes {
debug {
applicationIdSuffix ".debug"
}
// ...
}
好處是同一個(gè)機(jī)器上可以同時(shí)安裝debug和release版.
4. 統(tǒng)計(jì)build時(shí)間.
用--profile命令. 或Build Scans
5. 配置release.
Proguard在Java層面工作, 對(duì)于資源是不管的, 只把R中的id刪了.
如果想進(jìn)一步處理不用的資源, 需要加:
shrinkResources true
.
更深一步的居然還可以拆分apk: config-apk-splits
6. 發(fā)現(xiàn)一些有用的tasks, 或者自己開(kāi)發(fā). Reddit page.
7. 把依賴(lài)的版本號(hào)抽出來(lái).
8. 使用jcenter, 響應(yīng)更快.
9. 在開(kāi)發(fā)時(shí)把最小sdk設(shè)為21或以上, 會(huì)build得更快.
LIBRARIES & CODE
Android Amazing Open Source Apps
這篇文章列舉了一些好的開(kāi)源app.
包括google/iosched, android-architecture, Telegram, Plaid, wire-android, ribot/ribot-app-android, PocketHub.
DoorSignView
一個(gè)自定義View, 顯示門(mén)牌. AnimatedDoorSignView可以根據(jù)傳感器進(jìn)行動(dòng)畫(huà).
Java Error Handler
一個(gè)統(tǒng)一的錯(cuò)誤處理器. 為每一種錯(cuò)誤建立全局默認(rèn)的處理方式.