Android Weekly Notes Issue #220
Android Weekly Issue #220
August 28th, 2016
Android Weekly Issue #220
ARTICLES & TUTORIALS
Manage dependencies versions with gradle extra properties
依賴(lài)管理的小Tip: 把依賴(lài)的版本號(hào)作為變量管理.
改造之后, build.gradle文件變成這樣:
定義了版本號(hào)變量, 原來(lái)hardcode時(shí)的單引號(hào)變成了雙引號(hào), 然后用$符號(hào)取變量值.
上面這個(gè)是app module里面使用的例子, 如果你的應(yīng)用有多個(gè)module怎么辦呢?
當(dāng)然一種辦法是每個(gè)module里定義一組版本號(hào)變量, 更方便的辦法是在項(xiàng)目工程總目錄的build.gradle文件里定義變量.
可以在工程的build文件里寫(xiě)
也可以這樣定義:
project.ext.supportLibVersion = '24.0.0'使用的時(shí)候可以這樣取值: $rootProject.supportLibraryVersion.
也可以省略前面的rootProject, 直接取$supportLibraryVersion
Android CI with Docker
作者講了他怎么用Docker搭建CI.
環(huán)境:
首先, CI需要Android環(huán)境(JDK 7&8, Android SDK, Gradle, Release keychain, google-services.json, etc).
裝了這些環(huán)境之后, 需要保證他們?cè)诿恳粋€(gè)CI實(shí)例上都是同步更新的.
用了Docker之后, 更新環(huán)境的步驟變?yōu)?
更新你的Dockerfile -> Push到版本管理系統(tǒng) -> CI會(huì)build新的image, 然后push到docker registry.
Build:
docker run -v ./app:/opt/app docker-ci-android:latest gradle assembleRelease
Test:
有兩種測(cè)試, 一種是單元測(cè)試, 只需要JVM; 另一種是UI或者功能測(cè)試, 需要Android.
emulator會(huì)有一些問(wèn)題: why
所以你可能想要在更真實(shí)的機(jī)器上測(cè)試: STF提供了服務(wù), 你只需要用這個(gè)stf-client.
Deploy:
部署用一些gradle的task就可以完成.
fabric
gradle-play-publisher
后面還提到了一些擴(kuò)展和問(wèn)題.
Bottom Sheets in Android
BottomSheet是support library 23.2加入的, 是從底部滑上來(lái)的一個(gè)塊塊, 用來(lái)向用戶(hù)展現(xiàn)更多內(nèi)容.
Support Library提供了:
BottomSheetBehavior: 加在CoordinatorLayout的直接child view上, 然后在java代碼里get出來(lái), 設(shè)置state控制其狀態(tài).
有HIDE, COLLAPSED和EXPANDED三種狀態(tài), 分別對(duì)應(yīng)隱藏, 展開(kāi)到指定高度(peekHeight)和完全展開(kāi).
BottomSheetDialog:
BottomSheetDialogFragment.
Behaviour是給View加行為, 后面這兩種是更加模塊化的dialog, 狀態(tài)控制都一樣.
這里推薦一下筆者自己的demo: AndroidDesignWidgetsSample
再推薦一下這篇文章里面的Bottom Sheets部分: CodePath-Handling-Scrolls-with-CoordinatorLayout
Certificate public key pinning using Retrofit 2
SSL handshake, 交換了證書(shū)(Certificate), 這樣客戶(hù)端就可以通過(guò)證書(shū)來(lái)驗(yàn)證服務(wù)器的身份.
什么是Certificate public key pinning呢? 也叫作SSL pinning.
把host name和public key關(guān)聯(lián)起來(lái), 這個(gè)public key將用來(lái)和證書(shū)中的public key比較, 如果匹配了, 就證明你正在和正確的server通信.
而直接pinning證書(shū)相比pinning public key更容易一些, 但是也有不好的地方, 如果網(wǎng)站(比如Google)經(jīng)常輪換證書(shū)(rotate its certificate), 你的應(yīng)用就也得經(jīng)常更新, 而這種情況一般證書(shū)里面的public keys是保持不變的.
如何在Android中用Retrofit實(shí)現(xiàn)pinning呢?
首先需要網(wǎng)站的public key的hash, 有很多獲取方法, 參見(jiàn)okhttp3-CertificatePinner.
然后構(gòu)建CertificatePinner類(lèi)對(duì)象, 加到OkHttpClient上.
TLSv1.2從Android16+開(kāi)始支持, 但是對(duì)于20+的設(shè)備默認(rèn)是disabled的, 為了強(qiáng)制獲取支持, 可以繼承SSLSocketFactory, 強(qiáng)制設(shè)置為enabled, 代碼見(jiàn)原文吧.
Github上有完整的代碼PublicKeyPinning
作者最后還推薦了一個(gè)測(cè)試的工具mitmproxy.
Isometric AnimatedVectorDrawable - Part 3
作者繼續(xù)講了他如何構(gòu)建方塊地形圖的動(dòng)態(tài)效果.
一個(gè)AnimatedVectorDrawable的xml文件實(shí)際上是用來(lái)建立一個(gè)映射關(guān)系, 關(guān)聯(lián)objectAnimators和VectorDrawable上的獨(dú)立元素. 我們可以建立一個(gè)objectAnimator, 操縱我們的一塊元素的動(dòng)畫(huà)效果.
文中實(shí)現(xiàn)了讓方塊地形動(dòng)起來(lái)的動(dòng)畫(huà)效果.
The many flavors of commit()
FragmentTransaction的提交方法:
support library的FragmentTransaction現(xiàn)在提供了四種不同的方法來(lái)commit一個(gè)transaction:
commit()
commitAllowingStateLoss()
commitNow()
commitNowAllowingStateLoss()
這篇文章分析了這四個(gè)方法的不同.
commit() vs commitAllowingStateLoss():
用commit()提交有時(shí)候會(huì)遇到IllegalStateException, 說(shuō)你在onSaveInstanceState()之后提交, 這里有另一個(gè)文章很好地分析了這個(gè)問(wèn)題:Fragment Transactions & Activity State Loss
commit()和commitAllowingStateLoss()在實(shí)現(xiàn)上唯一的不同就是當(dāng)你調(diào)用commit()的時(shí)候, FragmentManger會(huì)檢查是否已經(jīng)存儲(chǔ)了它自己的狀態(tài), 如果已經(jīng)存了, 就拋出IllegalStateException.
那么如果你調(diào)用的是commitAllowingStateLoss(), 并且是在onSaveInstanceState()之后, 你可能會(huì)丟失掉什么狀態(tài)呢?
答案是你可能會(huì)丟掉FragmentManager的狀態(tài), 即save之后任何被添加或被移除的Fragments.
舉例說(shuō)明:
1.在Activity里顯示一個(gè)FragmentA;
2.然后Activity被后臺(tái), onStop()和onSaveInstanceState()被調(diào)用;
3.在某個(gè)事件觸發(fā)下, 你用FragmentB replace FragmentA , 使用的是 commitAllowingStateLoss().
這時(shí)候, 用戶(hù)再返回應(yīng)用, 可能會(huì)有兩種情況發(fā)生:
1.如果系統(tǒng)殺死了你的activity, 你的activity將會(huì)重建, 使用了上述步驟2保存的狀態(tài), 所以A會(huì)顯示, B不會(huì)顯示;
2.如果系統(tǒng)沒(méi)有殺死你的activity, 它會(huì)被提到前臺(tái), FragmentB就會(huì)顯示出來(lái), 到下次Activity stop的時(shí)候, 這個(gè)包含了B的狀態(tài)就會(huì)被存下來(lái).
(上述測(cè)試可以利用開(kāi)發(fā)者選項(xiàng)中的”Don’t Keep Activities”選項(xiàng)).
那么你要選擇哪一種呢? 這就取決于你提交的是什么, 還有你是否能接受丟失.
commit(), commitNow() 和 executePendingTransactions():
使用commit()的時(shí)候, 一旦調(diào)用, 這個(gè)commit并不是立即執(zhí)行的, 它會(huì)被發(fā)送到主線(xiàn)程的任務(wù)隊(duì)列當(dāng)中去, 當(dāng)主線(xiàn)程準(zhǔn)備好執(zhí)行它的時(shí)候執(zhí)行.
popBackStack()的工作也是這樣, 發(fā)送到主線(xiàn)程任務(wù)隊(duì)列中去. 也即說(shuō)它們都是異步的.
但是有時(shí)候你希望你的操作是立即執(zhí)行的, 之前的開(kāi)發(fā)者會(huì)在commit()調(diào)用之后加上 executePendingTransactions()來(lái)保證立即執(zhí)行, 即變異步為同步.
support library從v24.0.0開(kāi)始提供了 commitNow()方法, 之前用executePendingTransactions()會(huì)將所有pending在隊(duì)列中還有你新提交的transactions都執(zhí)行了, 而commitNow()將只會(huì)執(zhí)行你當(dāng)前要提交的transaction. 所以commitNow()避免你會(huì)不小心執(zhí)行了那些你可能并不想執(zhí)行的transactions.
但是你不能對(duì)要加在back stack中的transaction使用commitNow(), 即addToBackStack()和commitNow()不能同時(shí)使用.
為什么呢?
想想一下, 如果你有一個(gè)提交使用了commit(), 緊接著又有另一個(gè)提交使用了commitNow(), 兩個(gè)都想加入back stack, 那back stack會(huì)變成什么樣呢? 到底是哪個(gè)transaction在上, 哪個(gè)在下? 答案將是一種不確定的狀態(tài), 因?yàn)橄到y(tǒng)并沒(méi)有提供任何保證來(lái)確保順序, 所以系統(tǒng)決定干脆不支持這個(gè)操作.
前面提過(guò)popBackStack()是異步的, 所以它同樣也有一個(gè)同步的兄弟popBackStackImmediate().
所以實(shí)際應(yīng)用的時(shí)候怎么選擇呢?
support library在FragmentPagerAdapter里就使用了commitNow()來(lái)保證在更新結(jié)束的時(shí)候, 正確的頁(yè)面被加上或移除.
Break circular dependency with RxJava 用RxJava打破循環(huán)依賴(lài).
當(dāng)你把代碼分成各個(gè)部分, 比如用MVP, 這些各個(gè)部分之間可能會(huì)有相互依賴(lài), 比如View需要Presenter, Presenter也需要View.
作者也沒(méi)有說(shuō)雙向關(guān)聯(lián)有什么缺點(diǎn), 但是他說(shuō)RxJava可以把這種雙向的依賴(lài)改成單向的.
作者的辦法是使用RxBinding把button的click事件變成一個(gè)Observable, 然后Presenter監(jiān)聽(tīng)click這個(gè)Observable, 后面接一個(gè)flatMap, 里面發(fā)網(wǎng)絡(luò)請(qǐng)求, 得到結(jié)果之后再調(diào)用view的方法.
這么一改以后View中就不需要再持有Presenter的引用了.
舉這個(gè)例子, 最后是想說(shuō), 如果你想從A中調(diào)用B的異步方法, 你不用總是在A中保存一個(gè)B的引用, 你可以把A中的事件作為一個(gè)Observable. 這樣只需要B保存了A的引用就可以了.
Asynchronous layout inflation 異步解析layout
最近的support library revision 24中, Google的開(kāi)發(fā)者在v4包中加入了一個(gè)新的輔助類(lèi)AsyncLayoutInflater, 來(lái)實(shí)現(xiàn)布局的異步解析.
我們現(xiàn)在常用的布局解析inflate方法都是同步的, 那什么時(shí)候需要異步地做這件事情呢?
比如你想延遲加載布局中的一塊, 或者你想把布局解析作為用戶(hù)某個(gè)交互的一個(gè)響應(yīng). 這樣就可以用這個(gè)異步布局解析類(lèi), 保證了主線(xiàn)程在inflation進(jìn)行的時(shí)候仍然可響應(yīng).
怎么使用呢?
首先, 在主線(xiàn)程創(chuàng)建對(duì)象AsyncLayoutInflater(this),
用它inflate布局的時(shí)候第三個(gè)參數(shù)是一個(gè)OnInflateFinishedListener回調(diào).
以前同步方法的第三個(gè)參數(shù)是一個(gè)boolean, 說(shuō)布局是否需要attach到parent上, 現(xiàn)在沒(méi)有這個(gè)boolean參數(shù)了.
當(dāng)然, 使用異步解析也有缺點(diǎn):
- 父類(lèi)方法generateLayoutParams()必須是線(xiàn)程安全的.
- 被創(chuàng)建的所有View不能創(chuàng)建Handler,或者調(diào)用Looper.myLooper()方法.
- 不支持設(shè)置LayoutInflater.Factory和LayoutInflater.Factory2
- 不支持布局里有Fragment.
如果我們要異步inflate的布局不能支持異步, inflate的過(guò)程將會(huì)自動(dòng)轉(zhuǎn)化為在UI線(xiàn)程的解析.
作者文中附有Kotlin的例子.
Introduction to Automated Android Testing - Part 5
系列文章的第五篇, 之前第四篇的時(shí)候?qū)懥薖resenter, 定義了V和P的接口, 本篇接著寫(xiě)View接口的實(shí)現(xiàn).
這里Presenter和View關(guān)聯(lián)作者寫(xiě)了兩個(gè)attachView()和detachView()方法, 前者在Presenter構(gòu)造之后調(diào)用, 后者在Activity的onDestroy()里調(diào)用. 這里同時(shí)會(huì)unregister RxJava的subscriptions, 避免了內(nèi)存泄露的發(fā)生.
作者在布局時(shí)用了ConstraintLayout, 關(guān)于這個(gè)layout的使用她有另一個(gè)blog
另外作者還加了Toolbar上的SearchView, 到此, 作者的這個(gè)app就基本完成了.
作者的代碼里還有一個(gè)Injection類(lèi), 用來(lái)提供retrofit的service, 即代碼中UserRepo的獲取, 在Presenter構(gòu)造時(shí)傳入.
作者的代碼: GithubUsersSearchApp
預(yù)告下一篇將會(huì)加入U(xiǎn)I測(cè)試.
DiffUtil is a must!
support library 24.2.0推出了一個(gè)新的輔助類(lèi)DiffUtil, 它是用來(lái)解決什么問(wèn)題的呢?
如果你的RecyclerView.Adapter第一次接收到了新的數(shù)據(jù), 這很簡(jiǎn)單, 只需要將它們顯示出來(lái), 但如果已經(jīng)有了數(shù)據(jù), 新的數(shù)據(jù)又來(lái)了, 這時(shí)候怎么做才是最好的呢?
DiffUtil來(lái)了, 它就是專(zhuān)門(mén)為了解決RecyclerView的Adapter更新而設(shè)計(jì)的, 他可以計(jì)算出前后兩個(gè)list的不同, 然后返回一組更新操作, 把第一個(gè)list變?yōu)榈诙€(gè)list.
DiffUtil需要知道你的兩個(gè)list的基本信息: 長(zhǎng)度, 基本item的比較.
DiffUtil.Callback是用來(lái)向DiffUtil提供這些基本信息的, 它是一個(gè)抽象類(lèi), 你需要繼承它, 然后覆寫(xiě)里面的幾個(gè)方法. 它的構(gòu)造傳入了兩個(gè)待比較的list, 覆寫(xiě)的方法主要是get它們的size, 比較它們的內(nèi)容.
Callback里還有一個(gè)getChangePayload()方法, 它不是抽象的, 這個(gè)方法在areItemsTheSame() 返回true, 但是areContentsTheSame()返回false的時(shí)候被調(diào)用.
這意味著我們的item還是之前的那個(gè)item,但是可能里面的字段變化了.
這個(gè)方法的返回值即為兩個(gè)對(duì)應(yīng)item的diff, 基本來(lái)說(shuō), 這個(gè)方法返回的是為什么我們認(rèn)為list變化了.
文中的代碼例子返回了一個(gè)Bundle, 把compare不相等的字段都放進(jìn)去了, 用的是new item的值.
一旦我們寫(xiě)好了這個(gè)Callback類(lèi), 剩下的事情就很簡(jiǎn)單了, 我們只需要在新數(shù)據(jù)到來(lái)的時(shí)候計(jì)算一下diff, 然后更新.
@ Override public void onNewProducts(List<Product> newProducts) {DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new ProductListDiffCallback(mProducts, newProducts));diffResult.dispatchUpdatesTo(mProductAdapter);}當(dāng)然上面getChangePayload()返回的對(duì)象還得我們自己利用起來(lái), 它會(huì)被DiffResult分發(fā)到Adapter.
用的是notifyItemRangeChange(position, count, payload)方法, 傳到了Adapter的onBindViewHolder()方法, 我們判斷payload不為空時(shí), 從里面拿出diff做更新.
文檔里說(shuō)DiffUtil對(duì)很大的數(shù)據(jù)集可能比較費(fèi)時(shí), 所以建議把計(jì)算放在后臺(tái)線(xiàn)程.
作者還給出了一個(gè)RxJava的例子, 各種flatMap.
DESIGN
Diverse Device Hands
Facebook的design資源, 很多拿著手機(jī)的手的照片.
LIBRARIES & CODE
unipiazza-android-twostepslogin
一個(gè)實(shí)現(xiàn)兩步登錄的庫(kù), 比如Google web登錄, Material Design.
要用它的布局, 然后設(shè)置一些屬性, 還有UI交互事件的Listener.
Om Recorder
一個(gè)簡(jiǎn)單的Pcm / Wav 錄音機(jī), API簡(jiǎn)單, 可以錄制Pcm和Wav音頻, 可以配置輸出, 有暫停功能.
tiger
又一個(gè)依賴(lài)注入庫(kù), 但是README里說(shuō)這不算一個(gè)Google的官方產(chǎn)品, 官方的是Dagger和Guice.
這個(gè)tiger好像自稱(chēng)是目前最快的java依賴(lài)注入.
NEWS
Taking the final wrapper off of Android 7.0 Nougat
Android 7.0已經(jīng)問(wèn)世了, 從Nexus開(kāi)始, 同時(shí)API 24的source code已經(jīng)push到AOSP了.
轉(zhuǎn)載于:https://www.cnblogs.com/mengdd/p/5829870.html
總結(jié)
以上是生活随笔為你收集整理的Android Weekly Notes Issue #220的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 13 存储媒介
- 下一篇: iOS imageview图片压缩变形