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