本篇是 Android 內存優化的進階篇,難度能夠說達到了煉獄級別,建議對內存優化不是很是熟悉的仔細看看前篇文章: Android性能優化以內存優化,其中詳細分析瞭如下幾大模塊:php
若是你對以上基礎內容都比較瞭解了,那麼咱們便開始 Android 內存優化的探索之旅吧。html
本篇文章很是長,建議收藏後慢慢享用~前端
Android給每一個應用進程分配的內存都是很是有限的,那麼,爲何不能把圖片下載下來都放到磁盤中呢?那是由於放在 內存 中,展現會更 「快」,快的緣由有兩點,以下所示:java
這裏說一下解碼的概念。Android系統要在屏幕上展現圖片的時候只認 「像素緩衝」,而這也是大多數操做系統的特徵。而咱們 常見的jpg,png等圖片格式,都是把 「像素緩衝」 使用不一樣的手段壓縮後的結果,因此這些格式的圖片,要在設備上 展現,就 必須通過一次解碼,它的 執行速度會受圖片壓縮比、尺寸等因素影響。(官方建議:把從內存中淘汰的圖片,下降壓縮比後存儲到本地,以備後用,這樣能夠最大限度地下降之後複用時的解碼開銷。)linux
下面,咱們來了解一下內存優化的一些重要概念。android
手機不使用 PC 的 DDR內存,採用的是 LPDDR RAM,即 」低功耗雙倍數據速率內存「。其計算規則以下所示:git
LPDDR系列的帶寬 = 時鐘頻率 ✖️內存總線位數 / 8
LPDDR4 = 1600MHZ ✖️64 / 8 ✖️雙倍速率 = 25.6GB/s。
複製代碼
當系統 內存充足 的時候,咱們能夠 多用 一些得到 更好的性能。當系統 內存不足 的時候,咱們但願能夠作到 」用時分配,及時釋放「。github
對於Android內存優化來講又能夠細分爲以下兩個維度,以下所示:web
主要是 下降運行時內存。它的 目的 有以下三個:算法
下降應用佔ROM的體積,進行APK瘦身。它的 目的 主要是爲了 下降應用佔用空間,避免因ROM空間不足致使程序沒法安裝。
那麼,內存問題主要是有哪幾類呢?內存問題一般來講,能夠細分爲以下 三類:
下面,咱們來了解下它們。
內存波動圖形呈 鋸齒張、GC致使卡頓。
這個問題在 Dalvik虛擬機 上會 更加明顯,而 ART虛擬機 在 內存管理跟回收策略 上都作了 大量優化,內存分配和GC效率相比提高了5~10倍,因此 出現內存抖動的機率會小不少。
Android系統虛擬機的垃圾回收是經過虛擬機GC機制來實現的。GC會選擇一些還存活的對象做爲內存遍歷的根節點GC Roots,經過對GC Roots的可達性來判斷是否須要回收。內存泄漏就是 在當前應用週期內再也不使用的對象被GC Roots引用,致使不能回收,使實際可以使用內存變小。簡言之,就是 對象被持有致使沒法釋放或不能按照對象正常的生命週期進行釋放。通常來講,可用內存減小、頻繁GC,容易致使內存泄漏。
即OOM,OOM時會致使程序異常。Android設備出廠之後,java虛擬機對單個應用的最大內存分配就肯定下來了,超出這個值就會OOM。單個應用可用的最大內存對應於 /system/build.prop 文件中的 dalvik.vm.heapgrowthlimit。
此外,除了因內存泄漏累積到必定程度致使OOM的狀況之外,也有一次性申請不少內存,好比說 一次建立大的數組或者是載入大的文件如圖片的時候會致使OOM。並且,實際狀況下 不少OOM就是因圖片處理不當 而產生的。
在 Android性能優化以內存優化 中咱們已經介紹過了相關的優化工具,這裏再簡單回顧一下。
強大的 Java Heap 分析工具,查找 內存泄漏及內存佔用, 生成 總體報告、分析內存問題 等等。建議 線下深刻使用。
自動化 內存泄漏檢測神器。建議僅用於線下集成。
它的 缺點 比較明顯,具體有以下兩點:
ART 和 Dalvik 虛擬機使用 分頁和內存映射 來管理內存。下面咱們先從Java的內存分配開始提及。
Java的 內存分配區域 分爲以下 五部分:
流程可簡述爲 兩步:
實現比較簡單。
流程可簡述爲 三步:
實現簡單,運行高效,每次僅需遍歷標記一半的內存區域。
會浪費一半的空間,代價大。
流程可簡述爲 三步:
如今 主流的虛擬機 通常用的比較多的仍是分代收集算法,它具備以下 特色:
Android 中的內存是 彈性分配 的,分配值 與 最大值 受具體設備影響。
對於 OOM場景 其實能夠細分爲以下兩種:
咱們須要着重注意一下這兩種的區分。
以Android中虛擬機的角度來講,咱們要清楚 Dalvik 與 ART 區別,Dalvik 僅固定一種回收算法,而 ART 回收算法可在 運行期按需選擇,而且,ART 具有 內存整理 能力,減小內存空洞。
最後,LMK(Low Memory killer) 機制保證了進程資源的合理利用,它的實現原理主要是 根據進程分類和回收收益來綜合決定的一套算法集。
當 內存頻繁分配和回收 致使內存 不穩定,就會出現內存抖動,它一般表現爲 頻繁GC、內存曲線呈鋸齒狀。
而且,它的危害也很嚴重,一般會致使 頁面卡頓,甚至形成 OOM。
主要緣由有以下兩點:
這裏咱們假設有這樣一個場景:點擊按鈕使用 handler 發送一個空消息,handler 的 handleMessage 接收到消息後建立內存抖動,即在 for 循環建立 100個容量爲10萬 的 strings 數組並在 30ms 後繼續發送空消息。
通常使用 Memory Profiler (表現爲 頻繁GC、內存曲線呈鋸齒狀)結合代碼排查便可找到內存抖動出現的地方。
一般的技巧就是着重查看 循環或頻繁被調用 的地方。
下面列舉一些致使內存抖動的常見案例,以下所示:
使用 SparseArray類族、ArrayMap 來替代 HashMap。
在開始咱們今天正式的主題以前,咱們先來回歸一下內存泄漏的概念與解決技巧。
所謂的內存泄漏就是 內存中存在已經沒有用的對象。它的 表現 通常爲 內存抖動、可用內存逐漸減小。 它的 危害 即會致使 內存不足、GC頻繁、OOM。
而對於 內存泄漏的分析 通常可簡述爲以下 兩步:
對於MAT來講,其常規的查找內存泄漏的方式能夠細分爲以下三步:
此外,在 Android性能優化以內存優化 還有幾種進階的使用方式,這裏就不一一贅述了,下面,咱們來看看關於 MAT 使用時的一些關鍵細節。
要全面掌握MAT的用法,必需要先了解 隱藏在 MAT 使用中的四大細節,以下所示:
除此以外,MAT 共有 5個關鍵組件 幫助咱們去分析內存方面的問題,分別以下所示:
下面咱們這裏再簡單地回顧一下它們。
若是從GC Root到達對象A的路徑上必須通過對象B,那麼B就是A的支配者。
查看 線程數量 和 線程的 Shallow Heap、Retained Heap、Context Class Loader 與 is Daemon。
經過 圖形 的形式列出 佔用內存比較多的對象。
在下方的 Biggest Objects 還能夠查看其 相對比較詳細的信息,例如 Shallow Heap、Retained Heap。
列出有內存泄漏的地方,點擊 Details 能夠查看其產生內存泄漏的引用鏈。
在介紹圖片監控體系的搭建以前,首先咱們來回顧下 Android Bitmap 內存分配的變化。
將 Bitmap對象 和 像素數據 統一放到 Java Heap 中,即便不調用 recycle,Bitmap 像素數據也會隨着對象一塊兒被回收。
可是,Bitmap 所有放在 Java Heap 中的缺點很明顯,大體有以下兩點:
將圖片內存存放在Native中的步驟有 四步,以下所示:
咱們都知道的是,當 系統內存不足 的時候,LMK 會根據 OOM_adj 開始殺進程,從 後臺、桌面、服務、前臺,直到手機重啓。而且,若是頻繁申請釋放 Java Bitmap 也很容易致使內存抖動。對於這種種問題,咱們該 如何評估內存對應用性能的影響 呢?
對此,咱們能夠主要從如下 兩個方面 進行評估,以下所示:
對於具體的優化策略與手段,咱們能夠從如下 七個方面 來搭建一套 成體系化的圖片優化 / 監控機制。
在項目中,咱們須要 收攏圖片的調用,避免使用 Bitmap.createBitmap、BitmapFactory 相關的接口建立 Bitmap,而應該使用本身的圖片框架。
內存優化首先須要根據 設備環境 來綜合考慮,讓高端設備使用更多的內存,作到 針對設備性能的好壞使用不一樣的內存分配和回收策略。
所以,咱們可使用相似 device-year-class 的策略對設備進行分級,對於低端機用戶能夠關閉複雜的動畫或」重功能「,使用565格式的圖片或更小的緩存內存 等等。
業務開發人員須要 考慮功能是否對低端機開啓,在系統資源不夠時主動去作降級處理。
創建統一的緩存管理組件(參考 ACache),併合理使用 OnTrimMemory / LowMemory 回調,根據系統不一樣的狀態去釋放相應的緩存與內存。
在實現過程當中,須要 解決使用 static LRUCache 來緩存大尺寸 Bitmap 的問題。
而且,在經過實際的測試後,發現 onTrimMemory 的 ComponetnCallbacks2.TRIM_MEMORY_COMPLETE 並不等價於 onLowMemory,所以建議仍然要去監聽 onLowMemory 回調。
一個 空進程 也會佔用 10MB 內存,低端機應該儘量減小使用多進程。
針對低端機用戶能夠推出 4MB 的輕量級版本,現在日頭條極速版、Facebook Lite。
在開發過程當中,若是檢測到不合規的圖片使用(如圖片寬度超過View的寬度甚至屏幕寬度),應該馬上提示圖片所在的Activity和堆棧,讓開發人員更快發現並解決問題。在灰度和線上環境,能夠將異常信息上報到後臺,還能夠計算超寬率(圖片超過屏幕大小所佔圖片總數的比例)。
下面,咱們介紹下如何實現對大圖片的檢測。
繼承 ImageView,重寫實現計算圖片大小。可是侵入性強,而且不通用。
所以,這裏咱們介紹一種更好的方案:ARTHook。
ARTHook,即 掛鉤,用額外的代碼勾住原有的方法,以修改執行邏輯,主要能夠用於如下四個方面:
具體咱們是使用 Epic 來進行 Hook,Epic 是 一個虛擬機層面,以 Java 方法爲粒度的運行時 Hook 框架。簡單來講,它就是 ART 上的 Dexposed,而且它目前 支持 Android 4.0~10.0。
Epic一般的使用步驟爲以下三個步驟:
一、在項目 moudle 的 build.gradle 中添加
compile 'me.weishu:epic:0.6.0'
複製代碼
二、繼承 XC_MethodHook,實現 Hook 方法先後的邏輯。如 監控Java線程的建立和銷燬:
class ThreadMethodHook extends XC_MethodHook{
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Thread t = (Thread) param.thisObject;
Log.i(TAG, "thread:" + t + ", started..");
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Thread t = (Thread) param.thisObject;
Log.i(TAG, "thread:" + t + ", exit..");
}
}
複製代碼
三、注入 Hook 好的方法:
DexposedBridge.findAndHookMethod(Thread.class, "run", new ThreadMethodHook());
複製代碼
知道了 Epic 的基本使用方法以後,咱們即可以利用它來實現大圖片的監控報警了。
以 Awesome-WanAndroid 項目爲例,首先,在 WanAndroidApp 的 onCreate 方法中添加以下代碼:
DexposedBridge.hookAllConstructors(ImageView.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
// 1
DexposedBridge.findAndHookMethod(ImageView.class, "setImageBitmap", Bitmap.class, new ImageHook());
}
});
複製代碼
在註釋1處,咱們 經過調用 DexposedBridge 的 findAndHookMethod 方法找到全部經過 ImageView 的 setImageBitmap 方法設置的切入點,其中最後一個參數 ImageHook 對象是繼承了 XC_MethodHook 類,其目的是爲了 重寫 afterHookedMethod 方法拿到相應的參數進行監控邏輯的判斷。
接下來,咱們來實現咱們的 ImageHook 類,代碼以下所示:
public class ImageHook extends XC_MethodHook {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
// 1
ImageView imageView = (ImageView) param.thisObject;
checkBitmap(imageView,((ImageView) param.thisObject).getDrawable());
}
private static void checkBitmap(Object thiz, Drawable drawable) {
if (drawable instanceof BitmapDrawable && thiz instanceof View) {
final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
if (bitmap != null) {
final View view = (View) thiz;
int width = view.getWidth();
int height = view.getHeight();
if (width > 0 && height > 0) {
// 二、圖標寬高都大於view的2倍以上,則警告
if (bitmap.getWidth() >= (width << 1)
&& bitmap.getHeight() >= (height << 1)) {
warn(bitmap.getWidth(), bitmap.getHeight(), width, height, new RuntimeException("Bitmap size too large"));
}
} else {
// 三、當寬高度等於0時,說明ImageView尚未進行繪製,使用ViewTreeObserver進行大圖檢測的處理。
final Throwable stackTrace = new RuntimeException();
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
int w = view.getWidth();
int h = view.getHeight();
if (w > 0 && h > 0) {
if (bitmap.getWidth() >= (w << 1)
&& bitmap.getHeight() >= (h << 1)) {
warn(bitmap.getWidth(), bitmap.getHeight(), w, h, stackTrace);
}
view.getViewTreeObserver().removeOnPreDrawListener(this);
}
return true;
}
});
}
}
}
}
private static void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight, Throwable t) {
String warnInfo = "Bitmap size too large: " +
"\n real size: (" + bitmapWidth + ',' + bitmapHeight + ')' +
"\n desired size: (" + viewWidth + ',' + viewHeight + ')' +
"\n call stack trace: \n" + Log.getStackTraceString(t) + '\n';
LogHelper.i(warnInfo);
}
}
複製代碼
首先,在註釋1處,咱們重寫了 ImageHook 的 afterHookedMethod 方法,拿到了當前的 ImageView 和要設置的 Bitmap 對象。而後,在註釋2處,若是當前 ImageView 的寬高大於0,咱們便進行大圖檢測的處理:ImageView 的寬高都大於 View 的2倍以上,則警告。接着,在註釋3處,若是當前 ImageView 的寬高等於0,則說明 ImageView 尚未進行繪製,則使用 ImageView 的 ViewTreeObserver 獲取其寬高進行大圖檢測的處理。至此,咱們的大圖檢測檢測組件就已經實現了。若是有小夥伴對 epic 的實現原理感興趣的,能夠查看這篇文章。
首先咱們來了解一下這裏的 重複圖片 所指的概念: 即 Bitmap 像素數據徹底一致,可是有多個不一樣的對象存在。
重複圖片檢測的原理其實就是 使用內存 Hprof 分析工具,自動將重複 Bitmap 的圖片和引用堆棧輸出。
使用很是簡單,只須要修改 Main 類的 main 方法的第一行代碼,以下所示:
// 設置咱們本身 App 中對應的 hprof 文件路徑
String dumpFilePath = "//Users//quchao//Documents//heapdump//memory-40.hprof";
複製代碼
而後,咱們執行 main 方法便可在 //Users//quchao//Documents//heapdump 這個路徑下看到生成的 images 文件夾,裏面保存了項目中檢測出來的重複的圖片。images 目錄以下所示:
注意:須要使用 8.0 如下的機器,由於 8.0 及之後 Bitmap 中的 buffer 已保存在 native 內存之中。
具體的實現能夠細分爲以下三個步驟:
其中,獲取堆棧 的信息也能夠直接使用 haha 庫來進行獲取。這裏簡單說一下 使用 haha 庫獲取堆棧的流程,其具體能夠細分爲八個步驟,以下所示:
爲了創建全局的 Bitmap 監控,咱們必須 對 Bitmap 的分配和回收 進行追蹤。咱們先來看看 Bitmap 有哪些特色:
根據以上特色,咱們能夠創建一套 Bitmap 的高性價比監控組件:
這個方案的 性能消耗很低,能夠在 正式環境 中進行。可是,須要注意的一點是,正式與測試環境須要採用不一樣程度的監控。
要創建線上應用的內存監控體系,咱們須要 先獲取 App 的 DalvikHeap 與 NativeHeap,它們的獲取方式可歸結爲以下四個步驟:
對於監控場景,咱們須要將其劃分爲兩大類,以下所示:
根據 斐波那契數列 每隔一段時間(max:30min)獲取內存的使用狀況。常規內存的監控方法有多種實現方式,下面,咱們按照 項目早期 => 壯大期 => 成熟期 的常規內存監控方式進行 演進式 講解。
具體使用 Debug.dumpHprofData() 實現。
其實現的流程爲以下四個步驟:
可是,這種方式有以下幾個缺點:
在使用 LeakCanary 的時候咱們須要 預設泄漏懷疑點,一旦發現泄漏進行回傳。但這種實現方式缺點比較明顯,以下所示:
定製 LeakCanary 其實就是對 haha組件 來進行 定製。haha庫是 square 出品的一款 自動分析Android堆棧的java庫。這是haha庫的 連接地址。
對於haha庫,它的 基本用法 通常遵循爲以下四個步驟:
File heapDumpFile = ...
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
複製代碼
DataBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
複製代碼
Snapshot snapshot = Snapshot.createSnapshot(buffer);
複製代碼
ClassObj someClass = snapshot.findClass("com.example.SomeClass");
複製代碼
咱們在實現線上版的LeakCanary的時候主要要解決的問題有三個,以下所示:
在實現了線上版的 LeakCanary 以後,就須要 將線上版的 LeakCanary 與服務器和前端頁面結合 起來。具體的 內存泄漏監控閉環流程 以下所示:
此外,在實現 圖片內存監控 的過程當中,應注意 兩個關鍵點,以下所示:
對於低內存的監控,一般有兩種方式,分別以下所示:
爲了準確衡量內存性能,咱們須要引入一系列的內存監控指標,以下所示:
內存 UV 異常率 = PSS 超過 400MB 的 UV / 採集UV
PSS 獲取:調用 Debug.MemoryInfo 的 API 便可
複製代碼
若是出現 新的內存使用不當或內存泄漏 的場景,這個指標會有所 上漲。
內存 UV 觸頂率 = Java 堆佔用超過最大堆限制的 85% 的 UV / 採集UV
複製代碼
計算觸頂率的代碼以下所示:
long javaMax = Runtime.maxMemory();
long javaTotal = Runtime.totalMemory();
long javaUsed = javaTotal - runtime.freeMemory();
float proportion = (float) javaUsed / javaMax;
複製代碼
若是超過 85% 最大堆 的限制,GC 會變得更加 頻發,容易形成 OOM 和 卡頓。
在具體實現的時候,客戶端 儘可能只負責 上報數據,而 指標值的計算 能夠由 後臺 來計算。這樣即可以經過 版本對比 來監控是否有 新增內存問題。所以,創建線上內存監控的完整方案 至少須要包含如下四點:
每一個線程初始化都須要 mmap 必定的棧大小,在默認狀況下初始化一個線程須要 mmap 1MB 左右的內存空間。
在 32bit 的應用中有 4g 的 vmsize,實際能使用的有 3g+,這樣一個進程 最大能建立的線程數 能夠達到 3000個,可是,linux 對每一個進程可建立的線程數也有必定的限制(/proc/pid/limits),而且,不一樣廠商也能修改這個限制,超過該限制就會 OOM。
所以,對線程數量的限制,在必定程度上能夠 有效地避免 OOM 的發生。那麼,實現一套 全局的線程監控組件 即是 刻不容緩 的了。
在線下或灰度的環境下經過一個定時器每隔 10分鐘 dump 出應用全部的線程相關信息,當線程數超過當前閾值時,則將當前的線程信息上報並預警。
經過 Debug.startAllocCounting 來監控 GC 狀況,注意有必定 性能影響。
在 Android 6.0 以前 能夠拿到 內存分配次數和大小以及 GC 次數,其對應的代碼以下所示:
long allocCount = Debug.getGlobalAllocCount();
long allocSize = Debug.getGlobalAllocSize();
long gcCount = Debug.getGlobalGcInvocationCount();
複製代碼
而且,在 Android 6.0 及以後 能夠拿到 更精準 的 GC 信息:
Debug.getRuntimeStat("art.gc.gc-count");
Debug.getRuntimeStat("art.gc.gc-time");
Debug.getRuntimeStat("art.gc.blocking-gc-count");
Debug.getRuntimeStat("art.gc.blocking-gc-time");
複製代碼
對於 GC 信息的排查,咱們通常關注 阻塞式GC的次數和耗時,由於它會 暫停線程,可能致使應用發生 卡頓。建議 僅對重度場景使用。
美團的 Android 內存泄漏自動化鏈路分析組件 Probe 在 OOM 時會生成 Hprof 內存快照,而後,它會經過 單獨進程 對這個 文件 作進一步 分析。
它的缺點比較多,具體爲以下幾點:
在實現自動化鏈路分析組件 Probe 的過程當中主要要解決兩個問題,以下所示:
分析進程佔用的內存 跟 內存快照文件的大小 不成正相關,而跟 內存快照文件的 Instance 數量 呈 正相關。因此在開發過程當中咱們應該 儘量排除不須要的Instance實例。
Prope 的 整體架構圖 以下所示:
而它的整個分析流程具體能夠細分爲八個步驟,以下所示:
解析後的 Snapshot 中的 Heap 有四種類型,具體爲:
解析完 後使用了 計數壓縮策略,對 相同的 Instance 使用 計數,以 減小佔用內存。超過計數閾值的須要計入計數桶(計數桶記錄了 丟棄個數 和 每一個 Instance 的大小)。
若是對象是 基礎數據類型,會將 自身的 RetainSize 累加到父節點 上,將 懷疑對象 替換爲它的 父節點。
使用計數補償策略計算 RetainSize,主要是 判斷對象是否在計數桶中,若是在的話則將 丟棄的個數和大小補償到對象上,累積計算RetainSize,最後對 RetainSize 排序以查找可疑對象。
在配置的時候要注意兩個問題:
一、liballoc-lib.so在構建後工程的 build => intermediates => cmake 目錄下。將對應的 cpu abi 目錄拷貝到新建的 libs 目錄下。
二、在 DumpPrinter Java 庫的 build.gradle 中的 jar 閉包中須要加入如下代碼以識別源碼路徑:
sourceSets.main.java.srcDirs = ['src']
具體的使用步驟以下所示:
12-26 10:54:03.963 30450-30450/com.dodola.alloctrack I/AllocTracker: ====current alloc count 388=====
複製代碼
12-26 10:54:03.963 30450-30450/com.dodola.alloctrack I/AllocTracker: ====current alloc count 388=====
12-26 10:56:45.103 30450-30450/com.dodola.alloctrack I/AllocTracker: saveARTAllocationData write file to /storage/emulated/0/crashDump/1577329005
複製代碼
java -jar tools/DumpPrinter-1.0.jar dump文件路徑 > dump_log.txt
複製代碼
Found 4949 records:
tid=1 byte[] (94208 bytes)
dalvik.system.VMRuntime.newNonMovableArray (Native method)
android.graphics.Bitmap.nativeCreate (Native method)
android.graphics.Bitmap.createBitmap (Bitmap.java:975)
android.graphics.Bitmap.createBitmap (Bitmap.java:946)
android.graphics.Bitmap.createBitmap (Bitmap.java:913)
android.graphics.drawable.RippleDrawable.updateMaskShaderIfNeeded (RippleDrawable.java:776)
android.graphics.drawable.RippleDrawable.drawBackgroundAndRipples (RippleDrawable.java:860)
android.graphics.drawable.RippleDrawable.draw (RippleDrawable.java:700)
android.view.View.getDrawableRenderNode (View.java:17736)
android.view.View.drawBackground (View.java:17660)
android.view.View.draw (View.java:17467)
android.view.View.updateDisplayListIfDirty (View.java:16469)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:3905)
android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:3885)
android.view.View.updateDisplayListIfDirty (View.java:16429)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:3905)
複製代碼
在 Android 8.0 及以後,可使用 Address Sanitizer、Malloc 調試和 Malloc 鉤子 進行 native 內存分析,參見 native_memory
對於線下 Native 內存泄漏監控的創建,主要針對 是否能重編 so 的狀況 來記錄分配的內存信息。
設置內存兜底策略的目的,是爲了 在用戶無感知的狀況下,在接近觸發系統異常前,選擇合適的場景殺死進程並將其重啓,從而使得應用內存佔用回到正常狀況。
一般執行內存兜底策略時至少須要知足六個條件,以下所示:
只有在知足了以上條件以後,咱們纔會去殺死當前主進程並經過 push 進程從新拉起及初始化。
除了在 Android性能優化以內存優化 => 優化內存空間 中講解過的一些常規的內存優化策略之外,在下面列舉了一些更深刻的內存優化策略。
對於 Android 2.x 系統,使用反射將 BitmapFactory.Options 裏面隱藏的 inNativeAlloc 打開。
對於 Android 4.x 系統,使用或借鑑 Fresco 將 bitmap 資源在 native 中分配的方式。
使用 Glide、Fresco 等圖片加載庫,經過定製,在加載 bitmap 時,若發生 OOM,則使用 try catch 將其捕獲,而後清除圖片 cache,嘗試下降 bitmap format(ARGB888八、RGB56五、ARGB444四、ALPHA8)。
須要注意的是,OOM 是能夠捕獲的,只要 OOM 是由 try 語句中的對象聲明所致使的,那麼在 catch 語句中,是能夠釋放掉這些對象,解決 OOM 的問題的。
計算當前應用內存佔最大內存的比例的代碼以下:
max = Runtime.getRuntime().maxMemory();
available = Runtime.getRuntime.totalMemory() - Runtime.getFreeMemory();
ratio = available / max;
複製代碼
顯示地除去應用的 memory,以加速內存收集過程的代碼以下所示:
WindowManagerGlobal.getInstance().startTrimMemory(TRIM_MEMORY_COMPLETE);
複製代碼
當用戶切換到其它應用而且你的應用 UI 再也不可見時,應該釋放應用 UI 所佔用的全部內存資源。這可以顯著增長系統緩存進程的能力,可以提高用戶體驗。
在全部 UI 組件都隱藏的時候會接收到 Activity 的 onTrimMemory() 回調並帶有參數 TRIM_MEMORY_UI_HIDDEN。
在 Activity 的 onDestory 中遞歸釋放其引用到的 Bitmap、DrawingCache 等資源,以下降發生內存泄漏時對應用內存的壓力。
LeakCanary 的 AndroidExcludeRefs 列出了一些因爲系統緣由致使引用沒法釋放的例子,可以使用相似 Hack 的方式去修復。具體的實現代碼能夠參考 Booster => 系統問題修復。
一、國內Top團隊大牛帶你玩轉Android性能分析與優化 第四章 內存優化
四、GMTC-Android內存泄漏自動化鏈路分析組件Probe.key
六、Overview of memory management
九、管理應用的內存
十、《Android移動性能實戰》第二章 內存