I. 網絡相關
http頭信息帶Cache-Control域 確定緩存過期時間 防止重復請求
直接用IP直連,不用域名,策略性跟新本地IP列表。 – DNS解析過程耗時在百毫秒左右,并且還有可能存在DNS劫持。
圖片、JS、CSS等靜態資源,采用CDN(當然如果是使用7牛之類的服務就已經給你搭建布置好了)
全局圖片處理采用漏斗模型全局管控,所請求的圖片大小最好依照業務大小提供/最大不超過屏幕分辨率需要,如果請求原圖,也不要超過GL10.GL_MAX_TEXTURE_SIZE
全局縮略圖直接采用webp,在盡可能不損失圖片質量的前提下,圖片大小與png比縮小30% ~ 70%
如果列表里的縮略圖服務器處理好的小圖,可以考慮直接在列表數據請求中,直接以base64在列表數據中直接帶上圖片(國內還比較少,海外有些這種做法,好像web端比較常見)
輪詢或者socket心跳采用系統AlarmManager提供的鬧鐘服務來做,保證在系統休眠的時候cpu可以得到休眠,在需要喚醒時可以喚醒(持有cpu喚醒鎖)
可以通過將零散的網路的請求打包進行一次操作,避免過多的無線信號引起電量消耗。
1. 傳輸數據格式選擇
如果是基本需要全量數據的,考慮使用Protobuffers (序列化反序列化性能高于json)
如果傳輸回來的數據不需要全量讀取,考慮使用Flatbuffers (序列化反序列化幾乎不耗時,耗時是在讀取對象時(就這一部分如果需要優化,可以參看Flatbuffer Use Optimize
2. 輸入流
使用具有緩存策略的輸入流
原 建議替換為
InputStream BufferedInputStream
Reader BufferedReader
II. 數據結構
如果已知大概需要多大,就直接給初始大小,減少擴容時額外開銷。
1. List
ArrayList
里面就一數組,內存小,有序取值快,擴容效率低
LinkedList
里面就一雙向鏈表,內存大,隨機插入刪除快,擴容效率高。
2. Hash
HashSet
里面就一個HashMap,用key對外存儲,目的就是不允許重復元素。
ConcurrentHashMap
線程安全,采用細分鎖,鎖顆粒更小,并發性能更優
Collections.synchronizedMap
線程安全,采用當前對象作為鎖,顆粒較大,并發性能較差。
3. Int作為Key的Map
針對該特性進行了優化,采用二分法查找,簡單數組存儲。
SparseArray、SparseBooleanArray、SparseIntArray。
III. 數據庫相關
建多索引的原則: 哪個字段可以最快的減少查詢結果,就把該字段放在最前面
無法使用索引的情況
操作符BETWEEN、LIKE、OR
表達式
CASE WHEN
不推薦
不要設計出索引是其他索引的前綴(沒有意義)
更新時拒絕直接全量更新,要更新哪列就put哪列的數據
如果最頻繁的是更新與插入,別建很多索引 (原本表就很小就也沒必要建)
拒絕用大字符串創建索引
避免建太多索引,查詢時可能就不會選擇最好的來執行
推薦
多使用整型索引,效率遠高于字符串索引
搜索時使用SQL參數("?", parameter)代替字符串拼接(底層有特殊優化與緩存)
查詢需要多少就limit多少(如判斷是否含有啥,就limit 1就行了嘛)
如果出現很寬的列(如blob類型),考慮放在單獨表中(在查詢或者更新其他列數據時防止不必要的大數據i/o影響性能)
IV. JNI抉擇
Android JVM相關知識,可參看: ART、Dalvik
Android JNI、NDK相關知識,可參看: NDK
JNI不一定顯得更快,有些會更慢。
特點: 不用在虛擬機的框子下寫代碼
可以調用更底層的高性能的代碼庫 – Good
如果是Dalvik,將省去了由JIT編譯期轉為本地代碼的這個步驟。 – Good
Java調用JNI的耗時較Java調用Java肯定更慢,雖然隨著JDK版本的升級,差距已經越來越小(JDK1.6版本是5倍Java調用Java方法的耗時) – Bad
內存不在Java Heap,沒有OOM風險,有效減少gc。 – Good
一些重要的參數之類,也可以考慮放在Native層,保證安全性。參考: Android應用程序通用自動脫殼方法研究
V. 多進程抉擇
360 17個進程: 360手機衛士 Android開發 InfoQ視頻 總結
充分獨立,解耦部分
大內存(如臨時展示大量圖片的Activity)、無法解決的crash、內存泄漏等問題,考慮通過獨立進程解決
獨立于UI進程,需要在后臺長期存活的服務(參看Android中線程、進程與組件的關系)
非己方第三方庫(無法保證穩定、性能等問題,并且獨立組件),可考慮獨立進程
最后,多進程存在的兩個問題: 1. 由于進程間通訊或者首次調起進程的消耗等,帶來的cpu、i/o等的資源競爭。2. 也許對于部分同事來說,會還有可讀性問題吧,畢竟多了層IPC繞了點。
VI. UI層面
相關深入優化,可參看Android繪制布局相關
對于卡頓相關排查推薦參看: Android性能優化案例研究(上)與Android性能優化案例研究(下)
減少不必要的不透明背景相互覆蓋,減少重繪,因為GPU不得不一遍又一遍的畫這些圖層
保證UI線程一次完整的繪制(measure、layout、draw)不超過16ms(60Hz),否則就會出現掉幀,卡頓的現象
在UI線程中頻繁的調度中,盡量少的對象創建,減少gc等。
分步加載(減少任務顆粒)、預加載、異步加載(區別出耗時任務,采用異步加載)
VII. 庫推薦
可以參考Falcon Pro作者的推薦: Falcon Pro 3如何完成獨立開發演講分析
1. 代碼編寫習慣
RxJava (響應式編程,代碼更加簡潔,異步處理更快快捷、異常處理更加徹底、數據管道理念)
相關了解可以參看: RxJava
2. 圖片加載:
小型快捷: Picasso (接口干凈、支持okhttp、功能強大、穩定、高效, 可以延讀: PhotoGallery、Volley、Picasso 比較)
大項目考慮: Fresco (2.5M,pipeline解決資源競爭、Native Heep解決OOM,的同時減少GC)
3. 網絡底層庫:
Okhttp: 默認gzip、緩存、安全等
4. 網絡基層:
Retrofit: 非常好用的REST Client,結合RxJava簡單API實現、類型安全,簡單快捷
5. 數據庫層:
Realm: 效率極高(Falcon Pro 3的作者Joaquim用了該庫以后,所有數據庫操作都放到了UI線程)(基于TightDB,底層C++閉源,Java層開源,簡單使用,性能遠高于SQLite等)
6. Crash上報:
Fabric: 全面的信息(新版本還支持JNI Crash獲取和上報)、穩定的數據、及時的通知、強大的反混淆(其實在混淆后有上傳mapping)
7. 內存泄漏自動化檢測
LeakCanary: 自動化泄漏檢測與分析 ( 可以看看這個LeakCanary使用總結與Leakcanary Square的一款Android/Java內存泄漏檢測工具)
8. 其他
代碼質量: phabricator 的arc diff (盡量小顆粒度的arc diff 與update review),其實也可以看看Google是如何做的: 筆記-谷歌是如何做代碼審查的,還有一點的TODO要寫好deadline與master
編包管理: Gitlab CI (結合Gitlab,功能夠用,方便)
VIII. 內存泄漏相關
無法解決的泄漏(如系統底層引起的)移至獨立進程(如2.x機器存在webview的內存泄漏)
大圖片資源/全屏圖片資源,要不放在assets下,要不放在nodpi下,要不都帶,否則縮放會帶來額外耗時與內存問題
4.x在AndroidManifest中配置largeHeap=true,一般dvm heep最大值可增大50%以上。
在Activity#onDestory以后,遍歷所有View,干掉所有View可能的引用(通常泄漏一個Activity,連帶泄漏其上的View,然后就泄漏了大于全屏圖片的內存)。
萬金油: 靜態化內部類,使用WeakReference引用外部類,防止內部類長期存在,泄漏了外部類的問題。
圖片Decode
全局統一BitmapFactory#decode出口,捕獲此處decode oom,控制長寬(小于屏幕分辨率大小 )
如果采用RGB_8888 oom了,嘗試RGB_565(相比內存小一半以上(wh2(bytes)))
如果還考慮2.x機器的話,設置BitmapFactory#options的InNativeAlloc參數為true,此時decode的內存不會上報到dvm中,便不會oom。
IX. 編譯與發布
考慮采用DexGuard,或ProGuard結合相關資源混淆來提高安全與包大小,參考: DexGuard、Proguard、Multi-dex
結合Gradle、Gitlab-CI 與Slack(Incoming WebHooks),快速實現,打相關git上打相關Tag,自動編相關包通知Slack。
結合Gitlab-CI與Slack(Incoming WebHooks),快速實現,所有的push,Slack快速獲知。
結合Gradle中Android提供的productFlavors參數,定義不同的variations,快速批量打渠道包
X. 其他
final能用就用(高效: 編譯器在調用final方法時,會轉入內嵌機制)
懶預加載,如簡單的ListView、RecyclerView等滑動列表控件,停留在當前頁面的時候,可以考慮直接預加載下個頁面所需圖片
智能預加載,通過權重等方式結合業務層面,分析出哪些更有可能被用戶瀏覽使用,然后再在某個可能的時刻進行預加載。如,進入朋友圈之前通過用戶行為,智能預加載部分原圖。
做好有損體驗的準備,在一些無法避免的問題面前做好有損體驗(如,非UI進程crash,可以自己解決就不要讓用戶感知,或者UI進程crash了,做好場景恢復)
做好各項有效監控:crash(注意還有JNI的)、anr(定期掃描文件)、掉幀(繪制監控、activity生命周期監控等)、異常狀態監控(本地Log根據需要不同級別打Log并選擇性上報監控)等
文件存儲推薦放在/sdcard/Android/data/[package name]/里(在應用卸載時,會隨即刪除)(Context#getExternalFilesDir()),而非/sdcard/根目錄建文件夾(節操問題)
通過gradle的shrinkResources與minifyEnabled參數可以簡單快速的在編包的時候自動刪除無用資源
由于resources.arsc在api8以后,aapt中默認采用UTF-8編碼,導致資源中大都是中文的resources.arsc相比采用UTF-16編碼更大,此時,可以考慮aapt中指定使用UTF-16
谷歌建議,大于10M的大型應用考慮安裝到SD卡上: App Install Location
當然運維也是一方面: Optimize Your App
在已知并且不需要棧數據的情況下,就沒有必要需要使用異常,或創建Throwable生成棧快照是一項耗時的工作。