版權聲明:本文原創發佈於公衆號 wingjay,轉載請務必註明出處! https://www.jianshu.com/p/717b80ba8bc1
There are two kinds of people :
those who learn the hard way and those who learn by taking someone’s advice.
當前Android開發領域最佳實踐方案,覆蓋了最新的技術、架構等各種細節。
本文主要用來收集Android開發中積累的一些寶貴經驗,這些經驗中有一些約定熟成且經過檢驗的建議,有一些結合最新技術的實踐。無論是菜鳥還是大神,都應該學會閱讀別人的經驗,並結合自己的思考轉化成對自己有用的知識,這纔是最快的成長之路。另外,對於這些建議,我會盡量翔實的進行說明以確保能夠順利快速應用到實際開發中。
介紹
下面以這篇文章:Building Android Apps — 30 things that experience made me learn the hard way爲核心,對其中提出的每一點建議進行較爲深入的分析探究,最終整理成一篇完整的文章。當然,本文還在不斷更新中。
1. 第三方庫
在你添加每一個third party library之前,請認真考慮是否真的需要這個library。
2. OverDraw
如果用戶看不到,那就不要進行繪製(draw),或者說,不要過度繪製 OverDraw
OverDraw
會導致GPU浪費,也會導致app的速度變慢。爲了減少這種危害,我們可以利用Debug GPU Overdraw Tool
來觀察app裏的繪製情況,然後可以使用Hierarchy Viewer來進行優化。
3. 數據庫
除非不得不,否則不要使用database
4. 65k methods limit
Dalvik 65K methods limit
你很快就會遇到的,不過放心,multidexing
會幫助你。
什麼是Dalvik 65K methods limit
?我們知道,我們寫完java code之後,dx tool會把java編譯成Dalivik虛擬機能識別的DEX
文件,這個文件裏最多能夠索引65536個method
。關於這個有兩點要注意:
- 這些method是指能夠
索引(reference)
到的,而不是定義(define)
的。或者說,如果你定義了一個方法,但這個方法並沒有被調用,那麼就不算在內。 - 這些method不僅僅是開發人員自己寫的,還包括所有第三方library裏面的method。
所以,我們總共可以索引65536
個方法,包括自己寫的和引入第三方庫裏的。
那麼,我們如何能快速知道我們的app裏已經有多少個method了呢?
- bash script: dex-method-counts。這個工具可以快速計算,並且提供一個清晰的視圖來閱讀。
- dex.sh by Jake Wharton。這個工具由於採用了遞歸算法,所以耗時比較長。(Jake大神還寫了一篇有趣的分析文章Play Services 5.0 Is A Monolith Abomination,針對Play Services 5.0太大的問題進行了分析,有空時我再翻譯下給各位。雖然Play Services 6.5已經模塊化,更加輕量級了)。
現在,既然我們已經知道了自家app裏的method數了,那麼如何來處理這種情況呢?
- Multidex,官方提供的解決方案,這篇文章裏有詳細的使用方法,此不贅述。
-
ProGuard
ProGuard
可以把code裏unnecessary的method移除,壓縮apk,當然還有代碼混淆
的奇效。 - 再創建一個
DEX File
。把app裏可以獨立的模塊或code提取出來,放到一個獨立的dex文件裏,你可以使用Custom ClassLoader來加載這些類,然後使用接口
或反射
來調用這些方法。不過,這個過程還是比較麻煩的。
5. RxJava+RxAndroid+Retrolambda
使用前可以通過這篇gist來了解RxJava, RxAndroid & Retrolambda
的結合用法。這個組合可以優雅的在不同線程中處理事務,同時能夠方便的實現數據流動和及時響應,而且Retrolambda能夠精簡你的code。其中,核心的兩個概念是Observables
和Subscribers
,前者對外提供數據,後者監聽並消費這些數據。
另外,這裏有一個看起來不錯的項目Learning RxJava for Android by example,等空閒時再去閱讀下code。
6. Retrofit + RxJava
利用Retrofit與RxJava結合,爲你的app提供網絡請求服務。
你可以參考這個超讚的例子,讓你快速感受二者結合使用方法。
7. 按feature
來分package,而不是按layer
這是這篇文章提出的一個點,文中認爲分package就像公司安排座位,要按照team
來分而不是按照每個人的職位來分,即按照負責一個app的developer、designer、pm
坐在一起,而不是把所有developer
坐在一起,所有designer
坐在一起。所以,原文作者認爲把一個feature相關的如Activity
adapter
等都放在一起。
不過,我認爲按feature也有壞處,那就是複用
,拿adapter來講,一個app裏很多adapter是類似且可以複用的,如果我們把各個adapter拆倒各個角落裏,就很難提取其中的關聯來創建一個BaseAdapter
了。而且,不同feature之間也有很多公用的東西
,比如一個自定義view,那就很難界定應該放在哪個feature包裏了。相反,我們把所有自定義view放在一起,這樣也有助於我們發現某些自定義view的區別,然後在refactor時可以提取公用的東西來複用
。
關於這點,歡迎讀者給出自己意見。
8. 加速Gradle
9. 架構:使用clean Architecture
這裏有兩篇優質文章:Architecting Android…The clean way?和Architecting Android…The evolution 分別介紹並用code實現了一個Clean架構。後面我也會專門分析下這種架構,因爲對於任何一個project而言,最初的好的架構是非常重要的!所以,如果你想提高自己,那麼架構
這一關是必經之路。
10. 測試你的app
雖然做測試需要花費你不少時間,但一旦你完成了這一步,以後的開發會更加快速,app也會更加穩定。
這裏有個哥們,對unit test
進行了細緻的點評。
11. 使用依賴注入
神器Dagger
如果你不知道什麼是依賴注入
,你可以先讀一下這篇文章Dependency injection on Android: Dagger (Part 1),或者這篇依賴注入。簡單來說,依賴注入替代了傳統創建對象的new
操作,當需要創建一個class的實例時,使用依賴注入從外部直接獲取一個實例,具體這個實例是如何創建的不需要關心,由一個對象庫統一管理每個對象的創建過程,並直接對外提供對象。這樣做的好處是我們不用管實例是怎麼創建的,這種抽象可以使得每個對象的創建過程變得可擴展性,只要在對象庫裏修改一次,那麼所有用到這個實例的地方都隨之變更。例如在測試時,我們希望某個mock某個對象的數據,就可以修改注入的對象。
依賴注入有不少工具,不過Dagger2使用的是編譯時代碼生成(code generation)
方式而不是反射(reflection)
,所以它的性能比較出衆。這篇文章有對Dagger2的實踐和分析。
12. 對EditText使用合適的輸入類型
13. 關注新的開源library
你可以通過Android Arsenal來保持對開源項目的關注,同時利用這個工具dryrun來快速將開源項目跑在genymotion以看到實際效果。
14. Service
如果你創建了Service,那麼一旦這個Service完成了自己的使命,就應該立即清理掉它
15. 使用AccountManager來統一管理用戶的帳號密碼。
16. 使用持續集成CI(Continuous Integration)
來編譯併發布你的beta和release build
持續集成可以幫助你方便的編譯併發布項目,不過,不要去搭建你們自己的CI服務器,因爲你需要花費太多的時間來處理硬盤空間、安全問題和預防SSL攻擊等問題。你可以嘗試Jenkins、circleci、 travis 或者 shippable,這些價格並不貴,而且能幫你省很多事情。
17. 自動發佈到Play Store
你可以使用這個工具gradle-play-publisher來幫助你自動上傳apk到Play Store上
18. 開始考慮用svg替代png
理由很簡單,android developer應該很熟悉每次導入一張圖片時都需要生成四五種不同大小的png圖片並放入到對應文件夾。與其維護這麼多圖片,顯然使用一張svg圖片更加方便。而且,google也在不斷提供相關的支持,除了基本的Vector Drawable,從最新的Support Library我們也能看到google也在鼓勵developer們使用svg。
不過,svg也有自己的限制,比如它比較適合小icon,因爲它最終會生成bitmap加載以供顯示,所以這需要一定的cpu支持。當然總體來講svg還是更優的,至少大家可以不用再維護四五張不同尺寸的圖片了。
19. 將一些library的類進行抽象,從而方便後期替換library
例如,一旦我們打log會使用Log.i()
,但是,如果後面我們突然想換成Timber.i()
就會很麻煩,需要一個個log找到來替換。但是,如果我們抽象出一個AppLogger
來,全部調用AppLogger.i()
來記log,那麼我們只要簡單的在AppLogger
內部替換掉具體實現就可以了。
20. 監控網絡連接類型,區分移動數據流量
和Wi-Fi
;同樣,可以監控電量
和充電狀態
對於不同網絡類型,我們可以動態改變我們的UI,比如大圖可以選擇在Wi-Fi
下才加載,而在移動數據流量
則不加載。對於電量
也是類似的邏輯。用戶一定會很感謝app做的這種自適應的。
21. User Interface is like a joke.If you have to explain it, it's not that good.
22. 先寫slow但是right的代碼,再去進行優化
小結
本文長期更新,會保持跟蹤最新的技術和研究實踐經驗,爲大家提供有效有用的經驗,少走坑。
謝謝!
wingjay