性能的優化是一個老生常談的點,也是一個比較重要的點。做過一點性能優化的工作,現在對工作中的優化點做一個總結。如有錯誤,還請指正。
有哪些方面需要優化
在平時的優化過程中我們需要從哪幾個點來優化呢?其實我們平時自己一定也用過軟體,在使用軟體的過程中有沒有什麼想吐槽的呢?
“這個 app 怎麼還沒下載下傳完!”、“太卡了吧!”、"圖檔怎麼還沒加載出來!"、"怎麼剛進去就卡了!"、“這麼點了一下就退出了!”等等,是不是有這樣的想法?這些想法其實包含了我們今天要說的内容,就是從哪些方面來優化我們的 APP ,我總結了以下幾點。
- APK 瘦身優化
- 啟動速度優化
- 穩定性優化
- 記憶體的優化
- 操作流暢度優化
當然,需要優化的不僅僅是這幾個方面,我做的比較多的優化是這幾個方面,暫時就這幾個方面來談談優化吧。
APK 瘦身
如何檢視 APK 的組成
如果要優化 APK 的大小,我們首先需要知道我們編譯出來的 APK 都包含哪些東西,然後針對占用大的做裁剪,或者删除不需要的東西,進而達到瘦身的目的。
檢視 APK 的内容占用情況很簡單,打開 AS ,把 APK 拖到 AS 裡面就可以檢視 APK 包含的内容了。

