Android Weekly Issue #249
March 19th, 2017
Android Weekly Issue #249
本期內(nèi)容包括: 一個(gè)設(shè)計(jì)的實(shí)現(xiàn)Demo討論; Kotlin的Coroutines可能還是沒有RxJava好用; 在構(gòu)建SDK/Libraries時(shí)需要注意的事項(xiàng); 如何用OpenGL和JBox2D實(shí)現(xiàn)一個(gè)好看的多氣泡選擇器效果;
網(wǎng)絡(luò)請(qǐng)求中Etag, If-Modified-Since的工作原理和用OkHttp的客戶端實(shí)現(xiàn); 用ClassyShark導(dǎo)出類型分析; 指紋認(rèn)證實(shí)現(xiàn)代碼; MVI模式對(duì)調(diào)試帶來(lái)的好處; 用TensorFlow做一個(gè)圖像識(shí)別處理器.
ARTICLES & TUTORIALS
From design to Android
作者想建立一個(gè)項(xiàng)目, 把從Dribbble和MaterialUp上看到的一些設(shè)計(jì)實(shí)現(xiàn)出來(lái), 再講解一些實(shí)現(xiàn)細(xì)節(jié)和UI/UX的tips等.
本文是此系列文章的第一篇, 選擇的設(shè)計(jì)是preferred-date-and-time, 實(shí)現(xiàn)的demo在這里:from_design_to_android_part1.
實(shí)現(xiàn)中涉及到的點(diǎn): Bottom Sheets; ConstraintLayout
和其中的鏈?zhǔn)郊s束Chains; ViewSwitcher; Databinding; Scene和Transition.
Why Im Skeptical about Kotlin Coroutines
Kotlin為了處理異步發(fā)布了Coroutines特性, 本文作者對(duì)Coroutines持懷疑態(tài)度, 認(rèn)為RxJava的方式更好.
首先, Observables為我們要處理的問(wèn)題建立了很好的模型, 但coroutines并沒有起到這樣的作用. (coroutines只是把異步的操作寫成了看起來(lái)順序的樣子, 同時(shí)不阻塞主線程.)
其次, Observables讓我們?cè)谕胶彤惒綌?shù)據(jù)上都可以進(jìn)行同等的抽象, 而coroutines的同步處理和異步處理明顯不同.
最后, Observables讓我們?cè)诟叩某橄髮又泄ぷ? 比如對(duì)元素進(jìn)行遍歷處理的.map()
.
當(dāng)然, 本文并不是說(shuō)Coroutines一無(wú)是處, 它肯定是有自己的用處的, 但是作者只是持懷疑態(tài)度, 覺得它的地位遠(yuǎn)不及RxJava.
Things I wish I knew when I started building Android SDK/Libraries
當(dāng)你遇到一個(gè)問(wèn)題并且解決了, 有時(shí)候你會(huì)把解決方案作為一個(gè)庫(kù)發(fā)布出去.
作者發(fā)布了一些庫(kù)Android Libraries, 他提出了一些基本的問(wèn)題和幾個(gè)應(yīng)該注意的點(diǎn).
為什么要?jiǎng)?chuàng)建這個(gè)庫(kù)呢?
如果已經(jīng)有現(xiàn)成的解決方案, 嘗試使用已有的方案, 或者給已有的庫(kù)提Pull Request. 如果沒有解決方案, 好吧, 那創(chuàng)建自己的庫(kù)吧.
你的artifacts可選的類型是什么?
- Library Project: 直接項(xiàng)目引用.
- JAR: 包含了Java class文件和metadata.
- AAR: 除了Java class文件, 還包含了Android的資源和manifest.
你的庫(kù)放在哪里?
- 本地.
- 私有的代碼庫(kù).
- 公有的代碼庫(kù): Maven Central, Jcenter or JitPack.
(每一種方式都有教程鏈接).
上面三個(gè)基本問(wèn)題說(shuō)完了, 在建立這個(gè)庫(kù)的時(shí)候還有一些注意事項(xiàng):
避免多個(gè)參數(shù)
參數(shù)最好不要多于三個(gè), 可以用setter或者Builder模式來(lái)解決.
容易使用
- 直觀: 任何發(fā)生在庫(kù)里的行為, 最好有一些反饋, 比如打印出logs或者顯示在UI上.
- 一致性: 遵從semantic versioning.
- 容易使用, 不容易濫用. 最好一眼能看出它的用途. public的方法應(yīng)該有足夠的驗(yàn)證, 確保用戶不會(huì)濫用. 當(dāng)不存在依賴關(guān)系時(shí), 提供默認(rèn)值并處理場(chǎng)景.
最小化權(quán)限
盡量少地要求權(quán)限, 可以發(fā)送Intent讓更專業(yè)的應(yīng)用幫你做一些處理然后返回結(jié)果. 根據(jù)權(quán)限的獲取情況來(lái)使能你的feature, 不要僅因?yàn)闆]有權(quán)限就crash.
你也可以提供一些不需要權(quán)限的fallback的實(shí)現(xiàn), 讓庫(kù)的使用者去獲取權(quán)限.
最小化要求
有時(shí)候我們需要設(shè)備具有某項(xiàng)功能, 比如藍(lán)牙.
這時(shí)候就需要在manifest中寫uses-feature
.
如果我們?cè)趲?kù)中這樣寫, 它會(huì)被merge進(jìn)應(yīng)用的manifest, 在Play Store上, 整個(gè)應(yīng)用對(duì)無(wú)藍(lán)牙的設(shè)備都變?yōu)椴伙@示. 這樣只是引入了一個(gè)庫(kù), 卻失去了一部分用戶, 這肯定不是我們所希望看到的.
解決方案就是: 不要寫在manifest里, 換為在代碼中動(dòng)態(tài)檢查. 對(duì)于不支持的情況, 庫(kù)可以關(guān)掉這個(gè)功能, 提供fallback的實(shí)現(xiàn).
支持不同的版本
如果你有一個(gè)特定版本才支持的功能, 應(yīng)該做版本檢查, 然后對(duì)于更低的版本關(guān)掉它.
Production版本不要打log
不要悄悄crash, 另外fail fast
遇到崩潰時(shí)應(yīng)該總是輸出錯(cuò)誤信息.
如果你不想在production輸出任何log, 你至少應(yīng)該提供flag, 讓初始化的時(shí)候可以使能它.
如果你的庫(kù)遇到異常, 應(yīng)該立即失敗, 想開發(fā)者輸出Exception, 而不是卡在那里. 要避免寫出會(huì)阻塞主線程的代碼.
優(yōu)雅地處理錯(cuò)誤
當(dāng)你的庫(kù)出錯(cuò)的時(shí)候, 盡量做檢查, 使得代碼不會(huì)讓整個(gè)應(yīng)用崩潰, 而是只有你的庫(kù)提供的功能被關(guān)閉了.
捕獲特定的exceptions
處理不良的網(wǎng)絡(luò)連接
如果你的庫(kù)中有網(wǎng)絡(luò)請(qǐng)求, 請(qǐng)?zhí)幚砭W(wǎng)絡(luò)連接不良的情況.
如果有可能, 批處理你的網(wǎng)絡(luò)請(qǐng)求, 這會(huì)節(jié)約很多電量. 看這里.
使用FlatBuffers而不是json或xml, 來(lái)減小網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)量.
更多的網(wǎng)絡(luò)優(yōu)化看這里: Reducing Network Battery Drain.
盡量不要依賴很大的庫(kù)
主要是因?yàn)榉椒〝?shù)的限制.
不要依賴你不需要的庫(kù)
除了不依賴沒有用到的庫(kù), 還可以把添加依賴的選擇權(quán)留給你的用戶.
讓用戶來(lái)選擇性地添加你依賴的庫(kù), 如果他選擇不添加, 你的相關(guān)feature可以關(guān)閉.
可以這樣實(shí)現(xiàn):
private boolean hasOKHttpOnClasspath() {
try {
Class.forName("com.squareup.okhttp3.OkHttpClient");
return true;
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return false;
}
而你添加的時(shí)候可以這樣:
dependencies {
// for gradle version 2.12 and below
provided 'com.squareup.okhttp3:okhttp:3.6.0'
// or for gradle version 2.12+
compileOnly 'com.squareup.okhttp3:okhttp:3.6.0'
}
但是這種只能用于純Java的依賴, 如果是aar就不行.
不要拖慢啟動(dòng)時(shí)間
在應(yīng)用啟動(dòng)初始化你的庫(kù)時(shí), 不要花太多時(shí)間.
兩種解決方案: 一種是新啟一個(gè)線程來(lái)做初始化; 另一種是到使用之前才進(jìn)行初始化.
刪除功能的時(shí)候要優(yōu)雅
升級(jí)版本的時(shí)候, 不要?jiǎng)h除public的方法.
可以把方法標(biāo)記為@Deprecated
, 然后在未來(lái)的版本中慢慢刪除它.
讓你的代碼可測(cè)試
使用mock來(lái)測(cè)試你的代碼, 在代碼中國(guó)避免final的類和static的方法.
寫代碼的時(shí)候public的API用接口, 這樣更容易更換實(shí)現(xiàn), 更好測(cè)試.
文檔記錄所有的事
包括如何使用你的庫(kù), 庫(kù)中每一個(gè)feature都是什么.
- Repo根目錄有一個(gè)Readme.
- 所有的public方法應(yīng)該有javadoc注釋. 說(shuō)明目的, 參數(shù), 返回值.
- 有一個(gè)sample app, 展示如何使用你的庫(kù).
- 在你的release界面, 確保有一個(gè)詳盡的change log.
提供一個(gè)最簡(jiǎn)單的sample
越簡(jiǎn)單越容易讓人明白.
考慮加一個(gè)Licence
收集反饋
How to Create a Bubble Selection Animation on Android
作者他們想要在Android上實(shí)現(xiàn)Apple music中的選擇氣泡效果.
這種動(dòng)畫效果用于讓用戶在一系列的選擇項(xiàng)中做出選擇, 氣泡自由浮動(dòng), 一旦被選中就會(huì)變大一點(diǎn).
作者選擇的是用Kotlin, OpenGL和JBox2D(物理引擎).
詳細(xì)介紹的內(nèi)容包括: 如何用GLSL寫vertex shader和fragment shader; 如何貼圖; 用JBox2D來(lái)實(shí)現(xiàn)氣泡的動(dòng)畫(需要自己實(shí)現(xiàn)重力); 檢測(cè)用戶手勢(shì)移動(dòng)氣泡; 發(fā)現(xiàn)用戶點(diǎn)擊的氣泡.
項(xiàng)目在Github: Bubble-Picker.
Reducing networking footprint with OkHttp, Etags and If-Modified-Since
If-Modified-Since和Last-Modified
Header中使用了If-Modified-Since和Last-Modified, 如果兩次請(qǐng)求之間內(nèi)容并未改變, 第二次, server就會(huì)返回304 NOT MODIFIED
, 并且響應(yīng)不含body.
Etag和If-None-Match
Etag工作的原理類似, 它實(shí)現(xiàn)起來(lái)不容易出錯(cuò), 但是需要server跑一個(gè)完整的查詢, 并且每次都創(chuàng)建一個(gè)hash.
server將會(huì)在返回response之前根據(jù)響應(yīng)內(nèi)容創(chuàng)建一個(gè)hash, 然后把它作為Etag header; 客戶端在做下一次請(qǐng)求時(shí), 把這個(gè)Etag作為If-None-Match header發(fā)給server. 客戶端在準(zhǔn)備下一個(gè)響應(yīng)的時(shí)候, 比較新的hash和請(qǐng)求中發(fā)來(lái)的是否相同, 如果相同, 則返回?zé)o內(nèi)容的304 NOT MODIFIED
.
客戶端實(shí)現(xiàn)
如果你使用了Retrofit2, 或OkHttp3, 在客戶端使能Last-Modified或Etags是很容易的:
private final static int CACHE_SIZE_BYTES = 1024 * 1024 * 2;
...
OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
builder.cache(new Cache(context.getCacheDir(), CACHE_SIZE_BYTES));
...
根據(jù)server的響應(yīng), Last-Modified或Etags將會(huì)自動(dòng)啟用.
如果你還想減少處理的時(shí)間:
減少處理:
在304狀態(tài)下, Retrofit2和OkHttp3將會(huì)假裝這個(gè)響應(yīng)和上一次的相同, 所以被緩存的響應(yīng)會(huì)被返回, 你可以檢測(cè)響應(yīng)返回值, 如果是304就不做處理.
但是有時(shí)候你可能需要每次都重新parse, 這就不用檢查HTTP_NOT_MODIFIED
了, 看你的需要.
注意在Retrofit2中要用raw()中的response來(lái)檢查, 因?yàn)?code>response.networkResponse().code()返回的是被緩存了的狀態(tài)值:
if (response.isSuccessful() &&
response.raw().networkResponse() != null &&
response.raw().networkResponse().code() ==
HttpURLConnection.HTTP_NOT_MODIFIED) {
// not modified, no need to do anything.
return;
}
// parse response here
問(wèn)題解決
如果你的Etag或Last-Modified不工作.
檢查你的Headers.
可以用Stetho或OkHttp logging interceptor來(lái)檢查你的headers.
正確的輸出是這樣:
Cache-Control: private, must-revalidate
所有請(qǐng)求和響應(yīng)的Last-Modified和Etag headers都會(huì)被顯示出來(lái).
同時(shí)使用Etags和Last-Modified.
OkHttp3會(huì)按照嚴(yán)格的順序檢查cache headers:
- 1.如果上一個(gè)響應(yīng)包含Etag, 那么同樣的Etag值將會(huì)被加在下一個(gè)請(qǐng)求的If-Not-Match中.
- 2.如果第一點(diǎn)不滿足, 但上一個(gè)響應(yīng)包含Last-Modified, 那么這個(gè)值將會(huì)被記載下一個(gè)請(qǐng)求的If-Modified-Since中.
所以如果同時(shí)使用了兩種, Etag會(huì)屏蔽Last-Modified.
Exporting types from Android app using ClassyShark
如何導(dǎo)出ClassyShark
的類型分析.
Fingerprint authentication
一個(gè)指紋認(rèn)證的代碼例子.
Reactive Apps with MVI - Part 5
MVI模式系列文章第五篇.
之前在第一篇講過(guò)單向數(shù)據(jù)流的重要性, 應(yīng)用狀態(tài)應(yīng)該由業(yè)務(wù)邏輯驅(qū)動(dòng). 本篇我們將看到這樣做帶來(lái)的好處: 調(diào)試程序變得簡(jiǎn)單了.
我們經(jīng)常會(huì)遇到無(wú)法復(fù)現(xiàn)的bug, 這往往是因?yàn)槟阒恢辣罎? 卻不知道用戶在出現(xiàn)這個(gè)bug之前的實(shí)際狀態(tài).
當(dāng)我們用MVI的時(shí)候, 我們可以把每次用戶激發(fā)的intent和model(也即狀態(tài))都打出log(用Crashlytics或者其他工具).
這樣做以后, 我們從收集到的log中不僅能看到崩潰前最近的狀態(tài), 還能看到用戶的整個(gè)操作歷史.
而且用戶的應(yīng)用狀態(tài)截圖都被當(dāng)做json發(fā)送過(guò)來(lái), 我們可以拿到任何狀態(tài)當(dāng)做我們的初始狀態(tài).
這樣做以后, 不僅復(fù)現(xiàn)崩潰更加容易, 我們還可以利用這些序列化的狀態(tài)來(lái)寫一些回歸測(cè)試.
這樣做也是有缺點(diǎn)的: 狀態(tài)的序列化需要額外花費(fèi)一些毫秒; 崩潰時(shí)傳遞的數(shù)據(jù)量增大了; 對(duì)用戶的敏感信息, 要么忽略, 會(huì)導(dǎo)致信息不完整; 要么加密, 那就會(huì)需要更多的處理時(shí)間.
Add some machine learning to your apps, with TensorFlow
TensorFlow是一個(gè)開源的機(jī)器學(xué)習(xí)的庫(kù), 由Google開發(fā).
一個(gè)簡(jiǎn)單快速的開始方法就是用TensorFlow來(lái)建立一個(gè)圖像分類器. 相對(duì)于使用Google’s Cloud Vision API來(lái)說(shuō), 我們可以做一個(gè)離線和簡(jiǎn)化版本, 在Android設(shè)備上檢測(cè)和識(shí)別圖像中物體.
本篇文章中, 我們會(huì)創(chuàng)建一個(gè)app, 來(lái)識(shí)別游戲中的角色.
官方有一個(gè)Demo, 如果你要跑它, 你需要安裝NDK和Bazel.
作者創(chuàng)建了這個(gè)Repo, 可以直接clone下來(lái)build, 更快.
不管你選擇哪種方式, 能夠運(yùn)行之后, 這個(gè)sample使用了Inception, 一個(gè)提前訓(xùn)練好的可以檢測(cè)1000個(gè)物體的model.
之后作者重新下載了一些圖片, 對(duì)Inception進(jìn)行了重新訓(xùn)練, 優(yōu)化, 最后導(dǎo)入新的model并運(yùn)行, 文中詳細(xì)記錄了過(guò)程.
LIBRARIES & CODE
AutoplayVideos
在RecyclerView中顯示url對(duì)應(yīng)的Video, 當(dāng)view出現(xiàn)時(shí)自動(dòng)播放, view不見或部分可見時(shí)自動(dòng)暫停.
PreferenceHolder
一個(gè)Kotlin的SharedPreferences的包裝庫(kù).
ActivityStarter
提供了一種簡(jiǎn)化的方式來(lái)啟動(dòng)多參數(shù)的Activity.
用注解簡(jiǎn)化了從Bundle拿參數(shù)的過(guò)程, 也有相應(yīng)的存取狀態(tài)的方法. 可以用于Activity, Fragment, Service和BroadcastReceiver.
BlockCanaryEx
基于BlockCanary的擴(kuò)展, 用于檢測(cè)UI阻塞, 打印出了更多的方法信息, 并顯示出最耗時(shí)的方法.
EasySP
一個(gè)簡(jiǎn)單的SharedPreferences輔助類, 支持流式操作.