
內(nèi)存優(yōu)化-進(jìn)階篇
內(nèi)存優(yōu)化分為:
內(nèi)存抖動(dòng)、內(nèi)存泄露、內(nèi)存溢出 OOM
1、優(yōu)秀的架構(gòu)設(shè)計(jì)
1.1 MVVM 設(shè)計(jì)模式
MVC 中 Controller 的生命周期遠(yuǎn)大于 View。
MVP 中 View 會持有 Presenter 引用,二者生命周期也不同步。
Jepact 框架 MVVM 中 ViewModel 跟 View 是完全獨(dú)立的,當(dāng) Activity 銷毀時(shí),ViewModel 中未執(zhí)行完的數(shù)據(jù)處理不會導(dǎo)致內(nèi)存泄露。Lifecycle 也能感知 Activity 的生命周期,根據(jù) Activity 的生命周期進(jìn)行相應(yīng)的回調(diào)。
1.2 統(tǒng)一管理
統(tǒng)一緩存管理:監(jiān)聽OnTrimMemory回調(diào),當(dāng)系統(tǒng)內(nèi)存不足時(shí),根據(jù)不同的狀態(tài)釋放內(nèi)存
統(tǒng)一圖片加載庫
統(tǒng)一的網(wǎng)絡(luò)請求庫
......
2、設(shè)備分級
同一個(gè)應(yīng)用在4G以上的內(nèi)存手機(jī)運(yùn)行流程,但是在2G以下的低端機(jī)卡頓。
2.1 設(shè)備分級
根據(jù)設(shè)備的年限-內(nèi)存-Android版本號進(jìn)行分級。
對于低端機(jī)用戶關(guān)閉復(fù)雜動(dòng)畫,或者某些功能;使用RGB 565格式的圖片,使用更小的緩存內(nèi)存等。
2.2 合理使用進(jìn)程
合理管理App的進(jìn)程,一個(gè)空的進(jìn)程也會占用10MB左右的內(nèi)存,為了進(jìn)程?;?,啟動(dòng)了很多進(jìn)程進(jìn)行相互拉活。
對于低端機(jī)來說,減少應(yīng)用啟動(dòng)進(jìn)程數(shù)目、減少常駐進(jìn)程、有節(jié)操的保活。
2.3 安裝包的大小
針對低端機(jī)用戶推出輕量的極速版APk,例如今日頭條極速版、QQ輕聊版。
3、 Bitmap優(yōu)化
Bitmap內(nèi)存在Android 3.0以下放在native中,3.0-8.0放到Java內(nèi)存中,8.0以上又放到native中。
3.1 統(tǒng)一圖片庫
項(xiàng)目所有的moudel中使用統(tǒng)一的圖片管理機(jī)制,收攏圖片的調(diào)用。
低端機(jī)使用565格式、更加嚴(yán)格的縮放算法、Glide使用low模式的內(nèi)存加載。
使用webp圖片,webp圖片跟png圖片相比減少了至少四分之一的內(nèi)存占用。
3.2 統(tǒng)一圖片監(jiān)控
-
大圖片監(jiān)控
如果圖片的尺寸遠(yuǎn)大于view的尺寸,或者不合規(guī)的圖片使用,開發(fā)過程中彈出dialog提示。
在灰度和線上環(huán)境下可以將異常信息上報(bào)到后臺,我們可以計(jì)算有多少比例的圖片會超過屏幕的大小,也就是圖片的“超寬率”。
-
重復(fù)圖片監(jiān)控
Bitmap 的像素?cái)?shù)據(jù)完全一致,但是有多個(gè)不同的對象存在。
解決重復(fù)圖片,減少內(nèi)存占用。
-
圖片總內(nèi)存
通過收攏圖片的使用,可以統(tǒng)計(jì)出應(yīng)用所有圖片內(nèi)存占用,線上可以根據(jù)不同的系統(tǒng)、屏幕分辨率等維度去分析圖片的內(nèi)存占用情況。
在OOM奔潰時(shí),將圖片的總內(nèi)存都寫到奔潰日志中了,幫助排查問題。
4、監(jiān)控
4.1 Java 內(nèi)存泄漏
-
內(nèi)存泄露監(jiān)控
LeakCanary 自動(dòng)化檢測方案,至少做到 Activity 和 Fragment 的泄漏檢測。
-
開發(fā)過程規(guī)避
四大塊:集合類導(dǎo)致、單例/靜態(tài)變量導(dǎo)致、匿名內(nèi)部類/非靜態(tài)內(nèi)部類、頁面銷毀未注銷導(dǎo)致
4.2 OOM 監(jiān)控
暫時(shí)找不到合適的方法,待學(xué)習(xí)。
3.3 線上監(jiān)控
抽取線上部分用戶,應(yīng)用在前臺時(shí),可以每 5 分鐘采集一次 PSS、Java 堆、圖片總內(nèi)存。
4.4 Native 內(nèi)存泄漏監(jiān)控
不懂,待學(xué)習(xí)。
4.5 GC 監(jiān)控
5、兜底方案
5.1 BaseActivity 的 onDestory() 統(tǒng)一兜底
在Activity onDestory的時(shí)候,遍歷View樹,清空 backGround、Drawable、EditText 的 TextWatcher 等
private void traverse(ViewGroup root) {
final int childCount = root.getChildCount();
for (int i = 0; i < childCount; ++i) {
final View child = root.getChildAt(i);
if (child instanceof ViewGroup) {
child.setBackground(null);
traverse((ViewGroup) child);
} else {
if (child != null) {
child.setBackground(null);
}
if (child instanceof ImageView) {
((ImageView) child).setImageDrawable(null);
} else if (child instanceof EditText) {
((EditText) child).cleanWatchers();
}
}
}
}
5.2 Handler 定時(shí)監(jiān)控
后臺監(jiān)控內(nèi)存,起一個(gè)HandlerThread,一直在后臺拿內(nèi)存使用的狀態(tài),如果應(yīng)用內(nèi)存占用超過總內(nèi)存的 80%,及時(shí)釋放一些內(nèi)存。
具體代碼:MemoryHandler.java
5.3 特定情況重啟
如果短時(shí)間上不會造成 OOM,但在長時(shí)間的使用中,會使得應(yīng)用占用內(nèi)存越積越大,最終也會造成 OOM 情況發(fā)生。
在用戶無感知的情況下,在接近觸發(fā)系統(tǒng)異常前,選擇合適的場景殺死進(jìn)程并將其重啟,使得應(yīng)用的內(nèi)存占用回到正常情況。
主要考慮了幾種條件:
- 是否在主界面退到后臺 且 位于后臺的時(shí)間超過 30 分鐘
- 當(dāng)前時(shí)間為凌晨 2~5 點(diǎn)
- 不存在前臺服務(wù)(存在通知欄,音樂播放欄等情況)
- java heap 必須大于當(dāng)前進(jìn)程最大可分配的 85% || native 內(nèi)存大于 800M || vmsize 超過了 4G的 85%
- 非大量的流量消耗(每分鐘不超過 1M) && 進(jìn)程無大量 CPU 調(diào)度情況