天天看點

Android Weekly Notes Issue #235

本期内容包括: 開發一個自定義View并釋出為開源庫的完整流程介紹; 用`AnimatedVectorDrawable`實作的動畫; 什麼樣的程式是可測試的; `DownloadManager`介紹; Okhttp的重試; Android 7取消了`file://`; Android Studio即将推出的build cache功能; 支援離線模式的app構架; 如何寫自定義的lint規則; Epoxy, 一個處理複雜RecyclerView屏的庫; `FragmentPagerAdapter`和`FragmentStatePagerAdapter`的比較等.

December 11th, 2016

Android Weekly Issue #235

本期内容包括: 開發一個自定義View并釋出為開源庫的完整流程介紹; 用<code>AnimatedVectorDrawable</code>實作的動畫; 什麼樣的程式是可測試的; <code>DownloadManager</code>介紹; Okhttp的重試; Android 7取消了<code>file://</code>; Android Studio即将推出的build cache功能; 支援離線模式的app構架; 如何寫自定義的lint規則; Epoxy, 一個處理複雜RecyclerView屏的庫; <code>FragmentPagerAdapter</code>和<code>FragmentStatePagerAdapter</code>的比較等.

作者開發了一個環形的SeekBar, 并把它作為一個庫釋出到了JCenter.

作者首先講了自定義View的實作:

首先是關于View生命周期的介紹, 在寫自定義View的時候有幾個關鍵的生命周期回調需要處理:

Android Weekly Notes Issue #235

作者實作的幾個關鍵步驟講解:

自定義屬性并擷取.

在<code>onMeasure()</code>中控制尺寸.

在<code>onDraw()</code>中繪制: 避免在<code>onDraw()</code>中配置設定記憶體; 用<code>invalidate()</code>方法來激發重繪.

在<code>onTouchEvent()</code>處理使用者手勢. 在他的環形SeekBar的實作裡, 這裡涉及到了點選坐标到角度的轉換.

将自定義View庫開源到Github:

開源到Github有個好的README很重要, 這裡有幾個tips:

提供截圖, Gif或者Video.

提供安裝/使用說明.

作者自己的庫: SwagPoints

釋出庫:

去JFrog Bintray注冊.

建立repository, package, 和版本号.

生成并上傳, 用了這個library.

添加到Jcenter.

被接受之後收到郵件, 就可以使用了.

用<code>AnimatedVectorDrawable</code>實作的一個很fancy的位置标志動畫.

如果程式的架構不适合測試, 那麼硬要寫一些測試很可能就會面臨這樣的局面: 要麼就是發現沒法寫測試, 要麼就是為了寫測試而破壞了代碼, 做了一些奇怪的事情.

那麼到底是什麼樣的程式才是适合寫測試, 或者是可測試的呢?

有一個有趣的定義是seam(接縫), 在接縫處你可以改變程式的行為, 而不用編輯目前程式. 如果程式沒有接縫, 你将無法設定測試的初始條件和驗證測試結果.

本文中舉了一個實際的例子, 開始的時候程式沒有seam, 是以導緻無法測試, 後來把靜态方法改為執行個體的方法之後, 我們就可以通過Mockito來模拟行為, 設定條件, 最後通過驗證某一方法的調用與否來進行驗證.

用<code>DownloadManager</code>來處理下載下傳.

首先它在裝置上有自己的UI, 還有notification, 還有Downloads app能讓使用者管理下載下傳檔案.

我們可以查詢到檔案的一些資訊, 比如MIME type, 檔案尺寸, 下載下傳狀态等.

我們還可以用<code>getUriForDownloadedFile()</code>方法來擷取一個URI, 配合MIME type, 發送Intent, 來打開一個相關的檢視程式.

關于儲存檔案的合适地點:

檔案小, 僅app自己使用 -&gt; 私有資料區域(預設行為).

檔案大, 僅app自己使用 -&gt; 外部存儲的私有資料區域(不需要權限). <code>setDestinationInExternalFilesDir()</code>.

檔案需要被别的應用通路 -&gt; 外部存儲的共有區域, 需要<code>WRITE_EXTERNAL_STORAGE</code>權限. <code>setDestinationInExternalPublicDir()</code>.

在網路較慢或不穩定的時候, OkHttp有可能會重複發送請求, 直到成功.

這個重試的邏輯是通過RetryAndFollowUpInterceptor.java實作的.

那麼, 我們可以關掉這個重試行為嗎? 有一些issues就在讨論這個問題: Issue # 1043. 後來有兩個pull requests: PR #1259和PR #2479改進了這個問題, 減少(但并沒有消除)了不必要的retry請求.

