一,什麼是內存泄漏
首先我們要有堆內存的概念,堆內存是用來存放對象的,堆內存和棧內存的概念可以參考這篇文章https://www.cnblogs.com/liyonghua/p/8805017.html
jvm只有一個堆區(heap)被所有線程共享,堆中不存放基本類型和對象引用,只存放對象本身,幾乎所有的對象實例和數組都在堆中分配。當對象不被引用時,會被gc回收,釋放佔用的堆內存。如果不再用到的對象,被錯誤引用,而無法被回收,這就發生了“內存泄漏”
二,handle內存泄漏的原因
非內部類默認會持有外部類的引用,這樣當Activity被銷燬時,由於被匿名handler對象所持有而不能被釋放,Activity所佔用的內存就會泄露。
public class HandleTestActivity extends AppCompatActivity {
private TextView tv;
private int id = 0;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.e("HandleTestActivity", "handleMessage");
tv.setText(msg.arg1 + "");
Toast.makeText(HandleTestActivity.this, msg.arg1 + " handleMessage", Toast.LENGTH_LONG).show();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e("HandleTestActivity", "onCreate");
setContentView(R.layout.activity_handle_test);
tv = findViewById(R.id.tv);
Message msg = handler.obtainMessage();
++id;
msg.arg1 = id;
handler.sendMessageDelayed(msg, 2000);
}
@Override
protected void onPause() {
super.onPause();
Log.e("HandleTestActivity", "onPause");
Message msg = handler.obtainMessage();
++id;
msg.arg1 = id;
handler.sendMessageDelayed(msg, 100000);
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e("HandleTestActivity", "onDestroy");
//我們可以在Activity onDestroy()時調用handler.removeCallbacksAndMessages(null),
// 這樣就把queue裏所有的message都remove掉了,之前說過message被message pool回收掉會reset,
// 因此不會再引用handler,這條引用鏈就斷掉了。
// handler.removeCallbacksAndMessages(null);
}
/**
* 當垃圾回收確定不再有對對象的引用時,由垃圾回收器對對象調用。
* 子類重寫@code finalize方法以釋放系統資源或執行其他清理。
* @throws Throwable
*/
@Override
protected void finalize() throws Throwable {
super.finalize();
Log.e("HandleTestActivity", "finalize");
}
}
對於非靜態的內部類,內部類的創建依賴外部類的實例對象,在沒有外部類實例之前是無法創建內部類的。所以非內部類默認會持有外部類的引用,可以查看build後的class文件,文件路徑 app\build\intermediates\javac\
文件展開如下,HandleTestActivity$1 表示HandleTestActivity爲外部類,1代表匿名內部類,構造方法傳了HandleTestActivity的實例。非靜態的內部類也是類似的!
class HandleTestActivity$1 extends Handler {
HandleTestActivity$1(HandleTestActivity this$0) {
this.this$0 = this$0;
}
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.e("HandleTestActivity", "handleMessage");
HandleTestActivity.access$000(this.this$0).setText(msg.arg1 + "");
Toast.makeText(this.this$0, msg.arg1 + " handleMessage", 1).show();
}
}
當啓動HandleTestActivity後再finish,可以通過Android studio的profiler工具查看引用是否被釋放
handler實例引用了Activity,handler又被其messsage.target所引用,message放入message queue中,message queue生命週期結束前,message queue都間接引用了activity,導致內存泄漏!
也有些文章提到,可以在Activity onDestroy()時調用handler.removeCallbacksAndMessages(null),這樣確實message queue對activity的引用鏈沒有了,如下圖,但是handle對activity的引用鏈仍然存在
可以看到handle仍然引用着當前的this
三。解決方法
最好的解決方式就是使用靜態內部類解決
public class HandleTest2Activity extends AppCompatActivity {
private TextView tv;
private int id = 0;
private Handler handler = new Myhandle(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e("HandleTest1Activity", "onCreate");
setContentView(R.layout.activity_handle_test2);
tv = findViewById(R.id.tv);
Message msg = handler.obtainMessage();
++id;
msg.arg1 = id;
handler.sendMessageDelayed(msg, 2000);
}
@Override
protected void onPause() {
super.onPause();
Log.e("HandleTest1Activity", "onPause");
Message msg = handler.obtainMessage();
++id;
msg.arg1 = id;
handler.sendMessageDelayed(msg, 40000);
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e("HandleTest1Activity", "onDestroy");
// handler.removeCallbacksAndMessages(null);
}
/**
* 當垃圾回收確定不再有對對象的引用時,由垃圾回收器對對象調用。
* 子類重寫@code finalize方法以釋放系統資源或執行其他清理。
*
* @throws Throwable
*/
@Override
protected void finalize() throws Throwable {
super.finalize();
Log.e("HandleTest1Activity", "finalize");
}
private static class Myhandle extends Handler {
private WeakReference<Context> reference;//弱引用
public Myhandle(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Context context = reference.get();
Log.e("Myhandle", "handleMessage context=" + context);
if (null != context){
Toast.makeText(context, msg.arg1 + " handleMessage", Toast.LENGTH_LONG).show();
}
}
}
}
靜態內部類編譯好的class文件,對照HandleTestActivity$1,構造方法沒有傳入外部類的對象
class HandleTest2Activity$Myhandle extends Handler {
private WeakReference<Context> reference;
public HandleTest2Activity$Myhandle(Context context) {
this.reference = new WeakReference(context);
}
public void handleMessage(Message msg) {
super.handleMessage(msg);
Context context = (Context)this.reference.get();
Log.e("Myhandle", "handleMessage context=" + context);
if (null != context) {
Toast.makeText(context, msg.arg1 + " handleMessage", 1).show();
}
}
}
此時看運行完的結果,此時已經沒有handle對activity的引用了
四,leakcanary
內存泄漏也可以使用leakcanary分析
源碼地址 https://dev.tencent.com/u/kuangxuefeng/p/AndroidTestDemo/git