Android 如何有效的解决内存泄漏的问题
前言:最近在研究Handler的知識,其中涉及到一個問題,如何避免Handler帶來的內(nèi)存溢出問題。在網(wǎng)上找了很多資料,有很多都是互相抄的,沒有實際的作用。
本文的內(nèi)存泄漏檢測工具是:LeakCanary ?github地址:https://github.com/square/leakcanary
?
?
什么是內(nèi)存泄漏?
- 內(nèi)存泄漏是當(dāng)程序不再使用到的內(nèi)存時,釋放內(nèi)存失敗而產(chǎn)生了無用的內(nèi)存消耗。內(nèi)存泄漏并不是指物理上的內(nèi)存消失,這里的內(nèi)存泄漏是值由程序分配的內(nèi)存但是由于程序邏輯錯誤而導(dǎo)致程序失去了對該內(nèi)存的控制,使得內(nèi)存浪費。
?
怎樣會導(dǎo)致內(nèi)存泄漏?
- 資源對象沒關(guān)閉造成的內(nèi)存泄漏,如查詢數(shù)據(jù)庫后沒有關(guān)閉游標(biāo)cursor
- 構(gòu)造Adapter時,沒有使用 convertView 重用
- Bitmap對象不在使用時調(diào)用recycle()釋放內(nèi)存
- 對象被生命周期長的對象引用,如activity被靜態(tài)集合引用導(dǎo)致activity不能釋放
?
內(nèi)存泄漏有什么危害?
-
內(nèi)存泄漏對于app沒有直接的危害,即使app有發(fā)生內(nèi)存泄漏的情況,也不一定會引起app崩潰,但是會增加app內(nèi)存的占用。內(nèi)存得不到釋放,慢慢的會造成app內(nèi)存溢出。所以我們解決內(nèi)存泄漏的目的就是防止app發(fā)生內(nèi)存溢出。
?
1、新建線程引起的Activity內(nèi)存泄漏
例子:
package rxnet.zyj.com.myapplication;import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View;public class Activity6 extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_6);findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {finish();}});new Thread(new Runnable() {@Overridepublic void run() {try {//模擬耗時操作Thread.sleep( 15000 );} catch (InterruptedException e) {e.printStackTrace();}}}).start();} }運行上面的代碼后,點擊finish按鈕,過一會兒發(fā)生了內(nèi)存泄漏的問題。
?為什么Activity6會發(fā)生內(nèi)存泄漏?
進(jìn)入Activity6 界面,然后點擊finish按鈕,Activity6銷毀,但是Activity6里面的線程還在運行,匿名內(nèi)部類Runnable對象引用了Activity6的實例,導(dǎo)致Activity6所占用的內(nèi)存不能被GC及時回收。
?
?如何改進(jìn)?
Runnable改為靜態(tài)非匿名內(nèi)部類即可。
package rxnet.zyj.com.myapplication;import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View;public class Activity6 extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_6);findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {finish();}});new Thread( new MyRunnable()).start();}private static class MyRunnable implements Runnable {@Overridepublic void run() {try {Thread.sleep( 15000 );} catch (InterruptedException e) {e.printStackTrace();}}}}
?2、Activity添加監(jiān)聽器造成Activity內(nèi)存泄漏
package rxnet.zyj.com.myapplication;import android.app.Activity; import android.os.Bundle;public class LeakActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);NastyManager.getInstance().addListener(this);} }這個是在開發(fā)中經(jīng)常會犯的錯誤,NastyManager.getInstance() 是一個單例,當(dāng)我們通過 addListener(this) 將 Activity 作為 Listener 和 NastyManager 綁定起來的時候,不好的事情就發(fā)生了。
如何改進(jìn)?
想要修復(fù)這樣的 Bug,其實相當(dāng)簡單,就是在你的 Acitivity 被銷毀的時候,將他和 NastyManager 取消掉綁定就好了。
package rxnet.zyj.com.myapplication;import android.app.Activity; import android.os.Bundle;public class LeakActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);NastyManager.getInstance().addListener(this);}@Overrideprotected void onDestroy() {super.onDestroy();NastyManager.getInstance().removeListener(this);} }
3、Handler 匿名內(nèi)部類造成內(nèi)存溢出?
先看著一段代碼
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(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);Log.d("mmmmmmmm" , "handler " + msg.what ) ;}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handler);findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {finish();}});new Thread(new Runnable() {@Overridepublic void run() {handler.sendEmptyMessage( MESSAGECODE ) ;try {Thread.sleep( 8000 );} catch (InterruptedException e) {e.printStackTrace();}handler.sendEmptyMessage( MESSAGECODE ) ;}}).start() ;} }這段代碼運行起來后,立即點擊?finish 按鈕,通過檢測,發(fā)現(xiàn)?HandlerActivity 出現(xiàn)了內(nèi)存泄漏。當(dāng)Activity finish后,延時消息會繼續(xù)存在主線程消息隊列中8秒鐘,然后處理消息。而該消息引用了Activity的Handler對象,然后這個Handler又引用了這個Activity。這些引用對象會保持到該消息被處理完,這樣就導(dǎo)致該Activity對象無法被回收,從而導(dǎo)致了上面說的 Activity泄露。Handler?是個很常用也很有用的類,異步,線程安全等等。如果有下面這樣的代碼,會發(fā)生什么呢? handler.postDeslayed ,假設(shè) delay 時間是幾個小時… 這意味著什么?意味著只要 handler 的消息還沒有被處理結(jié)束,它就一直存活著,包含它的 Activity 就跟著活著。我們來想辦法修復(fù)它,修復(fù)的方案是 WeakReference ,也就是所謂的弱引用。垃圾回收器在回收的時候,是會忽視掉弱引用的,所以包含它的 Activity 會被正常清理掉。
如何避免
- 使用靜態(tài)內(nèi)部類
- 使用弱引用
修改后代碼是這樣的。
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 ;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handler);findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {finish();}});handler = new MyHandler( this ) ;new Thread(new Runnable() {@Overridepublic 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) ;}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if ( weakReference.get() != null ){// update android uiLog.d("mmmmmmmm" , "handler " + msg.what ) ;}}} }這個Handler已經(jīng)使用了靜態(tài)內(nèi)部類,并且使用了弱引用。但是這個并沒有完全解決?HandlerActivity 內(nèi)存泄漏的問題,罪魁禍?zhǔn)资蔷€程創(chuàng)建的方式出了問題,就像本文的第一個例子一樣。改進(jìn)的方式,是把Runnable類寫成靜態(tài)內(nèi)部類。
最終完整的代碼如下:
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 ;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handler);findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {finish();}});//創(chuàng)建Handlerhandler = new MyHandler( this ) ;//創(chuàng)建線程并且啟動線程new Thread( new MyRunnable() ).start();}private static class MyHandler extends Handler {WeakReference<HandlerActivity> weakReference ;public MyHandler(HandlerActivity activity ){weakReference = new WeakReference<HandlerActivity>( activity) ;}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if ( weakReference.get() != null ){// update android uiLog.d("mmmmmmmm" , "handler " + msg.what ) ;}}}private static class MyRunnable implements Runnable {@Overridepublic void run() {handler.sendEmptyMessage( MESSAGECODE ) ;try {Thread.sleep( 8000 );} catch (InterruptedException e) {e.printStackTrace();}handler.sendEmptyMessage( MESSAGECODE ) ;}} }等等,還沒完呢?
上面這個代碼已經(jīng)有效的解決了Handler,Runnable 引用Activity實例從而導(dǎo)致內(nèi)存泄漏的問題,但是這不夠。因為內(nèi)存泄漏的核心原因就是這個某個對象應(yīng)該被系統(tǒng)回收內(nèi)存的時候,卻被其他對象引用,造成該內(nèi)存無法回收。所以我們在寫代碼的時候,要始終繃著這個弦。再回到上面這個問題,當(dāng)當(dāng)前Activity調(diào)用finish銷毀的時候,在這個Activity里面所有線程是不是應(yīng)該在OnDestory()方法里,取消線程。當(dāng)然是否取消異步任務(wù),要看項目具體的需求,比如在Activity銷毀的時候,啟動一個線程,異步寫log日志到本地磁盤,針對這個需求卻需要在OnDestory()方法里開啟線程。所以根據(jù)當(dāng)前環(huán)境做出選擇才是正解。
所以我們還可以修改代碼為:在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 ;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handler);findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {finish();}});//創(chuàng)建Handlerhandler = new MyHandler( this ) ;//創(chuàng)建線程并且啟動線程new Thread( new MyRunnable() ).start();}private static class MyHandler extends Handler {WeakReference<HandlerActivity> weakReference ;public MyHandler(HandlerActivity activity ){weakReference = new WeakReference<HandlerActivity>( activity) ;}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if ( weakReference.get() != null ){// update android uiLog.d("mmmmmmmm" , "handler " + msg.what ) ;}}}private static class MyRunnable implements Runnable {@Overridepublic void run() {handler.sendEmptyMessage( MESSAGECODE ) ;try {Thread.sleep( 8000 );} catch (InterruptedException e) {e.printStackTrace();}handler.sendEmptyMessage( MESSAGECODE ) ;}}@Overrideprotected void onDestroy() {super.onDestroy();//如果參數(shù)為null的話,會將所有的Callbacks和Messages全部清除掉。handler.removeCallbacksAndMessages( null );} }
?
?
4、AsyncTask造成內(nèi)存泄漏
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 {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_2);findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {finish();}});new AsyncTask<String,Integer,String>(){@Overrideprotected String doInBackground(String... params) {try {Thread.sleep( 6000 );} catch (InterruptedException e) {}return "ssss";}@Overrideprotected void onPostExecute(String s) {super.onPostExecute(s);Log.d( "mmmmmm activity2 " , "" + s ) ;}}.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "" ) ;} }為什么?
上面代碼在activity中創(chuàng)建了一個匿名類AsyncTask,匿名類和非靜態(tài)內(nèi)部類相同,會持有外部類對象,這里也就是activity,因此如果你在Activity里聲明且實例化一個匿名的AsyncTask對象,則可能會發(fā)生內(nèi)存泄漏,如果這個線程在Activity銷毀后還一直在后臺執(zhí)行,那這個線程會繼續(xù)持有這個Activity的引用從而不會被GC回收,直到線程執(zhí)行完成。
? ?怎么解決?
- ?自定義靜態(tài)AsyncTask類
- AsyncTask的周期和Activity周期保持一致。也就是在Activity生命周期結(jié)束時要將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 ;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_asynctask);findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {finish();}});myTask = new MyTask() ;myTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "") ;}private static class MyTask extends AsyncTask{@Overrideprotected Object doInBackground(Object[] params) {try {//模擬耗時操作Thread.sleep( 15000 );} catch (InterruptedException e) {e.printStackTrace();}return "";}}@Overrideprotected void onDestroy() {super.onDestroy();//取消異步任務(wù)if ( myTask != null ){myTask.cancel(true ) ;}} }?
5、Timer Tasks 造成內(nèi)存泄漏
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 {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_2);findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {finish();}});//開始定時任務(wù)timer();}void timer(){new Timer().schedule(new TimerTask() {@Overridepublic void run() {while(true);}},1000 ); // 1秒后啟動一個任務(wù)} }
? 為什么??
這里內(nèi)存泄漏在于Timer和TimerTask沒有進(jìn)行Cancel,從而導(dǎo)致Timer和TimerTask一直引用外部類Activity。
??怎么解決??
- 在適當(dāng)?shù)臅r機(jī)進(jìn)行Cancel。
- TimerTask用靜態(tài)內(nèi)部類
? ?注意:在網(wǎng)上看到一些資料說,解決TimerTask內(nèi)存泄漏可以使用在適當(dāng)?shù)臅r機(jī)進(jìn)行Cancel。經(jīng)過測試,證明單單使用在適當(dāng)?shù)臅r機(jī)進(jìn)行Cancel , 還是有內(nèi)存泄漏的問題。所以一定要用靜態(tài)內(nèi)部類配合使用。
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 ;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_2);findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {finish();}});//開始定時任務(wù)timer();}void timer(){timerTask = new MyTimerTask() ;new Timer().schedule( timerTask ,1000 ); // 1秒后啟動一個任務(wù)}private static class MyTimerTask extends TimerTask{@Overridepublic void run() {while(true){Log.d( "ttttttttt" , "timerTask" ) ;}}}@Overrideprotected void onDestroy() {super.onDestroy();//取消定時任務(wù)if ( timerTask != null ){timerTask.cancel() ;}} }
參考資料
深入Android內(nèi)存泄露
Android內(nèi)存泄漏分析心得
Java內(nèi)存模型?
總結(jié)
以上是生活随笔為你收集整理的Android 如何有效的解决内存泄漏的问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 从零开始打造异步处理框架
- 下一篇: Android Butterknife