Android 如何有效的解決內存泄漏的問題

前言:最近在研究Handler的知識,其中涉及到一個問題,如何避免Handler帶來的內存溢出問題。在網上找了不少資料,有不少都是互相抄的,沒有實際的做用。java

本文的內存泄漏檢測工具是:LeakCanary  github地址:https://github.com/square/leakcanaryandroid

 

 

什麼是內存泄漏?

  • 內存泄漏是當程序再也不使用到的內存時,釋放內存失敗而產生了無用的內存消耗。內存泄漏並非指物理上的內存消失,這裏的內存泄漏是值由程序分配的內存可是因爲程序邏輯錯誤而致使程序失去了對該內存的控制,使得內存浪費。

 

怎樣會致使內存泄漏?

  • 資源對象沒關閉形成的內存泄漏,如查詢數據庫後沒有關閉遊標cursor
  • 構造Adapter時,沒有使用 convertView 重用
  • Bitmap對象不在使用時調用recycle()釋放內存
  • 對象被生命週期長的對象引用,如activity被靜態集合引用致使activity不能釋放

 

內存泄漏有什麼危害?

  • 內存泄漏對於app沒有直接的危害,即便app有發生內存泄漏的狀況,也不必定會引發app崩潰,可是會增長app內存的佔用。內存得不到釋放,慢慢的會形成app內存溢出。因此咱們解決內存泄漏的目的就是防止app發生內存溢出。git

 

一、新建線程引發的Activity內存泄漏

例子:github

package rxnet.zyj.com.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class Activity6 extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_6);

        findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
//模擬耗時操做 Thread.sleep( 15000 ); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }

  運行上面的代碼後,點擊finish按鈕,過一下子發生了內存泄漏的問題。數據庫

 爲何Activity6會發生內存泄漏?

進入Activity6 界面,而後點擊finish按鈕,Activity6銷燬,可是Activity6裏面的線程還在運行,匿名內部類Runnable對象引用了Activity6的實例,致使Activity6所佔用的內存不能被GC及時回收。安全

 

 如何改進?

Runnable改成靜態非匿名內部類便可。app

package rxnet.zyj.com.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class Activity6 extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_6);

        findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        new Thread( new MyRunnable()).start();

    }

    private static class MyRunnable implements Runnable {

        @Override
        public void run() {
            try {
                Thread.sleep( 15000 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
}

  

 二、Activity添加監聽器形成Activity內存泄漏

package rxnet.zyj.com.myapplication;

import android.app.Activity;
import android.os.Bundle;

public class LeakActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        NastyManager.getInstance().addListener(this);
    }
}

  這個是在開發中常常會犯的錯誤,NastyManager.getInstance() 是一個單例,當咱們經過 addListener(this) 將 Activity 做爲 Listener 和 NastyManager 綁定起來的時候,很差的事情就發生了。異步

如何改進?

想要修復這樣的 Bug,其實至關簡單,就是在你的 Acitivity 被銷燬的時候,將他和 NastyManager 取消掉綁定就行了。async

package rxnet.zyj.com.myapplication;

import android.app.Activity;
import android.os.Bundle;

public class LeakActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        NastyManager.getInstance().addListener(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        NastyManager.getInstance().removeListener(this);
    }
}

  

三、Handler 匿名內部類形成內存溢出?

先看着一段代碼ide

package rxnet.zyj.com.myapplication;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

public class HandlerActivity extends AppCompatActivity {

    private final static int MESSAGECODE = 1 ;

    private final Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.d("mmmmmmmm" , "handler " + msg.what ) ;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                handler.sendEmptyMessage( MESSAGECODE ) ;
                try {
                    Thread.sleep( 8000 );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.sendEmptyMessage( MESSAGECODE ) ;
            }
        }).start() ;

    }
}

  這段代碼運行起來後,當即點擊 finish 按鈕,經過檢測,發現 HandlerActivity 出現了內存泄漏。當Activity finish後,延時消息會繼續存在主線程消息隊列中8秒鐘,而後處理消息。而該消息引用了Activity的Handler對象,而後這個Handler又引用了這個Activity。這些引用對象會保持到該消息被處理完,這樣就致使該Activity對象沒法被回收,從而致使了上面說的 Activity泄露。Handler 是個很經常使用也頗有用的類,異步,線程安全等等。若是有下面這樣的代碼,會發生什麼呢? handler.postDeslayed ,假設 delay 時間是幾個小時… 這意味着什麼?意味着只要 handler 的消息尚未被處理結束,它就一直存活着,包含它的 Activity 就跟着活着。咱們來想辦法修復它,修復的方案是 WeakReference ,也就是所謂的弱引用。垃圾回收器在回收的時候,是會忽視掉弱引用的,因此包含它的 Activity 會被正常清理掉。

如何避免

  • 使用靜態內部類
  • 使用弱引用

修改後代碼是這樣的。

package rxnet.zyj.com.myapplication;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import java.lang.ref.WeakReference;

public class HandlerActivity extends AppCompatActivity {

    private final static int MESSAGECODE = 1 ;
    private static Handler handler ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        handler = new MyHandler( this ) ;

        new Thread(new Runnable() {
            @Override
            public void run() {
                handler.sendEmptyMessage( MESSAGECODE ) ;
                try {
                    Thread.sleep( 8000 );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.sendEmptyMessage( MESSAGECODE ) ;
            }
        }).start() ;

    }

    private static class MyHandler extends Handler {
        WeakReference<HandlerActivity> weakReference ;

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

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if ( weakReference.get() != null ){
                // update android ui
                Log.d("mmmmmmmm" , "handler " + msg.what ) ;
            }
        }
    }
}

  這個Handler已經使用了靜態內部類,而且使用了弱引用。可是這個並無徹底解決 HandlerActivity 內存泄漏的問題,罪魁禍首是線程建立的方式出了問題,就像本文的第一個例子同樣。改進的方式,是把Runnable類寫成靜態內部類。

最終完整的代碼以下:

package rxnet.zyj.com.myapplication;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import java.lang.ref.WeakReference;

public class HandlerActivity extends AppCompatActivity {

    private final static int MESSAGECODE = 1 ;
    private static Handler handler ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        //建立Handler
        handler = new MyHandler( this ) ;

        //建立線程而且啓動線程
        new Thread( new MyRunnable() ).start();
    }

    private static class MyHandler extends Handler {
        WeakReference<HandlerActivity> weakReference ;

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

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if ( weakReference.get() != null ){
                // update android ui
                Log.d("mmmmmmmm" , "handler " + msg.what ) ;
            }
        }
    }

    private static class MyRunnable implements Runnable {

        @Override
        public void run() {
            handler.sendEmptyMessage( MESSAGECODE ) ;
            try {
                Thread.sleep( 8000 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            handler.sendEmptyMessage( MESSAGECODE ) ;
        }
    }
}

  等等,還沒完呢?

面這個代碼已經有效的解決了Handler,Runnable 引用Activity實例從而致使內存泄漏的問題,可是這不夠。由於內存泄漏的核心緣由就是這個某個對象應該被系統回收內存的時候,卻被其餘對象引用,形成該內存沒法回收。因此咱們在寫代碼的時候,要始終繃着這個弦。再回到上面這個問題,噹噹前Activity調用finish銷燬的時候,在這個Activity裏面全部線程是否是應該在OnDestory()方法裏,取消線程。固然是否取消異步任務,要看項目具體的需求,好比在Activity銷燬的時候,啓動一個線程,異步寫log日誌到本地磁盤,針對這個需求卻須要在OnDestory()方法裏開啓線程。因此根據當前環境作出選擇纔是正解。

因此咱們還能夠修改代碼爲:在onDestroy() 裏面移除全部的callback 和 Message 。

package rxnet.zyj.com.myapplication;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import java.lang.ref.WeakReference;

public class HandlerActivity extends AppCompatActivity {

    private final static int MESSAGECODE = 1 ;
    private static Handler handler ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        //建立Handler
        handler = new MyHandler( this ) ;

        //建立線程而且啓動線程
        new Thread( new MyRunnable() ).start();
    }

    private static class MyHandler extends Handler {
        WeakReference<HandlerActivity> weakReference ;

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

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if ( weakReference.get() != null ){
                // update android ui
                Log.d("mmmmmmmm" , "handler " + msg.what ) ;
            }
        }
    }

    private static class MyRunnable implements Runnable {

        @Override
        public void run() {
            handler.sendEmptyMessage( MESSAGECODE ) ;
            try {
                Thread.sleep( 8000 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            handler.sendEmptyMessage( MESSAGECODE ) ;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

       //若是參數爲null的話,會將全部的Callbacks和Messages所有清除掉。
        handler.removeCallbacksAndMessages( null );
    }
}

  

 

 

四、AsyncTask形成內存泄漏

package rxnet.zyj.com.myapplication;

import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class Activity2 extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_2);

        findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });


        new AsyncTask<String,Integer,String>(){

            @Override
            protected String doInBackground(String... params) {
                try {
                    Thread.sleep( 6000 );
                } catch (InterruptedException e) {
                }
                return "ssss";
            }

            @Override
            protected void onPostExecute(String s) {
                super.onPostExecute(s);
                Log.d( "mmmmmm activity2 " , "" + s ) ;
            }

        }.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "" ) ;
        
    }
}

  爲何?

