Android開發中常見的內存泄露案例以及解決方法總結

 

 

一、單例模式引發的內存泄露web

因爲單例模式的靜態特性,使得它的生命週期和咱們的應用同樣長,若是讓單例無限制的持有Activity的強引用就會致使內存泄漏
如錯誤代碼示例:
public class UserInfoBean {
    private static UserInfoBean userInfoBean;

    private Context mContext;

    private UserInfoBean(Context context) {
        this.mContext = context;
    }

    public static UserInfoBean getUserInfoBean(Context context) {
        if (userInfoBean == null) {
            synchronized (UserInfoBean.class) {
                if (userInfoBean == null) {
                    userInfoBean = new UserInfoBean(context);
                }
            }
        }
        return userInfoBean;
    }
}

正確代碼:數據庫

將 this.mContext = context改爲:this.mContext = context.getApplicationContext();或者代碼中用到的Context可使用本身定義的MyApplication中的MyApplication.getInstance獲取;設計模式

二、Handler引發的內存泄露緩存

Handler引發的內存泄漏在開發中最爲常見的。Handler、Message、MessageQueue都是相互關聯在一塊兒的,若是Handler發送的Message還沒有被處理,那麼該Message以及發送它的Handler對象都會被線程MessageQueue一直持有。性能優化

因爲Handler屬於TLS(Thread Local Storage)變量,生命週期和Activity是不一致的,所以這種實現方式很難保證跟Activity的生命週期一直,因此很容易沒法釋放內存異步

如錯誤代碼:ide

 private final Handler mHandler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            // ...  
        }  
    };  
@Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        mHandler.sendMessageDelayed(Message.obtain(), 60000*5);  
    }  

在上面的例子中生命了一個延時5分鐘執行的Message,當該Activity退出的時候,延時任務(Message)還在主線成的MessageQueue中等待,此時的Message持有Handler的強引用,而且因爲Handler是咱們的Activity類的非靜態內部類,因此Handler會持有該Activity的強引用,此時該Activity退出時沒法進行內存回收,形成內存泄漏。oop

解決辦法:將Handler聲明爲靜態內部類,這樣它就不會持有外部類的引用了,Handler的的生命週期就與Activity無關了。不過假若用到Context等外部類的非static對象,仍是應該經過使用Application中與應用同生命週期的Context比較合適性能

正確代碼:優化

private static final class MyHandler extends Handler {
        private WeakReference<HomeMainActivity> mActivity;

        public MyHandler(HomeMainActivity mainActivity) {
            mActivity = new WeakReference<>(mainActivity);
      //or
      //mActivity=mainActivity.getApplicationContext; } @Override
public void handleMessage(Message msg) { super.handleMessage(msg); HomeMainActivity mainActivity = mActivity.get(); if (null != mActivity) { //相關處理 } } }
private final MyHandler mHandler = new MyHandler(this);
mHandler.sendMessageDelayed(Message.obtain(), 60000*5);

雖然咱們結束了Activity的內存泄漏問題,可是通過Handler發送的延時消息還在MessageQueue中,Looper也在等待處理消息,因此咱們要在Activity銷燬的時候處理掉隊列中的消息。

@Override
    protected void onDestroy() {
        super.onDestroy();
        //傳入null,就表示移除全部Message和Runnable
        mHandler.removeCallbacksAndMessages(null);
    }

 三、匿名內部類在異步線程中的使用引發的內存泄漏

Android開發常常會繼承實現 Activity 或者 Fragment 或者 View。若是使用了匿名類,而又被異步線程所引用,若是沒有任何措施一樣會致使內存泄漏的:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_inner_bad);

        Runnable runnable1 = new MyRunnable();
        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {

            }
        };
    }

    private static class MyRunnable implements Runnable{

        @Override
        public void run() {

        }
    }
}

runnable1 和 runnable2的區別就是,runnable2使用了匿名內部類,咱們看看引用時的引用內存 

能夠看到,runnable1是沒有什麼特別的。但runnable2多出了一個MainActivity的引用,如果這個引用再傳入到一個異步線程,此線程在和Activity生命週期不一致的時候,也就形成了Activity的泄露。

