1. 程式人生 > >Android 記憶體洩漏問題

Android 記憶體洩漏問題

1. 內部類引用導致Activity的洩露

    在Android中內部類的引用最常見的是handler,我們經常會這樣寫:

private Handler handler = new Handler(){
    @Override
    public void handlerMessage(Message msg){
    }
}

   但這樣寫會造成嚴重的記憶體洩漏,編寫中會有如下警告:意思是,Handler應該是static型別的,否則可能會造成記憶體洩漏。

This Handler class should be static or leaks might occur(com.example.progress.MainActivity.)

   警告的原因是:Android應用層是java編寫的 ,在java中,“非靜態類”或“匿名類”都是持有他們所在外部類的一個引用。也就是說這裡會持有一個activity的引用。而Handler在Android中是用來做訊息通訊的,比如 介面更新 網路操作 都可能通過handler來進行訊息傳遞。

public class MainActivity extends Activity {
 
  private final Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    //釋出訊息並將其執行延遲
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60*10 );
 
    finish();
  }
}

    當activity 完成finish()時,延時訊息在得到處理前,會一直儲存在主執行緒的訊息佇列裡持續10分鐘。由上文可知handler隱式的持有MainActivity的引用。這條引用關係會一直保持,直到訊息得到處理,從而阻止了MainActivity被垃圾回收器回收,造成了應用程式的洩漏。

  • 只要有未處理訊息,訊息就會引用handler,非靜態handler會引用外部類,即Activity,導致Activity無法被回收,造成洩漏;
  • Handler類屬於非靜態匿名類,同樣會引用外部類。

解決方法有2種:

(1)靜態內部類不會持有外部類的引用,所以,我們可以把handler類放在單獨的類檔案中,或者使用靜態內部類避免洩漏。

(2)如果想要在handler內部去呼叫所在的外部類Activity,可以在handler內部使用弱引用的方式指向所在Activity,這樣統一不會導致記憶體洩漏。(弱引用的解決方式,請看:https://blog.csdn.net/weixin_38327420/article/details/83098174

public class MainActivity extends Activity {
 
  /**
   * 靜態內部類的例項不包含對其外部類的隱式引用
   */
  private static class MyHandler extends Handler {
    private final WeakReference<MainActivity> mActivity;
 
    public MyHandler(MainActivity activity) {
      mActivity = new WeakReference<MainActivity>(activity);
    }
 
    @Override
    public void handleMessage(Message msg) {
      SampleActivity activity = mActivity.get();
      if (activity != null) {
        // ...
      }
    }
  }
 
  private final MyHandler mHandler = new MyHandler(this);
 
  /**
   * 靜態內部類不會對其外部類進行隱式引用
   */
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { /* ... */ }
  };
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
     finish();
  }
}

    注意:在Android開發中,如果一個內部類例項的生命週期比Activity更長,那麼我們千萬不要使用非靜態的內部類。最好是,使用靜態內部類,然後在該類裡使用弱引用來指向所在的Activity。

2. 單例模式造成的記憶體洩漏

這是一種簡單的單例模式,先不評論是否執行緒安全,關鍵是傳入的context引數。

public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context;
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}
  • 若傳入的是Application的Context:將沒有什麼問題,因為單例的生命週期和Application的一樣長 ;
  • 若傳入的是Activity的Context:當這個Context所對應的Activity退出時,由於該Context和Activity的生命週期一樣長(Activity間接繼承於Context),所以當前Activity退出時它的記憶體並不會被回收,因為單例物件持有該Activity的引用。這樣就造成了記憶體洩漏的問題。
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;
    }
}

  解決方法是:傳入applicationContext,使得單例的生命週期和應用的生命週期一樣長。

3. 非靜態內部類造成的記憶體洩漏

   這個和第一種Handler造成的記憶體洩漏有點類似,因為非靜態內部類預設會持有外部類的引用,而又使用了該非靜態內部類建立了一個靜態的例項,該例項的生命週期和應用一樣長,這就導致了該靜態例項一直會持有該Activity的引用,導致Activity的記憶體資源不能正常回收。

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

        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                SystemClock.sleep(10000);
                return null;
            }
        }.execute();
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(10000);
            }
        }).start();

   如果上面的非同步執行緒和Thread都在Activity中執行,且執行的是耗時任務,若Activity被finish,但是Thread和非同步任務沒有執行完成,還持有外部Activity的隱式應用,這就導致activity不能被回收,造成記憶體洩漏。

static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        private WeakReference<Context> weakReference;
  
        public MyAsyncTask(Context context) {
            weakReference = new WeakReference<>(context);
        }
  
        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(10000);
            return null;
        }
  
        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            MainActivity activity = (MainActivity) weakReference.get();
            if (activity != null) {
                //...
            }
        }
    }
    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            SystemClock.sleep(10000);
        }
    }

    new Thread(new MyRunnable()).start();
    new MyAsyncTask(this).execute();

  解決辦法:不在Activity中執行耗時操作,可以避免Activity的記憶體資源洩漏。

  注意:在Activity銷燬時,應該取消相應的任務 AsyncTask::cancel(),避免任務在後臺執行浪費資源。

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

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

https://blog.csdn.net/lingjianglin/article/details/52232220