Android開發——避免內存泄露

本文翻譯自Avoiding memory leak——Post by Romain Guy

著做權歸原做者全部。轉載請註明出處,由JohnTsai翻譯html


Android應用被分配的堆的大小限制爲16MB。這對於手機來講已經不少了,但對於一些開發者想得到的來講仍舊不夠。即便你沒有計劃使用全部的這些內存。你應該儘量的少用以免其餘應用在運行時由於內存不足而被殺掉。Android內存中保存的應用越多,用戶在應用間切換得越快。做爲我工做的一部分,我在Android應用中遇到過得內存泄露問題,它們大多數時候是由於同一個錯誤:對Context維持了一個長時間的引用java


在Android中,Context可用於不少操做,但主要是用於加載和訪問資源。這就是爲何全部的widget都要在它們的構造方法中接收Context參數。在一個常規的Android應用中,一般有兩種Context:ActivityApplicationandroid

通常開發者將前者傳遞給須要Context的類和方法:app

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);
  
  TextView label = new TextView(this);
  label.setText("Leaks are bad");
  
  setContentView(label);
}

 

 

這意味着View有一整個Activity的引用。所以可訪問到Activity持有的任何東西。所以,若是你泄露了Context(泄露(leak)意味着你維持了一個指向它的引用致使GC不能回收它),你就泄露了不少內存。若是你不當心的話,很是容易就會泄露整個Activity。ide

當屏幕的方向改變時,系統默認會銷燬當前的Activity並保存它的狀態,而後建立一個新的Activity。在這個過程當中,Android會從資源中從新加載應用的UI。如今假設你寫了一個有大bitmap的應用,你不想每次旋轉時都加載bitmap。維持這個實例、不須要每次從新加載的最簡單的辦法就是將它維持在一個靜態域中。this

private static Drawable sBackground;

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);
  
  TextView label = new TextView(this);
  label.setText("Leaks are bad");
  
  if (sBackground == null) {
    sBackground = getDrawable(R.drawable.large_bitmap);
  }
  label.setBackgroundDrawable(sBackground);
  
  setContentView(label);
}

 

這個代碼是很是便捷可是也很是錯誤。它泄露了第一次因屏幕方向改變而建立的Activity。當一個Drawable對象依附到一個View對象上時,View對象被做爲一個callback設置到drawable對象上了。在上面的代碼片斷中,這就意味着drawable有一個TextView的引用,而TextView有activity的引用(Context),activity有不少東西的緣由(取決於你的代碼)google

這個例子是Context泄露的最簡單的例子,你能夠看看咱們在Home screen的源碼中是如何處理這種問題的(看unbindDrawables()方法),經過在activity銷燬時,將存儲的drawable的callback爲null。有趣的是,有多種狀況能致使咱們建立泄露的Context,這樣很糟糕。它們使得咱們很快就耗盡內存。spa

有兩種簡單的方法可避免Context相關的內存泄露。scala

  • 最明顯的方法是避免在Context的做用域以外使用它。上面那個例子展現了靜態引用或是內部類對外部類的隱式引用都是一樣危險的。
  • 第二種方法就是使用Application Context。這個Context會一直存活只要你的應用是活着的,而且不依賴於Activity的生命週期。若是你打算維持一個長時間存在的而且須要Context的對象時,記住使用應用的Context。獲取方法:Context.getApplicationContext()或Activity.getApplication()

概況來講,爲了不Context相關的內存泄露,記住下面幾點:翻譯

  • 不要維持一個長時間存在對Activity的Context的引用(Activity的引用和Activity有着同樣的生命週期)
  • 使用Application的Context而不是Activity的Context
  • 避免在Activity中使用非靜態內部類,若是你不想控制他們的生命週期。使用靜態內部類,並在它的內部建立一個對Activity的弱引用。

下面的例子由譯者補充

使用非靜態內部類,Android Studio報可能內存泄露的警告:

//解決方法 //使用靜態內部類,並在其中建立對Activity的弱引用 
 private static class MyHandler extends Handler{

        //對Activity的弱引用
        private final WeakReference<HandlerActivity> mActivity;

        public MyHandler(HandlerActivity activity){
            mActivity = new WeakReference<HandlerActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            HandlerActivity activity = mActivity.get();
            if(activity==null){
                super.handleMessage(msg);
                return;
            }
            switch (msg.what) {
                case DOWNLOAD_FAILED:
                    Toast.makeText(activity, "下載失敗", Toast.LENGTH_SHORT).show();
                    break;
                case DOWNLOAD_SUCCESS:
                    Toast.makeText(activity, "下載成功", Toast.LENGTH_SHORT).show();
                    Bitmap bitmap = (Bitmap) msg.obj;
                    activity.imageView.setVisibility(View.VISIBLE);
                    activity.imageView.setImageBitmap(bitmap);
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }

    private final MyHandler mHandler = new MyHandler(this);

 

  • 垃圾回收器不是針對內存泄露的保險。
相關文章
相關標籤/搜索