全局關閉重試行為: <code>OkHttpClient.Builder .retryOnConnectionFailure()</code>設定為false. 但是注意這樣是很粗暴并具有破壞性的, 消除了retry邏輯帶來的好處:

如果Url有多個IP, 失敗了一個還可以試另一個.

連接配接池中的連接配接偶爾會time out, 減少這種意外導緻的後果.

可以順次查找多個代理, 如果都失敗了再轉向直接連接配接.

解決真正的問題: 關閉靜默重試在某些情形下有幫助, 但是其實它隐藏了真正的問題, 就是你的API是否是幂等的idempotent. server端可以根據用戶端的GUID來檢測重複, 這樣server就不會多次執行操作, 會通知發送者.

Android N (Nougat, API 24)開始, 不再允許發送<code>file://</code>的Intent, 将會直接抛出<code>FileUriExposedException</code>異常.

是以當你把<code>targetSdkVersion</code>改為24之後, 你必須要確定你修複了這些問題再釋出.

解決方案是什麼呢? 用<code>content://</code>, 結合<code>FileProvider</code>:

首先在manifest裡面聲明:

然後在<code>res\xml\provider_paths.xml</code>檔案裡指明路徑:

最後, 把

改為

然後放在Intent裡發送就好了.

注意, 如果你的<code>targetSdkVersion</code>還沒有更新到24, 那麼即便是在Nougat的手機上<code>file://</code>也仍然是能正常使用的.

Android Studio目前的最新版是2.3 Canary 2. 有一些新的改進, 但是其中最吸引人的是這個build cache. 它會使你的clean build更快.

本文後面解析了build cache的工作原理.

一個好的應用應該在網絡不好甚至離線的時候仍然可以使用, 我們應該做些什麼呢?

确定連接配接狀況. 可以使用這個network-connection-class

. 如果你使用的是Okhttp, 可以加一個Intercepter來進行采樣.

有效地緩存. 從網絡取資料很慢并且昂貴, 是以有效地利用之前取到的資料是很關鍵的優化. (Cache-Control, Etag).

在本地操作, 在全局同步. 等網絡請求的時候可以先顯示本地資料, 而不是loading.

有效地處理線程.

優化圖檔. 網絡不好的時候先用RGB_565, 等網絡變好了再取高品質圖檔.

使用大Cookie. 盡量一次傳輸更多的資料(big cookie), 而不是頻繁發送一些小請求(small cookies).

如何建立自定義的lint規則.

事情的由來是作者發現了一個死循環調用, 然後他想做一個什麼标記以防以後其他人會犯同樣的錯誤.

然後他想到的是@Nullable注解, 的檢查, 實質是依靠lint來實作的.

于是他自己寫了一個自定義的lint規則, 來提示使用用他的注解<code>@CarefulNow</code>标記的方法時應當注意.

詳細的實作方式請看原文.

epoxy是一個Android庫, 用來處理複雜的RecyclerView屏. 本文介紹了它在項目中實際的使用.

可能有很多Android開發者對于

FragmentPagerAdapter和FragmentStatePagerAdapter的差別不是太清楚或根本不知道, 本文作者就具體介紹了二者的不同.

基本不同

<code>FragmentPagerAdapter</code>

适用于項目個數确定的情形.

為什麼呢? 因為一旦fragment的執行個體被建立, 它永遠也不會從<code>FragmentManager</code>中移除, 直到Activity被銷毀.

當Fragment不見的時候, 僅僅是<code>onDestroyView()</code>被調用, 當fragment再次回來時, 再調用<code>onCreateView()</code>.

<code>FragmentStatePagerAdapter</code>

當fragment的執行個體不可達的時候, 執行個體就會立即從<code>FragmentManager</code>移除. 被移除的fragment執行個體的狀态由<code>FragmentStatePagerAdapter</code>儲存, 當你再次回到該項的時候, fragment會重建新執行個體, 并且狀态被恢複. 是以這種adapter适用于項目個數不确定或的情況.

是以使用<code>FragmentPagerAdapter</code>的時候需要注意記憶體問題.

notifyDatasetChanged()的問題.

<code>notifyDataSetChanged()</code>是用來處理資料集變化的情況, 比如一些項目增删的情況. 這個方法不是用來重新整理目前顯示的Fragment或其中的Views的.

文章中還有一些關于資料改變實作以及現有issue的讨論. 為了解決issue作者還釋出了一個庫UpdatableFragmentStatePagerAdapter.

一個quick settings tile來開啟"Don't keep activities".

一個波形的loading圖, 水面上漲代表loading程度.

Simple MVWhatever for Android.

一個處理複雜的RecyclerView屏的庫.

錄屏腳本.

作者: 聖騎士Wind

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

Github: https://github.com/mengdd

微信公衆号: 聖騎士Wind

Android Weekly Notes Issue #235