Android開發——避免內存泄露


Android開發——避免內存泄露

本文翻譯自Avoiding memory leak——Post by Romain Guy
著作權歸原作者所有。轉載請注明出處,由JohnTsai翻譯


Android應用被分配的堆的大小限制為16MB。這對於手機來說已經很多了,但對於一些開發者想獲得的來說仍舊不夠。即使你沒有計划使用所有的這些內存。你應該盡可能的少用以避免其他應用在運行時因為內存不足而被殺掉。Android內存中保存的應用越多,用戶在應用間切換得越快。作為我工作的一部分,我在Android應用中遇到過得內存泄露問題,它們大多數時候是因為同一個錯誤:對Context維持了一個長時間的引用


在Android中,Context可用於很多操作,但主要是用於加載和訪問資源。這就是為什么所有的widget都要在它們的構造方法中接收Context參數。在一個常規的Android應用中,通常有兩種Context:ActivityApplication

一般開發者將前者傳遞給需要Context的類和方法:

@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。

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

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有很多東西的原因(取決於你的代碼)

這個例子是Context泄露的最簡單的例子,你可以看看我們在Home screen的源碼中是如何處理這種問題的(看unbindDrawables()方法),通過在activity銷毀時,將存儲的drawable的callback為null。有趣的是,有多種情況能導致我們創建泄露的Context,這樣很糟糕。它們使得我們很快就耗盡內存。

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

  • 最明顯的方法是避免在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);

  • 垃圾回收器不是針對內存泄露的保險。

如果本文對你的開發有所幫助,並且你手頭恰好有零錢。

不如打賞我一杯咖啡,鼓勵我繼續分享優秀的文章。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM