前言:最近在研究Handler的知識,其中涉及到一個問題,如何避免Handler帶來的內存溢出問題。在網上找了很多資料,有很多都是互相抄的,沒有實際的作用。
本文的內存泄漏檢測工具是:LeakCanary github地址:https://github.com/square/leakcanary
什么是內存泄漏?
- 內存泄漏是當程序不再使用到的內存時,釋放內存失敗而產生了無用的內存消耗。內存泄漏並不是指物理上的內存消失,這里的內存泄漏是值由程序分配的內存但是由於程序邏輯錯誤而導致程序失去了對該內存的控制,使得內存浪費。
怎樣會導致內存泄漏?
- 資源對象沒關閉造成的內存泄漏,如查詢數據庫后沒有關閉游標cursor
- 構造Adapter時,沒有使用 convertView 重用
- Bitmap對象不在使用時調用recycle()釋放內存
- 對象被生命周期長的對象引用,如activity被靜態集合引用導致activity不能釋放
內存泄漏有什么危害?
-
內存泄漏對於app沒有直接的危害,即使app有發生內存泄漏的情況,也不一定會引起app崩潰,但是會增加app內存的占用。內存得不到釋放,慢慢的會造成app內存溢出。所以我們解決內存泄漏的目的就是防止app發生內存溢出。
1、新建線程引起的Activity內存泄漏
例子:
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改為靜態非匿名內部類即可。
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();
}
}
}
}
2、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 取消掉綁定就好了。
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);
}
}
3、Handler 匿名內部類造成內存溢出?
先看着一段代碼
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 );
}
}

4、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 ) ;
}
}
}
5、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() ;
}
}
}
參考資料
