記憶體洩漏大集結:安卓開發者不可錯過的效能優化技巧
theme: cyanosis
當我們開發安卓應用時,效能優化是非常重要的一個方面。一方面,優化可以提高應用的響應速度、降低卡頓率,從而提升使用者體驗;另一方面,優化也可以減少應用的資源佔用,提高應用的穩定性和安全性,降低應用被殺死的概率,從而提高使用者的滿意度和留存率。
但是,對於許多開發者來說,安卓效能優化往往是一個比較棘手的問題。由於安卓裝置的種類繁多,硬體配置各不相同,因此優化的方法和策略也各不相同。同時,安卓應用的開發週期較長,往往需要不斷地迭代和更新,因此優化也需要不斷地持續和優化。
因此,學習安卓效能優化的知識和技巧,是每個安卓開發者必備的技能之一。通過掌握安卓效能優化的基本原理和方法,我們可以更加深入地瞭解安卓裝置的工作機制,理解應用的效能瓶頸,從而採取有效的優化策略和措施,提高應用的效能和穩定性,提升使用者的滿意度和留存率。
因此,本系列部落格旨在介紹安卓效能優化的基本原理、優化策略和實踐技巧,幫助開發者更好地瞭解安卓裝置的工作原理,掌握安卓效能優化的基本方法和技巧,從而提高應用的效能和穩定性,為使用者提供更加優質的使用體驗
安卓開發的效能優化問題非常廣泛,以下是其中一些常見的問題:
- 記憶體洩漏:當應用程式不正確地管理記憶體時,會發生記憶體洩漏,導致記憶體佔用過高,甚至導致應用程式崩潰。
- 佈局優化:佈局是應用程式中最常見的效能瓶頸之一,因為過於複雜的佈局會導致應用程式響應緩慢或卡頓。
- 圖片優化:圖片是應用程式中佔用記憶體最多的資源之一,因此必須謹慎使用,並對其進行適當的壓縮和快取,以確保應用程式的效能。
- 網路請求優化:網路請求可以在應用程式中佔用大量的時間和資源,因此必須對其進行優化,以減少請求次數和提高響應速度。
- 資料庫優化:當應用程式需要大量訪問資料庫時,可能會導致效能問題。通過優化資料庫設計和使用適當的資料庫快取,可以提高應用程式的效能。
- 多執行緒優化:多執行緒可以提高應用程式的效能,但如果不正確地使用它們,則可能導致死鎖、執行緒競爭和其他問題。
- 記憶體優化:記憶體是應用程式效能的重要因素之一。通過及時釋放不再需要的記憶體和避免不必要的記憶體分配,可以提高應用程式的效能。
- 程式碼優化:優化程式碼結構和演算法可以提高應用程式的效能。例如,使用更快速和有效的資料結構和演算法來提高應用程式的響應速度。
- 安全性優化:安全問題也可能對應用程式的效能產生負面影響。通過避免不安全的程式碼實踐和使用加密技術來保護資料,可以提高應用程式的安全性和效能。
我在效能優化 中記錄過幾篇文章,也不多,都是從遇到問題的角度出發寫的,今天就從上述的常規優化開始,一個一個分析記錄一下。
為什麼說是常規優化呢?因為Android的效能優化歸結到底就是記憶體問題,而記憶體層次的優化,不僅是描述中的這些常規優化項,還可以進行磁碟讀寫次數、磁碟頁資料同步等進一步的優化,而這些優化在這裡是不討論的。
記憶體洩漏
記憶體洩漏是指應用程式在執行過程中,無法正確地釋放已經不再使用的記憶體資源,導致記憶體佔用不斷增加,最終導致應用程式崩潰或執行緩慢。
記憶體洩漏的原理
安卓記憶體洩漏的原理是指應用程式在使用記憶體時,由於程式設計問題或者錯誤,導致無法釋放不再使用的記憶體,最終導致系統中的記憶體不足,影響系統的穩定性和效能。
以下是一些可能導致安卓記憶體洩漏的常見原因:
- 物件引用未釋放:當物件被建立時,如果沒有被正確釋放,那麼這些物件就會一直佔用記憶體,直到應用程式退出。例如,當一個Activity被銷燬時,如果它還持有其他物件的引用,那麼這些物件就無法被垃圾回收器回收,從而導致記憶體洩漏。
如果存在記憶體洩漏,那麼這些記憶體中的物件就會被引用,無法被垃圾回收機制回收,這時我們需要通過GCRoot來識別記憶體洩漏的物件和引用。
GCRoot是垃圾回收機制中的根節點,根節點包括虛擬機器棧、本地方法棧、方法區中的類靜態屬性引用、活動執行緒等,這些物件被垃圾回收機制視為“活著的物件”,不會被回收。
當垃圾回收機制執行時,它會從GCRoot出發,遍歷所有的物件引用,並標記所有活著的物件,未被標記的物件即為垃圾物件,將會被回收。
當存在記憶體洩漏時,垃圾回收機制無法回收一些已經不再使用的物件,這些物件仍然被引用,形成了一些GCRoot到記憶體洩漏物件的引用鏈,這些物件將無法被回收,導致記憶體洩漏。
通過查詢記憶體洩漏物件和GCRoot之間的引用鏈,可以定位到記憶體洩漏的根源,進而解決記憶體洩漏問題,LeakCancry就是通過這個機制實現的。 一些常見的GCRoot包括: - 虛擬機器棧(Local Variable)中引用的物件。 - 方法區中靜態屬性(Static Variable)引用的物件。 - JNI 引用的物件。 - Java 執行緒(Thread)引用的物件。 - Java 中的 synchronized 鎖持有的物件。
- 匿名內部類造成的記憶體洩漏:匿名內部類通常會持有外部類的引用,如果外部類的生命週期比匿名內部類長,那麼就會導致外部類無法被回收,從而導致記憶體洩漏。
- 靜態變數持有Activity或Context的引用:如果一個靜態變數持有Activity或Context的引用,那麼這些Activity或Context就無法被垃圾回收器回收,從而導致記憶體洩漏。
- 未關閉的Cursor、Stream或者Bitmap物件:如果程式在使用Cursor、Stream或者Bitmap物件時沒有正確關閉這些物件,那麼這些物件就會一直佔用記憶體,從而導致記憶體洩漏。
- 資源未釋放:如果程式在使用系統資源時沒有正確釋放這些資源,例如未關閉資料庫連線、未釋放音訊資源等,那麼這些資源就會一直佔用記憶體,從而導致記憶體洩漏。
常見的記憶體洩漏
靜態引用導致的記憶體洩漏
當一個物件被一個靜態變數持有時,即使這個物件已經不再使用,也不會被垃圾回收器回收,這就會導致記憶體洩漏 ```java public class MySingleton { private static MySingleton instance; private Context context;
private MySingleton(Context context) {
this.context = context;
}
public static MySingleton getInstance(Context context) {
if (instance == null) {
instance = new MySingleton(context);
}
return instance;
}
} ```
上面的程式碼中,MySingleton持有了一個Context物件的引用,而MySingleton是一個靜態變數,導致即使這個物件已經不再使用,也不會被垃圾回收器回收。
注意事項:如果需要使用靜態變數,請注意在不需要時將其設定為null,以便及時釋放記憶體。
匿名內部類導致的記憶體洩漏
匿名內部類會隱式地持有外部類的引用,如果這個匿名內部類被持有了,就會導致外部類無法被垃圾回收。
``` public class MyActivity extends Activity { private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
button = new Button(this);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// do something
}
});
setContentView(button);
}
} ``` 匿名內部類OnClickListener持有了外部類MyActivity的引用,如果MyActivity被銷燬之前,button沒有被清除,就會導致MyActivity無法被垃圾回收。
注意事項:在Activity銷燬時,應該將所有持有Activity引用的物件設定為null。
Handler引起的記憶體洩漏
Handler是在Android應用程式中常用的一種執行緒通訊機制,如果Handler被錯誤地使用,就會導致記憶體洩漏。 ```java public class MyActivity extends Activity { private static final int MSG_WHAT = 1; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_WHAT: // do something break; default: super.handleMessage(msg); } } };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendEmptyMessageDelayed(MSG_WHAT, 1000 * 60 * 5);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 在Activity銷燬時,應該將Handler的訊息佇列清空,以避免記憶體洩漏。
mHandler.removeCallbacksAndMessages(null);
}
} ``` Handler持有了Activity的引用,如果Activity被銷燬之前,Handler的訊息佇列中還有未處理的訊息,就會導致Activity無法被垃圾回收。
注意事項:在Activity銷燬時,應該將Handler的訊息佇列清空,以避免記憶體洩漏。
Bitmap物件導致的記憶體洩漏
當一個Bitmap物件被建立時,它會佔用大量記憶體,如果不及時釋放,就會導致記憶體洩漏。
``` public class MyActivity extends Activity { private Bitmap mBitmap;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
// 載入一張大圖
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.big_image);
}
@Override protected void onDestroy() { super.onDestroy(); // 釋放Bitmap物件 mBitmap.recycle(); mBitmap = null; } } ```
當Activity被銷燬時,Bitmap物件mBitmap應該被及時釋放,否則就會導致記憶體洩漏。
注意事項:當使用大量Bitmap物件時,應該及時回收不再使用的物件,避免記憶體洩漏。另外,可以考慮使用圖片載入庫來管理Bitmap物件,例如Glide、Picasso等。
資源未關閉導致的記憶體洩漏
當使用一些系統資源時,例如檔案、資料庫等,如果不及時關閉,就可能導致記憶體洩漏。例如:
public void readFile(String filePath) throws IOException {
FileInputStream fis = null;
try {
fis = new FileInputStream(filePath);
// 讀取檔案...
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
上面的程式碼中,如果在讀取檔案之後沒有及時關閉FileInputStream物件,就可能導致記憶體洩漏。
注意事項:在使用一些系統資源時,例如檔案、資料庫等,要及時關閉相關物件,避免記憶體洩漏。
避免記憶體洩漏需要在編寫程式碼時時刻注意,及時清理不再使用的物件,確保記憶體資源得到及時釋放。 ,同時,可以使用一些工具來檢測記憶體洩漏問題,例如Android Profiler、LeakCanary等。
WebView 記憶體洩漏
當使用WebView時,如果不及時釋放,就可能導致記憶體洩漏
``` public class MyActivity extends Activity { private WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = findViewById(R.id.webview);
mWebView.loadUrl("https://www.example.com");
}
@Override
protected void onDestroy() {
super.onDestroy();
// 釋放WebView物件
if (mWebView != null) {
mWebView.stopLoading();
mWebView.clearHistory();
mWebView.clearCache(true);
mWebView.loadUrl("about:blank");
mWebView.onPause();
mWebView.removeAllViews();
mWebView.destroy();
mWebView = null;
}
}
} ``` 上面的程式碼中,當Activity銷燬時,WebView物件應該被及時釋放,否則就可能導致記憶體洩漏。
注意事項:在使用WebView時,要及時釋放WebView物件,可以在Activity銷燬時呼叫WebView的destroy方法,同時也要清除WebView的歷史記錄、快取等內容,以確保釋放所有資源。
監測工具
- 記憶體監視工具:Android Studio提供了記憶體監視工具,可以在開發過程中實時監視應用程式的記憶體使用情況,幫助開發者及時發現記憶體洩漏問題。
- DDMS:Android SDK中的DDMS工具可以監視Android裝置或模擬器的程序和執行緒,包括記憶體使用情況、堆疊跟蹤等資訊,可以用來診斷記憶體洩漏問題。
- MAT:MAT(Memory Analyzer Tool)是一款基於Eclipse的記憶體分析工具,可以分析應用程式的堆記憶體使用情況,識別和定位記憶體洩漏問題。
- 騰訊的Matrix,也是非常好的一個開源專案,推薦大家使用
總結
記憶體洩漏是指程式中的某些物件或資源沒有被妥善地釋放,從而導致記憶體佔用不斷增加,最終可能導致應用程式崩潰或系統執行緩慢等問題。
常見的記憶體洩漏問題包括:
- 長時間持有Activity或Fragment物件導致的記憶體洩漏;
- 匿名內部類和非靜態內部類導致的記憶體洩漏;
- WebView持有Activity物件導致的記憶體洩漏;
- 單例模式持有資源物件導致的記憶體洩漏;
- 資源未關閉導致的記憶體洩漏;
- 靜態變數持有Context物件導致的記憶體洩漏;
- Handler持有外部類引用導致的記憶體洩漏;
- Bitmap佔用大量記憶體導致的記憶體洩漏;
- 單例持有大量資料導致的記憶體洩漏。
為避免記憶體洩漏問題,我們可以採取以下措施:
- 及時釋放Activity或Fragment物件;
- 避免匿名內部類和非靜態內部類;
- 在使用WebView時,及時呼叫destroy方法;
- 在單例模式中避免長時間持有資源物件;
- 及時關閉資源物件;
- 避免靜態變數持有Context物件;
- 避免Handler持有外部類引用;
- 在使用Bitmap時,及時釋放記憶體;
- 避免單例持有大量資料。
歡迎補充討論