四、集合引起的內存泄漏

咱們一般會把一些對象的引用加入到集合容器(好比ArrayList)中,當咱們再也不須要該對象時,並無把它的引用從集合中清理掉,當集合中的內容過於大的時候,而且是static的時候就形成了內存泄漏,全部最好在onDestory清空;

    private List<String> nameList;
    private List<Fragment> list;

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (nameList != null){
            nameList.clear();
            nameList = null;
        }
        if (list != null){
            list.clear();
            list = null;
        }
    }

五、Android WebView Memory Leak WebView內存泄漏(查看做者原文)

WebView解析網頁時會申請Native堆內存用於保存頁面元素,當頁面較複雜時會有很大的內存佔用。若是頁面包含圖片,內存佔用會更嚴重。而且打開新頁面時,爲了能快速回退,以前頁面佔用的內存也不會釋放。有時瀏覽十幾個網頁,都會佔用幾百兆的內存。這樣加載網頁較多時,會致使系統不堪重負,最終強制關閉應用,也就是出現應用閃退或重啓。

 要使用WebView不形成內存泄漏,首先應該作的就是不能在xml中定義webview節點,而是在須要的時候動態生成。即:能夠在使用WebView的地方放置一個LinearLayout相似ViewGroup的節點,而後在要使用WebView的時候,動態生成即:

WebView      mWebView = new WebView(getApplicationgContext()); 
LinearLayout mll      = findViewById(R.id.xxx); 
mll.addView(mWebView);

 而後必定要在onDestroy()方法中顯式的調用

protected void onDestroy() {       super.onDestroy(); mWebView.removeAllViews(); mWebView.destroy() }

注意: new  WebView(getApplicationgContext()) ;必須傳入ApplicationContext若是傳入Activity的Context的話,對內存的引用會一直被保持着。有人用這個方法解決了當Activity被消除後依然保持引用的問題。可是你會發現,若是你須要在WebView中打開連接或者你打開的頁面帶有flash,得到你的WebView想彈出一個dialog,都會致使從ApplicationContext到ActivityContext的強制類型轉換錯誤,從而致使你應用崩潰。這是由於在加載flash的時候,系統會首先把你的WebView做爲父控件,而後在該控件上繪製flash,他想找一個Activity的Context來繪製他,可是你傳入的是ApplicationContext。後果,你能夠曉得了哈。

 

其餘常見的引發內存泄漏緣由

  • 構造Adapter時,沒有使用緩存的 convertView
  • Bitmap在不使用的時候沒有使用recycle()釋放內存
  • 非靜態內部類的靜態實例容易形成內存泄漏:即一個類中若是你不可以控制它其中內部類的生命週期(譬如Activity中的一些特殊Handler等),則儘可能使用靜態類和弱引用來處理(譬如ViewRoot的實現)。
  • 警戒線程未終止形成的內存泄露;譬如在Activity中關聯了一個生命週期超過Activity的Thread,在退出Activity時切記結束線程。一個典型的例子就是HandlerThread的run方法是一個死循環,它不會本身結束,線程的生命週期超過了Activity生命週期,咱們必須手動在Activity的銷燬方法中調用thread.getLooper().quit();纔不會泄露。
  • 對象的註冊與反註冊沒有成對出現形成的內存泄露;譬如註冊廣播接收器、註冊觀察者(典型的譬如數據庫的監聽)等。
  • 建立與關閉沒有成對出現形成的泄露;譬如Cursor資源必須手動關閉,WebView必須手動銷燬,流等對象必須手動關閉等。
  • 不要在執行頻率很高的方法或者循環中建立對象(好比onMeasure),可使用HashTable等建立一組對象容器從容器中取那些對象,而不用每次new與釋放。
  • 避免代碼設計模式的錯誤形成內存泄露;譬如循環引用,A持有B,B持有C,C持有A,這樣的設計誰都得不到釋放

參考文章:

1.【Android 性能優化】—— 詳解內存優化的前因後果

2.Android WebView Memory Leak WebView內存泄漏

相關文章
相關標籤/搜索