1. 程式人生 > >效能優化篇---記憶體管理之Android記憶體洩露

效能優化篇---記憶體管理之Android記憶體洩露

  • 記憶體洩漏:當你不再需要某個例項後,但是這個物件卻仍然被引用,防止被垃圾回收。這個情況就叫做記憶體洩露(Memory Leak)。

常見洩漏場景:

1.Handler 導致的記憶體洩漏

12345678910111213141516171819202122 public class SampleActivity extends AppCompatActivity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg)
{ // ... } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Post a message and delay its execution for 10 minutes. mLeakyHandler.postDelayed(new Runnable() { @Override
public void run() { /* ... */ } }, 1000 * 60 * 1); // Go back to the previous Activity. finish(); }}

原理淺析

當 Android 應用程式啟動時,framework 會為該應用程式的主執行緒建立一個 Looper 物件,負責輪詢從Message Queue取資料。

當在主執行緒中例項化一個 Handler 物件後,會自動與主執行緒 Looper 的訊息佇列關聯起來。當 Activity 已經結束/銷燬,而handmerssage有未處理完的訊息時(

既message持有Message Queue持有 Handler持有當前 Activity 引用)此時就極有可能導致記憶體洩漏。


123456789101112131415 private static class MyHandler extends Handler { private final WeakReference<Sample2Activity> mActivity; public MyHandler(Sample2Activity activity) { mActivity = new WeakReference<Sample2Activity>(activity); } @Override public void handleMessage (Message msg) { Sample2Activity activity = mActivity.get(); if (activity != null) { // ... } } }
  • 方案1:靜態內部類加 WeakRefrence。
  • 方案2:當退出activity時,要注意所在Handler訊息佇列中的Message是否全部處理完成,可以考慮removeCallbacksAndMessages(null)手動關閉

2.靜態變數導致記憶體洩漏

1234567891011121314151617 public class Sample3Activity extends AppCompatActivity{ private static Context sContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sample3); sContext = this; //finish(); Button button = (Button)findViewById(R.id.finish); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { finish(); } }); }}

可能導致記憶體洩漏,原因是:靜態變數持有當前 Activity。導致當前 Activity 結束時候,靜態變數仍然持有它的引用。可以參見[Android靜態變數的生命週期]。建議少引用,或者及時需要手動置空

3.單利模式導致記憶體洩漏,如果用到Context  ,儘量用ApplicationContext

12345678910111213 public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context.getApplicationContext(); } public static AppManager getInstance(Context context) { if (instance != null) { instance = new AppManager(context); } return instance; }}

4.非靜態內部類持有外部類的例項

123456789101112131415 public class Sample4Activity extends AppCompatActivity { private static LeakSample mLeakSample = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mLeakSample == null){ mLeakSample = new LeakSample(); } //... } class LeakSample { //... }}

因為非靜態內部類持有外部類物件的引用。正確的做法為: 將該內部類設為靜態內部類或將該內部類抽取出來封裝成一個單例,如果需要使用Context,請使用ApplicationContext。

5.執行緒造成的記憶體洩漏

Runnable 是一個匿名內部類( AsyncTask 存在匿名內部類的情況),對當前 Activity 都有一個隱式引用。例項程式碼如下:

1234567891011121314151617181920212223 public class Sample4Activity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sample3); leakSample(); finish(); } private void leakSample() { new MyThread().start(); } private class MyThread extends Thread { @Override public void run() { while (true) { SystemClock.sleep(1000); } } }

當你執行耗時任務,在onDestroy()的時候考慮呼叫Thread.close(),如果對執行緒的控制不夠強的話,可以使用RxJava自動建立執行緒池進行控制,並在生命週期結束時取消訂閱;

在使用AsyncTask時,在Activity銷燬時候也應該取消相應的任務AsyncTask.cancel()方法,避免任務在後臺執行浪費資源,進而避免記憶體洩漏的發生。

6.屬性動畫導致記憶體洩漏

屬性動畫中有一類無線迴圈的動畫,要及時取消,否則最終導致 Activity 無法被釋放。動畫的特徵程式碼如下:

1 animator.setRepeatCount(ValueAnimator.INFINITE);

解決辦法自然很簡單,在 OnDestory() 中去取消動畫即可.

7.資源未關閉造成的記憶體洩漏

     對於使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者登出,否則這些資源將不會被回收,造成記憶體洩漏。

其他 :

廣播洩露:手動註冊廣播時,記住退出的時候要unregisterReceiver()第三方SDK/開源框架洩露:ShareSDK, JPush等第三方SDK需要按照文件控制生命週期各種callBack/Listener的洩露,要及時設定為Null,特別是static的callback某些Service也要及時關閉,比如圖片上傳,當上傳成功後,要stopself(),建議用intentservice。Webview需要手動呼叫WebView.onPause()以及WebView.destory()

 多用洩露檢測LeakCanary

開始使用

1.在 build.gradle 中加入引用,不同的編譯使用不同的引用:

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
 }

2.在 Application 中:

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}

詳見LeakCanary 中文使用說明

https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

這樣,就萬事俱備了! 在 debug build 中,如果檢測到某個 activity 有記憶體洩露,LeakCanary 就是自動地顯示一個通知。

,或者MAT僅工具。

參考:

http://www.jianshu.com/p/c59c199ca9fa

http://allenwu.itscoder.com/2016/10/21/allenwu_20161023_oom_in_android_and_solution/