Android 內存優化篇——夜用大片防側漏

Handler的正確寫法

正常情況下,本着方便快捷,省時省力的思想,我們會將Handler寫成這副模樣:

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // TODO you have message
        }
    };

但是如果你用的是Android Studio,那麼他會報一片屎黃屎黃的顏色,那是因爲Android Studio的Inspect Code檢出出了這裏可能會造成內存泄漏,Inspect Code可以檢查你的代碼並標記出可能出現問題的地方,後面的優化工具篇會講到,回到正題,鼠標放在小燈泡的圖標上,你就會看到這些東西:

Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

他主要是告訴你,這裏你聲明瞭一個內部類,他會阻礙GC回收垃圾,嗶哩嗶哩……
用代碼說話:

public class SampleActivity extends Activity {
  private final Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // 發送一個10分鐘後執行的一個消息
    mHandler.postDelayed(new Runnable() {
      @Override
      public void run() { }
    }, 600000);

    // 結束當前的Activity
    finish();
  }
}

當Activity結束後,在 Message queue 處理這個Message之前,它會持續存活着。這個Message持有Handler的引用,而Handler有持有Activity(SampleActivity)的引用,這個Activity所有的資源,在這個消息處理之前都不能也不會被回收,所以發生了內存泄露。

辣麼,正確的寫法應該是這樣的:

private MyHandler handler = new MyHandler(this);

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 當參數爲null的時候,可以清除掉所有跟次handler相關的Runnable和Message,我們在onDestroy中調用次方法也就不會發生內存泄漏了。
        handler.removeCallbacksAndMessages(null);
    }

    private void todoHandler(Message msg){
        // TODO handlerMessage
    }

    public static class MyHandler extends Handler{

        private final WeakReference<MainActivity> mActivity;

        public MyHandler(MainActivity activity) {
            mActivity = new WeakReference(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity mainActivity = mActivity.get();
            if (mainActivity != null){
                mainActivity.todoHandler(msg);
            }
        }

    }

如果你覺得這麼寫太TM麻煩了,不用擔心,嫌麻煩的不止你一個,總有受不了的,這裏提供一個WeakHandler庫 : GitHub

單例,不僅僅存在線程安全問題

單例模式深受廣大開發者的喜愛,但從相對論的角度來看,肉吃多了也能死人。所以,在適合的情況下使用單例。而在單例的實現方式上,更多人關注線程安全方面的問題,但是內存泄漏問題同樣重要。
在用LeakCanary檢測APP內存泄漏的時候,發現N多地方出現了單例造成的內存泄漏。
下面來看一段反面教材:

public class XXXManager{  

    private static XXXManager instance;
    private Context context;

    private XXXManager(Context context) {
        this.context = context;
    }

    public static XXXManager getInstance(Context context) {
      if (instance != null) {
        instance = new XXXManager(context);
      }
      return instance;
    } 
}

XXXManager實例是存在於整個app生命週期的,如果是傳入的Application完全沒有問題,但如果你傳入的是某個Activity,那麼這個不幸的Context就跟死了沒法兒投胎一樣的跟着XXXManager,況且如果你在調用getInstance()之前這個實例已經存在了,那麼你傳入的這個Context並沒有什麼卵用。下面給出兩個方案,我認爲第二個會好一點。
方案1:

private XXXManager(Context context) {
    // 這樣你持有的就是Application的Context了,沒毛病~
    this.context = context.getApplication();
}

方案2:

    private static Context mContext;

    public static XXXManager getInstance(){
        if (mContext != null){
           if (instance == null){
               instance = new XXXManager(mContext);
           }
            return instance;
        }
        // 如果mContext = null 拋出異常提醒沒有註冊過
        try {
            throw new Exception("not register!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 在MyApplication中先註冊
    public static void register(Context context){
        mContext = context;
    }

這裏爲了突出重點,忽略了線程安全,使用的時候需要注意到這一點。

買一送一,最後奉上一個單例的正確寫法:
參考鏈接

public static SettingsDbHelper getInstance(Context context) {  
    SettingsDbHelper inst = sInst;  // <<< 在這裏創建臨時變量
    if (inst == null) {
        synchronized (SettingsDbHelper.class) {
            inst = sInst;
            if (inst == null) {
                inst = new SettingsDbHelper(context);
                sInst = inst;
            }
        }
    }
    return inst;  // <<< 注意這裏返回的是臨時變量
}

多線程的濫用——你用多少內存換來懶惰?

我相信很多人都這麼幹過:

 new Thread(new Runnable() {
      @Override
      public void run() {
        SystemClock.sleep(10000);
      }
    }).start();

這裏的Runnable和前面提到的錯誤書寫handler一樣,是一個匿名內部類,因此它們對當前Activity都有一個隱式引用。如果Activity在銷燬之前,任務還未完成,那麼將導致Activity的內存資源無法回收,造成內存泄漏。
同Handler的解決方法一樣,你需要使用一個靜態的內部類:

static class MyRunnable implements Runnable{
    @Override
    public void run() {
        SystemClock.sleep(10000);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章