Android 有效解決記憶體流失的問題
Android記憶體流失,我想做Android 應用的時候遇到的話很是頭疼,這裡是我在網上找的不錯的資料,執行個體詳解這個問題的解決方案
前言:最近在研究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 {<br> //類比耗時操作 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類A
syncTask的周期和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() ; } }}
感謝閱讀,希望能協助到大家,謝謝大家對本站的支援!