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


 

 

1、單例模式引起的內存泄露

由於單例模式的靜態特性,使得它的生命周期和我們的應用一樣長,如果讓單例無限制的持有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獲取;

2、Handler引起的內存泄露

Handler引起的內存泄漏在開發中最為常見的。Handler、Message、MessageQueue都是相互關聯在一起的,如果Handler發送的Message尚未被處理,那么該Message以及發送它的Handler對象都會被線程MessageQueue一直持有。

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

如錯誤代碼:

 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退出時無法進行內存回收,造成內存泄漏。

解決辦法:將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);
    }

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

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的泄露。

4、集合引發的內存泄漏

我們通常會把一些對象的引用加入到集合容器(比如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;
        }
    }

5、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內存泄漏


免責聲明!

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



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