Android 有效的解決內(nèi)存泄漏的問題實(shí)例詳解
Android 有效的解決內(nèi)存泄漏的問題
Android內(nèi)存泄漏,我想做Android 應(yīng)用的時(shí)候遇到的話很是頭疼,這里是我在網(wǎng)上找的不錯(cuò)的資料,實(shí)例詳解這個(gè)問題的解決方案
前言:最近在研究Handler的知識(shí),其中涉及到一個(gè)問題,如何避免Handler帶來的內(nèi)存溢出問題。在網(wǎng)上找了很多資料,有很多都是互相抄的,沒有實(shí)際的作用。
本文的內(nèi)存泄漏檢測工具是:LeakCanary github地址:https://github.com/square/leakcanary
什么是內(nèi)存泄漏?
內(nèi)存泄漏是當(dāng)程序不再使用到的內(nèi)存時(shí),釋放內(nèi)存失敗而產(chǎn)生了無用的內(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ì)象沒關(guān)閉造成的內(nèi)存泄漏,如查詢數(shù)據(jù)庫后沒有關(guān)閉游標(biāo)cursor
構(gòu)造Adapter時(shí),沒有使用 convertView 重用
Bitmap對(duì)象不在使用時(shí)調(diào)用recycle()釋放內(nèi)存
對(duì)象被生命周期長的對(duì)象引用,如activity被靜態(tài)集合引用導(dǎo)致activity不能釋放
內(nèi)存泄漏有什么危害?
內(nèi)存泄漏對(duì)于app沒有直接的危害,即使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 { @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 {<br> //模擬耗時(shí)操作 Thread.sleep( 15000 ); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
運(yùn)行上面的代碼后,點(diǎn)擊finish按鈕,過一會(huì)兒發(fā)生了內(nèi)存泄漏的問題。
為什么Activity6會(huì)發(fā)生內(nèi)存泄漏?
進(jìn)入Activity6 界面,然后點(diǎn)擊finish按鈕,Activity6銷毀,但是Activity6里面的線程還在運(yùn)行,匿名內(nèi)部類Runnable對(duì)象引用了Activity6的實(shí)例,導(dǎo)致Activity6所占用的內(nèi)存不能被GC及時(shí)回收。
如何改進(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 { @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添加監(jiān)聽器造成Activity內(nèi)存泄漏
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); } }
這個(gè)是在開發(fā)中經(jīng)常會(huì)犯的錯(cuò)誤,NastyManager.getInstance() 是一個(gè)單例,當(dāng)我們通過 addListener(this) 將 Activity 作為 Listener 和 NastyManager 綁定起來的時(shí)候,不好的事情就發(fā)生了。
如何改進(jìn)?
想要修復(fù)這樣的 Bug,其實(shí)相當(dāng)簡單,就是在你的 Acitivity 被銷毀的時(shí)候,將他和 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 匿名內(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(){ @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() ; } }
這段代碼運(yùn)行起來后,立即點(diǎn)擊 finish 按鈕,通過檢測,發(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ì)象無法被回收,從而導(dǎo)致了上面說的 Activity泄露。Handler 是個(gè)很常用也很有用的類,異步,線程安全等等。如果有下面這樣的代碼,會(huì)發(fā)生什么呢? handler.postDeslayed ,假設(shè) delay 時(shí)間是幾個(gè)小時(shí)… 這意味著什么?意味著只要 handler 的消息還沒有被處理結(jié)束,它就一直存活著,包含它的 Activity 就跟著活著。我們來想辦法修復(fù)它,修復(fù)的方案是 WeakReference ,也就是所謂的弱引用。垃圾回收器在回收的時(shí)候,是會(huì)忽視掉弱引用的,所以包含它的 Activity 會(huì)被正常清理掉。
如何避免
使用靜態(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 ; @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 ) ; } } } }
這個(gè)Handler已經(jīng)使用了靜態(tài)內(nèi)部類,并且使用了弱引用。但是這個(gè)并沒有完全解決 HandlerActivity 內(nèi)存泄漏的問題,罪魁禍?zhǔn)资蔷€程創(chuàng)建的方式出了問題,就像本文的第一個(gè)例子一樣。改進(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 ; @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(); } }); //創(chuàng)建Handler handler = 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) ; } @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 ) ; } } }
等等,還沒完呢?
上面這個(gè)代碼已經(jīng)有效的解決了Handler,Runnable 引用Activity實(shí)例從而導(dǎo)致內(nèi)存泄漏的問題,但是這不夠。因?yàn)閮?nèi)存泄漏的核心原因就是這個(gè)某個(gè)對(duì)象應(yīng)該被系統(tǒng)回收內(nèi)存的時(shí)候,卻被其他對(duì)象引用,造成該內(nèi)存無法回收。所以我們在寫代碼的時(shí)候,要始終繃著這個(gè)弦。再回到上面這個(gè)問題,當(dāng)當(dāng)前Activity調(diào)用finish銷毀的時(shí)候,在這個(gè)Activity里面所有線程是不是應(yīng)該在OnDestory()方法里,取消線程。當(dāng)然是否取消異步任務(wù),要看項(xiàng)目具體的需求,比如在Activity銷毀的時(shí)候,啟動(dòng)一個(gè)線程,異步寫log日志到本地磁盤,針對(duì)這個(gè)需求卻需要在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 ; @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(); } }); //創(chuàng)建Handler handler = 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) ; } @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(); //如果參數(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 { @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中創(chuàng)建了一個(gè)匿名類AsyncTask,匿名類和非靜態(tài)內(nèi)部類相同,會(huì)持有外部類對(duì)象,這里也就是activity,因此如果你在Activity里聲明且實(shí)例化一個(gè)匿名的AsyncTask對(duì)象,則可能會(huì)發(fā)生內(nèi)存泄漏,如果這個(gè)線程在Activity銷毀后還一直在后臺(tái)執(zhí)行,那這個(gè)線程會(huì)繼續(xù)持有這個(gè)Activity的引用從而不會(huì)被GC回收,直到線程執(zhí)行完成。
怎么解決?
自定義靜態(tài)AsyncTask類A
syncTask的周期和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 ; @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 { //模擬耗時(shí)操作 Thread.sleep( 15000 ); } catch (InterruptedException e) { e.printStackTrace(); } return ""; } } @Override protected 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 { @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(); } }); //開始定時(shí)任務(wù) timer(); } void timer(){ new Timer().schedule(new TimerTask() { @Override public void run() { while(true); } },1000 ); // 1秒后啟動(dòng)一個(gè)任務(wù) } }
為什么?
這里內(nèi)存泄漏在于Timer和TimerTask沒有進(jìn)行Cancel,從而導(dǎo)致Timer和TimerTask一直引用外部類Activity。
怎么解決?
在適當(dāng)?shù)臅r(shí)機(jī)進(jìn)行Cancel。
TimerTask用靜態(tài)內(nèi)部類
注意:在網(wǎng)上看到一些資料說,解決TimerTask內(nèi)存泄漏可以使用在適當(dāng)?shù)臅r(shí)機(jī)進(jìn)行Cancel。經(jīng)過測試,證明單單使用在適當(dāng)?shù)臅r(shí)機(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 ; @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(); } }); //開始定時(shí)任務(wù) timer(); } void timer(){ timerTask = new MyTimerTask() ; new Timer().schedule( timerTask ,1000 ); // 1秒后啟動(dòng)一個(gè)任務(wù) } private static class MyTimerTask extends TimerTask{ @Override public void run() { while(true){ Log.d( "ttttttttt" , "timerTask" ) ; } } } @Override protected void onDestroy() { super.onDestroy(); //取消定時(shí)任務(wù) if ( timerTask != null ){ timerTask.cancel() ; } } }
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
- Android Studio 3.0上分析內(nèi)存泄漏的原因
- Android內(nèi)存泄漏終極解決篇(下)
- Android性能優(yōu)化之利用強(qiáng)大的LeakCanary檢測內(nèi)存泄漏及解決辦法
- Android內(nèi)存泄漏終極解決篇(上)
- Android性能優(yōu)化之利用Rxlifecycle解決RxJava內(nèi)存泄漏詳解
- Android開發(fā):淺談MVP模式應(yīng)用與內(nèi)存泄漏問題解決
- 淺談Android應(yīng)用的內(nèi)存優(yōu)化及Handler的內(nèi)存泄漏問題
- Android 內(nèi)存泄漏的幾種可能總結(jié)
- 詳解Android內(nèi)存泄漏檢測與MAT使用
- Android內(nèi)存泄漏的輕松解決方法
相關(guān)文章
Android添加自定義下拉刷新布局阻尼滑動(dòng)懸停彈動(dòng)畫效果
這篇文章主要為大家介紹了Android添加自定義下拉刷新布局阻尼滑動(dòng)懸停彈動(dòng)畫效果詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Android開發(fā)中Listview動(dòng)態(tài)加載數(shù)據(jù)的方法示例
這篇文章主要介紹了Android開發(fā)中Listview動(dòng)態(tài)加載數(shù)據(jù)的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android操作ListView界面布局與數(shù)據(jù)動(dòng)態(tài)更新相關(guān)操作技巧,需要的朋友可以參考下2017-10-10Android實(shí)現(xiàn)歡迎滑動(dòng)頁面
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)歡迎滑動(dòng)頁面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04總結(jié)Android App內(nèi)存優(yōu)化之圖片優(yōu)化
網(wǎng)上有很多大拿分享的關(guān)于Android性能優(yōu)化的文章,主要是通過各種工具分析,使用合理的技巧優(yōu)化APP的體驗(yàn),提升APP的流暢度,但關(guān)于內(nèi)存優(yōu)化的文章很少有看到。下面是我在實(shí)踐過程中使用的一些方法,很多都是不太成熟的項(xiàng)目,只是將其作為一種處理方式分享給大家。2016-08-08Android手機(jī)衛(wèi)士之確認(rèn)密碼對(duì)話框
這篇文章主要為大家詳細(xì)介紹了Android手機(jī)衛(wèi)士之確認(rèn)密碼對(duì)話框,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10Android側(cè)滑效果簡單實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Android側(cè)滑效果簡單實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11