上面代碼在activity中建立了一個匿名類AsyncTask,匿名類和非靜態內部類相同,會持有外部類對象,這裏也就是activity,所以若是你在Activity裏聲明且實例化一個匿名的AsyncTask對象,則可能會發生內存泄漏,若是這個線程在Activity銷燬後還一直在後臺執行,那這個線程會繼續持有這個Activity的引用從而不會被GC回收,直到線程執行完成。

   怎麼解決?

  •  自定義靜態AsyncTask類
  • AsyncTask的週期和Activity週期保持一致。也就是在Activity生命週期結束時要將AsyncTask cancel掉。

 

package rxnet.zyj.com.myapplication;

import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class AsyncTaskActivity extends AppCompatActivity {

    private static MyTask myTask ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_asynctask);

        findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        myTask = new MyTask() ;
        myTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "") ;

    }

    private static class MyTask extends AsyncTask{

        @Override
        protected Object doInBackground(Object[] params) {
            try {
                //模擬耗時操做
                Thread.sleep( 15000 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "";
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        //取消異步任務
        if ( myTask != null ){
            myTask.cancel(true ) ;
        }
    }
}

 

五、Timer Tasks 形成內存泄漏

package rxnet.zyj.com.myapplication;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

import java.util.Timer;
import java.util.TimerTask;

public class TimerActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_2);

        findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        //開始定時任務
        timer();
    }

    void timer(){
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                while(true);
            }
        },1000 );  // 1秒後啓動一個任務
    }
}

  

  爲何? 

這裏內存泄漏在於Timer和TimerTask沒有進行Cancel,從而致使Timer和TimerTask一直引用外部類Activity。

  怎麼解決? 

  • 在適當的時機進行Cancel。
  • TimerTask用靜態內部類

   注意:在網上看到一些資料說,解決TimerTask內存泄漏可使用在適當的時機進行Cancel。通過測試,證實單單使用在適當的時機進行Cancel , 仍是有內存泄漏的問題。因此必定要用靜態內部類配合使用。

package rxnet.zyj.com.myapplication;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import java.util.Timer;
import java.util.TimerTask;

public class TimerActivity extends AppCompatActivity {

    private TimerTask timerTask ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_2);

        findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        //開始定時任務
        timer();
    }

    void timer(){
        timerTask = new MyTimerTask() ;
        new Timer().schedule( timerTask ,1000 );  // 1秒後啓動一個任務
    }

    private static class MyTimerTask extends TimerTask{

        @Override
        public void run() {
            while(true){
                Log.d( "ttttttttt" , "timerTask" ) ;
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        //取消定時任務
        if ( timerTask != null ){
            timerTask.cancel() ;
        }
    }
}

  

 參考資料

 深刻Android內存泄露

相關文章
相關標籤/搜索