可以看到占大頭的是 res 代碼等,是以瘦身可以從這幾個方面來考慮。
如何減少 res 資源大小
- 删除備援的資源
一般随着項目的疊代,部分圖檔等資源不再使用了,但是可能仍然被編譯到了 apk 裡面,是以可以删除這部分不再使用的資源,可以使用 lint 工具來搜尋項目中不再使用的圖檔等資源。
- 重複資源的優化
除了有備援資源,還有些是檔案名不一樣,但是内容一樣的圖檔,可以通過比較 md5 值來判斷是不是一樣的資源,然後編輯 resources.arsc 來重定向。
- 圖檔壓縮
未壓縮的圖檔檔案占用空間較大,可以考慮壓縮未壓縮過的圖檔來瘦身。常用的工具是 tinypng 網站。
同時也可以借助 TinyPngPlugin 等插件或者其他開源工具來幫助壓縮圖檔。
- 資源混淆
通過将資源路徑 res/drawable/wechat 變為 r/d/a 的方式來減少 apk 的大小,當 apk 有較多資源項的時候,效果比較明顯,這是一款微信開源的工具,詳細位址是:AndResGuard
- 指定語言
如果沒有特殊的需求的話,可以隻編譯中文,因為其他的語言用不上,如果用不上的語言編譯了,會在 resource 的表裡面占用大量的空間,故
android {
defaultConfig {
...
// 僅支援 中文
resConfigs "zh"
}
}
如何減少 so 庫資源大小
- 自己編譯的 so
release 包的 so 中移除調試符号。可以使用 Android NDK 中提供的 arm-eabi-strip 工具從原生庫中移除不必要的調試符号。
如果是 cmake 來編譯的話,可以再編輯腳本添加如下代碼
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s")
- 别人編譯的 so
聯系作者修改,一般很難聯系到。
- 動态下發 so
可以通過伺服器下發 so , 下載下傳完後再進入應用,但是體驗不好,但是是一個思路。
- 隻編譯指定平台的 so
一般我們都是給 arm 平台的機器開發,如果沒有特殊情況,我們一般隻需要考慮 arm 平台的。具體的方法是 app 下的 build.gradle 添加如下代碼
android {
defaultConfig {
ndk {
abiFilter "armeabi"
}
}
}
各個平台的差别如下:
平台 | 說明 |
---|---|
armeabi-v7a | arm 第 7 代及以上的處理器,2011 年後的裝置基本都是 |
arm64-v8a | arm 第 8 代 64 位處理器裝置 |
armeabi | arm 第 5、6 代處理器,早期的機器都是這個平台 |
x86 | x86 32 位平台,平闆和模拟器用的多 |
x86_64 | x86 64 位平台 |
如何減少代碼資源大小
- 一個功能盡量用一個庫
比如加載圖檔庫,不要 glide 和 fresco 混用,因為功能是類似的,隻是使用的方法不一樣,用了多個庫來做類似的事情,代碼肯定就變多了。
- 混淆
混淆的話,減少了生成的 class 大小,這樣積少成多,也可以從一定層度減少 apk 的大小。
- R 檔案内聯
通過把 R 檔案裡面的資源内聯到代碼中,進而減少 R 檔案的大小。
可以使用 shrink-r-plugin 工具來做 R 檔案的内聯
參考文檔
Android App包瘦身優化實踐
啟動速度
啟動的類型
一般分為,冷啟動和熱啟動
> 冷啟動:啟動時,背景沒有任何該應用的程序,系統需要重新建立一個程序,并結合啟動參數啟動該應用。
> 熱啟動:啟動時,系統已經有該應用的程序(比如按 home 鍵臨時退出該應用)下啟動該應用。
如何擷取啟動時間
- adb 指令
adb shell am start -S -W 包名/啟動類的全名
adb shell am start -S -W xxx/xxxActivity
Stopping: xxx
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=xxx/xxxActivity }
Status: ok
Activity: xxx/xxxActivity
ThisTime: 770
TotalTime: 770
WaitTime: 848
Complete
ThisTime: 表示最後一個 Activity 啟動時間
TotalTime: 表示啟動過程中,所有的 Activity 的啟動時間
WaitTime: 表示應用程序的建立時間 + TotalTime
一般我們關注 TotalTime 就好了。
另外,谷歌在 Android4.4(API 19)上也提供了測量方法,在 logcat 中過濾 Displayed 字段也可以看到啟動時間
> 2021-04-06 19:25:52.803 2210-2245 I/ActivityManager: Displayed xxx/xxxActivity: +623ms
+623ms 就是Activity 的啟動時間。
- 時間戳
時間戳的方法基于以下的 2 個知識點。
- 應用程序剛建立,會調用 Application 的 onCreate 方法。
- 首次進入一個 Activity 後會在 onResume() 方法後面調用 onWindowsFocusChange 方法。
結合這 2 個特性,我們可以在 A Application 的 onCreate() 方法和 Activity 的 onWindowsFocusChange 方法裡面,通過時間戳來擷取應用的冷啟動時間。
如何監控啟動過程
- systrace
systrace 是一個功能很強大的工具,除了可以檢視卡頓問題,也可以用來檢視應用的啟動問題。使用示例如下:
> python $ANDROID_HOME/platform-tools/systrace/systrace.py gfx view wm am pm ss dalvik app sched -b 90960 -a 你的包名 -o test.log.html
用 Google 浏覽器打開 test.log.html 就可以看到詳細的啟動資訊。
- Debug 接口
package android.os;
...
class Debug {
...
public static void startMethodTracingSampling(String tracePath, int bufferSize, int intervalUs) {
}
public static void startMethodTracing(String tracePath, int bufferSize) {
}
}
利用 Debug 類的這兩個方法,可以生成一個 trace 檔案,這個 trace 檔案,可以直接在 AS 裡面打開,可以看到從 startMethodTracingSampling 到 startMethodTracing 過程中的方法調用等資訊,也可以較好的分析啟動問題。
一般有那些優化方法
- 耗時操作放到異步程序
比如檔案解壓、讀寫等耗時 IO 操作可以新開一個線程來執行。
- 延時初始化
即暫時不适用的工具類等延後到使用的時候再去初始化。比如從 xml 裡面讀取顔色,可以考慮在使用的時候再去讀取和解析。
- 線程優化
線程的建立需要消耗較多的系統系統資源,減少線程的建立。可以考慮共用一個線程池。
如何檢測線程的建立,可以參考我個開源庫 performance
APP 穩定性的次元
app 穩定一般指的是 app 能正常運作, app 不能正常運作的情況分為兩大類,分别是 Crash 和 ANR
> Crash:運作過程中發生的錯誤,是無法避免的。
> ANR:應用再運作時,由于無法再規定的時間段内響應完,系統做出的一個操作。
如何治理 Crash
應用發生 Crash 是由于應用在運作時,應用産生了一個未處理的異常(就是沒有被 try catch 捕獲的異常)。這會導緻 app 無法正常運作。
如果需要解決的話,就需要知道這個未處理的異常是在哪裡産生的,一般是通過分析未處理的異常的方法調用堆棧來解決問題。
Android APP 可以分為 2 層,Java 層和 Native 層。是以如何捕獲需要分開說。
Java 層擷取未處理的異常的調用堆棧
這個需要了解 Java 虛拟機是如何把一個未捕獲的異常報上來的。
未捕獲的異常,會沿着方法的調用鍊依次上抛,直到 ThreadGroup 的 uncaughtException 方法
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
// 遞歸調用,可以忽略
parent.uncaughtException(t, e);
} else {
// 交給了 Thread.getDefaultUncaughtExceptionHandler() 來處理未捕獲的異常
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
查閱代碼發現,發現 ThreadGroup 最終會給 Thread 的 defaultUncaughtExceptionHandler 處理。
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
上面的代碼顯示:Thread 的 defaultUncaughtExceptionHandler 是 Thread 類的一個靜态變量。
看到這裡,如何捕獲 Java 層未處理的異常就很清晰了,給 Thread 設定一個新的 defaultUncaughtExceptionHandler,在這個新的 defaultUncaughtExceptionHandler 裡面收集需要的資訊就可以了。
需要注意的一點是 舊的 defaultUncaughtExceptionHandler 需要儲存下來,然後新的 defaultUncaughtExceptionHandler 收集資訊後,需要再轉給舊的 defaultUncaughtExceptionHandler 繼續處理。
Native 層擷取未處理的異常的相關資訊
Java 層如何收集未處理的異常的資訊說過了,我們來看看 Native 層發生未處理的異常的話,是如何處理的。 Native 層的處理,需要掌握 linux 的一些知識,由于本人不是特别了解 linux ,這裡就直接參考别人的文章了。如果有錯誤,還請指正。
本人通過查閱資料發現,Native 層如果發生未處理的異常(注:如果 Native 層捕獲了異常,是可以通過 JNI 抛到 Java 層去處理的) ,系統會發出信号給 Native 層,在 Native 層如果要收集未處理的異常資訊,就需要注冊對應信号的處理函數。當發生異常的時候,Native 層會收到資訊,然後通過處理器來收集資訊。
注冊信号處理函數如下:
#includeint sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
- signum:代表信号編碼,可以是除SIGKILL及SIGSTOP外的任何一個特定有效的信号,如果為這兩個信号定義自己的處理函數,将導緻信号安裝錯誤。
- act:指向結構體sigaction的一個執行個體的指針,該執行個體指定了對特定信号的處理,如果設定為空,程序會執行預設處理。
- oldact:和參數act類似,隻不過儲存的是原來對相應信号的處理,也可設定為NULL。
有了信号處理函數,後面還要做的事情就是收集資訊了,由于本人不是很熟悉 Native 的開發,這裡就不展開說了了,大家可以參考 Android 平台 Native 代碼的崩潰捕獲機制及實作。
如何治理 ANR
ANR 是 Applicatipon No Response 的簡稱。如果應用卡死或者響應過慢,系統就會殺死應用。為什麼要殺死應用?其實也很好了解,如果不殺死應用,大家會以為系統壞了。
那我們如何監控 ANR 呢?以及我們如何分析 ANR 的問題呢?常見的導緻 ANR 的原因有哪些呢?
首先,ANR 的原理是 AMS 在 UI 操作開始的時候,會根據 UI 操作的類型開啟一個延時任務,如果這個任務被觸發了,就表示應用卡死或者響應過慢。這個任務會在 UI 操作結束的時候被移除。
然後,如何分析 ANR 問題呢?
一般 ANR 發生的時候, logcat 裡面會列印 ANR 相關的資訊,過濾關鍵字 ANR 就可以看到,這裡不做詳細分析,可以參考後面的文章。
然後一般會在 /data/anr 目錄下面生成 traces.txt 檔案,裡面一般包含了 ANR 發生的時候,系統和所有應用的線程等資訊(需要注意的是,不同的 rom 可能都不一樣),通過 logcat 列印的資訊和 traces.txt 裡面的資訊,大部分的 ANR 可以分析出原因,但是呢,也有相當一部分的 ANR 問題無法分析,因為 logcat 和 traces.txt 提供的資訊有限,有時候甚至沒有特别有用的資訊,特别是 Android 的權限收緊, traces.txt 檔案在高 Android 版本無法讀取,給 ANR 問題的分析增加了不少的困難。不過好在最近發現頭條給 ANR 寫了一個系列的文章,裡面對 ANR 問題的治理方法,個人覺得很好,這裡引用一下。
- 今日頭條 ANR 優化實踐系列 - 設計原理及影響因素
- 今日頭條 ANR 優化實踐系列 - 監控工具與分析思路
- 今日頭條 ANR 優化實踐系列分享 - 執行個體剖析集錦
- 今日頭條 ANR 優化實踐系列 - Barrier 導緻主線程假死
本人之前寫過一個小的性能監測的工具,其中有監控 UI 線程 Block 的功能,考慮後續加入頭條的 ANR 監測機制,等後續完成了,在做一個詳細的總結吧。這次的總結就寫到這裡。
硬體的記憶體總是有限的,所有每個應用分到的記憶體也是有限的,所有記憶體的優化很有必要,否則應用就沒有足夠的記憶體使用了,這個時候就會 Crash 。
記憶體都消耗在哪裡了
優化記憶體的話,需要了解記憶體在哪裡消耗了了,針對記憶體消耗大的場景做優化,對症下藥,才可以有一個好的優化效果。
Android Studio 裡面的 Profiler 工具是一個很好用的工具,通過裡面的 memory 工具可以實時監控 APP 運作過程中的記憶體配置設定。
dump APP 記憶體堆棧後,還可以看到各個類占用的記憶體情況。
可以檢視每個對象的詳細資訊。
Android Studio 裡面的 Profiler 工具的具體使用教程請參考官方教程,這裡就不做詳細介紹了。
如何合理使用記憶體
利用上面的方法,找到記憶體消耗大的場景,就需要做優化了,主要做法就是想辦法減少特定場景下的記憶體的使用。個人總結了一下平時可能會做的優化。
- 圖檔相關的優化
圖檔是我目前做的應用裡面占用記憶體比較大的一塊了,也碰到了一些問題,我主要是通過以下的方法來做優化。
- 暫時用不上的圖檔不加載,比如說,有個網絡加載異常的圖,不要一開始就初始化,等到真的有異常了需要展示的時候再初始化
- 加載圖檔的時候,盡量加載指定大小的圖檔,因為有時候會碰到控件的大小小于實際圖檔尺寸的情況,這個時候,會浪費一些記憶體。有需要的話,可以讓背景傳回不同尺寸的圖檔。
- 根據不同的圖檔格式
- 不顯示的圖檔,可以考慮先釋放。
- 盡可能少地建立對象
毫無疑問,如果對象少,記憶體肯定也消耗的少,那平時需要注意哪些呢?
- 自定義 view 的時候,不要在 onDraw 方法裡面頻繁建立對象。因為 onDraw 方法可能會頻繁調用,這個時候就會建立大量的對象。進而造成浪費,同時也會導緻 gc 觸發的頻率升高,造成卡頓。
- 盡量少建立線程,建立線程其實是比較消耗資源的,建立一個空的線程,大概會占用 1-2 M 記憶體。同時一般異步任務很快就會執行完,如果頻繁建立線程來做異步任務,除了記憶體使用的多,還可能 GC 造成卡頓。執行異步任務的話,一般建議用線程池來執行,但是需要注意線程池的使用。
- 盡量用 StringBuilder 或者 StringBuffer 來拼接字元串。平時發現的問題主要是在列印 logcat 的時候和拼接背景傳回的資料的時候會建立大量的 String,是以如果有類似的情況也可以考慮做一些優化。
記憶體洩漏是什麼
記憶體洩漏指的是本應該釋放的記憶體,由于一些原因,被 GC ROOT 對象持有,進而而無法在 GC 的時候釋放,這樣可能會導緻的一個問題就是,重複操作以後,APP 沒有足有的記憶體使用了,這個時候系統會殺死 APP 。是以記憶體洩漏是需要排查的。
如何監控和分析記憶體洩漏問題
上一個小結總結了上面是記憶體洩漏,是因為某些 GC ROOT 對象持有了期望釋放的對象,導緻期望釋放的記憶體無法及時釋放。是以如何監控和分析記憶體洩漏問題就成了如何找到 GC ROOT 的問題。
一般手動分析的步驟是:重複操作懷疑有記憶體洩漏的場景,然後觸發幾次 GC 。等幾秒鐘後,把 APP 的記憶體堆棧 dump 下來(可以使用 as 的工具 dump),然後用 sdk 裡面的 cover 工具轉換一下,然後用 MAT 工具來分析記憶體洩漏的對象到 GC ROOT 的引用鍊。
手動分析總是很麻煩的,一個好消息是,有一個特别好用的自動監控和分析記憶體洩漏的工具,這個工具就是 leakcanary ,它可以自動監控并給出記憶體洩漏的對象到 GC ROOT 的引用鍊。
使用很簡單,隻需要在 APP 的 build.gradle 下面新增
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}
leakcanary 比較核心的一個原理就是利用了弱引用的一個特性,這個特性就是:
> 在建立弱引用的時候,可以指定一個 RefrenceQueue ,當弱引用引用的對象的可達性發生變化的時候,系統會把這個弱引用引用的對象放到之前指定的 RefrenceQueue 中等待處理。
是以 GC 後,引用對象仍然沒有出現在 RefrenceQueue 的時候,說明可能發生了記憶體洩漏,這個時候 leakcanary 就會 dump 應用的 heap ,然後用 shark 庫分析 heap ,找出一個到 GC ROOT 的最短引用鍊并提示。
常見的記憶體洩漏的場景
個人總結了下工作中碰到記憶體洩漏的一些場景,現記錄下來,大家可以參考下。
- 靜态變量持有 Context 等。
- 單例執行個體持有 Context 等。
- 一些回調沒有反注冊,比如廣播的注冊和反注冊等,有時候一些第三方庫也需要注意。
- 一些 Listener 沒有手動斷開連接配接。
- 匿名内部類持有外部類的執行個體。比如 Handler , Runnable 等常見的用匿名内部類的實作,常常會不小心持有 Context 等外部類執行個體。
為什麼會卡頓
為什麼卡頓之前,我們先需要簡單了解一點硬體相關的知識。就是在界面繪制的過程中, CPU 主要的任務是計算出螢幕上所有 View 對應的圖形和向量等資訊。 GPU 的主要任務就是把 CPU 計算出的圖形栅格化并轉化為位圖,可以簡單了解為螢幕像素點對應的值。
如果操作過程中卡頓了,一般就是 CPU 和 GPU 其中的一個或者多個無法短時間完成對應的任務。
一般而言,CPU 除了需要計算 View 對應的圖形和向量等資訊,還要做邏輯運算和檔案讀寫等任務,是以 CPU 造成卡頓更常見。一般也是通過減少 CPU 的計算任務來優化卡頓。
影響 CPU 的使用率一般有以下幾個方面:
- 讀寫檔案
- 解析大量圖檔
- 頻繁請求網絡
- 複雜的布局
- 頻繁建立對象
如何檢測卡頓
雖然我們知道了大概哪些原因會導緻卡頓,但是我們無法準确定位出問題的代碼點在哪裡,針對上面的部分問題,本人寫了一個開源庫來自動檢測,這個開源庫的位址是
> https://github.com/XanderWang/performance
詳細的原理,可以參考上面的連接配接,這裡簡單總結下監控 UI 卡段的原理。
我們知道,Android 裡面,界面的重新整理需要再主線程或者說 UI 線程執行。而界面的繪制起始點又利用了 Looper 消息循環機制。Looper 消息循環機制有一個有意思的特點,就是 Looper 在 dispatch Message 的時候,會在 dispatch 前和 dispatch 後利用 Printer 列印特定 tag 的字元串,通過接管 Printer ,我們就可以擷取 dispatch message 前後的時機。
然後我們可以在 dispatch message 之前,在異步線程啟動一個抓取系統資訊的延時任務。在 dispatch message 之後,我們可以移除異步線程的這個延時任務。如果某個消息的執行沒有超過門檻值,那就表示在異步線程的延時任務被取消,表明沒有卡頓。如果某個消息的執行時間超過了門檻值,那異步線程裡的延時任務就會執行,表明有卡頓,異步線程的延時任務會擷取此時的系統狀态,進而輔助我們分析卡頓問題。
如何優化卡頓
如何檢測說完了,我們來說說如何優化。在 為什麼會卡頓 小結我總結了幾種常見,現在對幾種場景的優化總結下。
最常見的一個讀寫檔案而不自知的就是 SharePerfrences 的使用,使用 sp 的時候需要注意不要頻繁調用 apply 或者 commit 方法,因為每調用一次就有可能會有一次寫檔案操作(高版本系統做了優化 apply 做了優化,不一定會寫檔案)。是以,如果調用次數多的話,就會多次寫檔案,寫檔案又是一個耗時且耗資源的操作,是以要少做。
一般優化方法是合理拆分 sp 檔案,一個 sp 檔案不要包含太多的項,同時每一項的内容盡量短。盡量批量送出資料後再 commit 或者 apply 。同時需要注意的是 commit 會直接觸發寫檔案(内容有變化的時候),是以如果在 UI 線程調用 commit 方法需要注意可能會阻塞 UI 線程。
如果有更高的性能需求,可以考慮用 mmkv 來替換或者 DataStore 來替換 sp 。具體的替換方法就不細說了。網上有很多資料參考。
另外一個常見的讀寫檔案的場景是從 xml 檔案裡面讀取布局、色值等操作,這些都是一些 io 操作。從 xml 讀取布局的話,可以考慮用代碼直接建立 view 來優化,從 xml 裡面讀取顔色可以考慮加個 HashMap 來優化。
解碼圖檔毫無疑問是一個計算量大的操作,是以一般加載圖檔的時候最好根據實際顯示的尺寸做壓縮,并且儲存壓縮後的縮略圖,友善下次直接加載。
另外還需要注意清單滾動過程中,控制對圖檔的加載,一般清單在滑動過程中,不加載圖檔,等清單滾動停止後,才開始加載圖檔。
另外的一個優化的方法就是減少圖檔的使用,不過這個難度有點大。
另外還可以考慮針對不同的圖檔格式,用不同的解碼格式。比如 png 格式的圖檔根據機器實際情況選擇 8888 或者 4444 解碼方式解碼圖檔。如果是 jpg/jpeg 格式的圖檔,就用 565 的解碼方式解碼圖檔。對于用不同的解碼方式解碼圖檔,效率是否會高,本人沒做過測試,但是毫無疑問,記憶體的使用是不同的。
網絡請求的話,可以參考下面的優化方法。
- 如果使用 okhttp 請求網絡的話,盡量全局使用一個 httpclient ,這樣做的好處是可以複用,提高網絡請求效率。
- 背景支援的話,開啟 gzip 壓縮,這樣網絡傳輸的資料量小些,傳輸效率會高些。
- 自定義 dns ,減少解析 dns 的時間。
- 通過和背景商量,部分資料背景接口一步到位,盡量避免多次請求後才拿到完整的目标資料。
如果布局複雜的話, CPU 要進行大量的計算才可以确定最終的圖形。是以布局複雜的話,CPU 需要大量的運算資源,是以優化複雜的布局是很有必要的。
- 減少布局層次,可以利用 ViewStub 、merge 和 include 等标簽來嘗試減少布局層次。
- 使用高效的布局容器,比如 ConstraintLayout,可以不用嵌套布局容器來實作複雜效果。
- 部分效果可以考慮用自定義 View 實作。
這個優化感覺不是特别好做,可能優化了,但是效果不好,但是又不能不做。
為什麼這個要列出來呢?因為頻繁建立對象,可能會短時間内消耗大量記憶體,然後記憶體不足的時候系統就會嘗試 GC 來回收對象,而 GC 是很耗資源的操作,雖然現在 Android 系統對 GC 做了很多優化,但是盡量減少 GC 的觸發總是好的。
一般頻繁建立對象的場景有:
- 自定義 View 的時候,在 onDraw 方法建立臨時對象
- 循環裡面使用 "+" 拼接字元串
- ArrayList 等有容積限制的容器類初始化的容量不合理,導緻後續新增資料頻繁擴容。
小結