Android 記憶體洩漏問題
1. 內部類引用導致Activity的洩露
在Android中內部類的引用最常見的是handler,我們經常會這樣寫:
private Handler handler = new Handler(){
@Override
public void handlerMessage(Message msg){
}
}
但這樣寫會造成嚴重的記憶體洩漏,編寫中會有如下警告:意思是,Handler應該是static型別的,否則可能會造成記憶體洩漏。
This Handler class should be static or leaks might occur(com.example.progress.MainActivity.)
警告的原因是:Android應用層是java編寫的 ,在java中,“非靜態類”或“匿名類”都是持有他們所在外部類的一個引用。也就是說這裡會持有一個activity的引用。而Handler在Android中是用來做訊息通訊的,比如 介面更新 網路操作 都可能通過handler來進行訊息傳遞。
public class MainActivity extends Activity { private final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //釋出訊息並將其執行延遲 mLeakyHandler.postDelayed(new Runnable() { @Override public void run() { /* ... */ } }, 1000 * 60*10 ); finish(); } }
當activity 完成finish()時,延時訊息在得到處理前,會一直儲存在主執行緒的訊息佇列裡持續10分鐘。由上文可知handler隱式的持有MainActivity的引用。這條引用關係會一直保持,直到訊息得到處理,從而阻止了MainActivity被垃圾回收器回收,造成了應用程式的洩漏。
- 只要有未處理訊息,訊息就會引用handler,非靜態handler會引用外部類,即Activity,導致Activity無法被回收,造成洩漏;
- Handler類屬於非靜態匿名類,同樣會引用外部類。
解決方法有2種:
(1)靜態內部類不會持有外部類的引用,所以,我們可以把handler類放在單獨的類檔案中,或者使用靜態內部類避免洩漏。
(2)如果想要在handler內部去呼叫所在的外部類Activity,可以在handler內部使用弱引用的方式指向所在Activity,這樣統一不會導致記憶體洩漏。(弱引用的解決方式,請看:https://blog.csdn.net/weixin_38327420/article/details/83098174)
public class MainActivity extends Activity {
/**
* 靜態內部類的例項不包含對其外部類的隱式引用
*/
private static class MyHandler extends Handler {
private final WeakReference<MainActivity> mActivity;
public MyHandler(MainActivity activity) {
mActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
/**
* 靜態內部類不會對其外部類進行隱式引用
*/
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() { /* ... */ }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
finish();
}
}
注意:在Android開發中,如果一個內部類例項的生命週期比Activity更長,那麼我們千萬不要使用非靜態的內部類。最好是,使用靜態內部類,然後在該類裡使用弱引用來指向所在的Activity。
2. 單例模式造成的記憶體洩漏
這是一種簡單的單例模式,先不評論是否執行緒安全,關鍵是傳入的context引數。
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
- 若傳入的是Application的Context:將沒有什麼問題,因為單例的生命週期和Application的一樣長 ;
- 若傳入的是Activity的Context:當這個Context所對應的Activity退出時,由於該Context和Activity的生命週期一樣長(Activity間接繼承於Context),所以當前Activity退出時它的記憶體並不會被回收,因為單例物件持有該Activity的引用。這樣就造成了記憶體洩漏的問題。
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
解決方法是:傳入applicationContext,使得單例的生命週期和應用的生命週期一樣長。
3. 非靜態內部類造成的記憶體洩漏
這個和第一種Handler造成的記憶體洩漏有點類似,因為非靜態內部類預設會持有外部類的引用,而又使用了該非靜態內部類建立了一個靜態的例項,該例項的生命週期和應用一樣長,這就導致了該靜態例項一直會持有該Activity的引用,導致Activity的記憶體資源不能正常回收。
4. 執行緒造成的記憶體洩漏
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}.execute();
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
如果上面的非同步執行緒和Thread都在Activity中執行,且執行的是耗時任務,若Activity被finish,但是Thread和非同步任務沒有執行完成,還持有外部Activity的隱式應用,這就導致activity不能被回收,造成記憶體洩漏。
static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();
解決辦法:不在Activity中執行耗時操作,可以避免Activity的記憶體資源洩漏。
注意:在Activity銷燬時,應該取消相應的任務 AsyncTask::cancel(),避免任務在後臺執行浪費資源。
5. 資源未關閉造成的記憶體洩漏
在使用BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源時,應該在Activity銷燬時及時關閉或者登出,否則這些資源將不會被回收,造成記憶體洩漏。