Android 中的 Activity Context 內存泄露,簡單說就是 Activity 調用 onDestroy()
方法銷燬後,此 Activity 還被其餘對象強引用,致使此 Activity 不能被 GC(JAVA 垃圾回收器) 回收,最後出現內存泄露。java
緣由主要有兩個:ide
問題代碼:post
static Activity activity;
void setStaticActivity() {
activity = this;
}複製代碼
這裏使用了 static 來修飾 Activity,靜態變量持有 Activity 對象很容易形成內存泄漏,由於靜態變量是和應用存活時間相同的,因此當 Activity 生命週期結束時,引用仍被持有。this
解決方法:spa
1.去掉 static 關鍵字,使用別的方法來實現想要的功能。任什麼時候候不建議 static 修飾 Activity,若是這樣作了,Android Studio 也會給出警告提示。線程
2.在 onDestroy 方法中置空 Activity 靜態引用翻譯
@Override
public void onDestroy() {
super.onDestroy();
if (activity != null) {
activity = null;
}
}複製代碼
3.也可使用到軟引用解決,確保在 Activity 銷燬時,垃圾回收機制能夠將其回收。像下面這樣作:code
private static WeakReference<MainActivity> activityReference;
private void setStaticActivity() {
activityReference = new WeakReference<MainActivity>(this);
}
// 注意在使用時,必須判空
private void useActivityReference(){
MainActivity activity = activityReference.get();
if (activity != null) {
// ...
}
}複製代碼
問題代碼:對象
public class LoadingDialog extends Dialog {
private static LoadingDialog mDialog;
private TextView mText;
}複製代碼
這裏 static 雖然沒有直接修飾 TextView(擁有 Context 引用),可是修飾了 mDialog 成員變量,mDialog 是 一個 LoadingDialog 對象, LoadingDialog 對象 包含一個 TextView 類型的成員變量,因此 mText 變量的生命週期也是全局的,和應用同樣。這樣,mText 持有的 Context 對象銷燬時,沒有 GC 回收,致使內存泄露。生命週期
解決方法:
1.不使用 static 修飾
2.在適當的地方如,dismissLoadingDialog()
方法中置空 mDialog,這樣雖然能夠解決,可是也存在風險,若是 dismissLoadingDialog()
沒有或忘記被調用,一樣也會致使內存泄漏。
public static void dismissLoadingDialog() {
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
mDialog = null;
}
}複製代碼
問題代碼:
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;
}
}複製代碼
這是一段典型的單例模式,不一樣的是 AppManager 須要 Context 做爲成員變量,和上面例子同樣,這裏延長了 Context 的存活時間,Context 若是是 Activity Context 的話,必然會引發內存泄漏。
解決方法:
1.使用 Applicaion Context 代替 Activity Context (推薦)
private AppManager(Context context) {
this.context = context.getAppcalition();
}複製代碼
或者在 App 中寫一個獲取 Applicaion Context 的方法。
private AppManager() {
this.context = App.getAppcalitionContext();
}複製代碼
2.在調用的地方使用弱引用
WeakReference<MainActivity> activityReference = new WeakReference<MainActivity>(this);
Context context = activityReference.get();
if(context != null){
AppManager.getInstance(context);
// ...
}複製代碼
問題代碼:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test();
}
public void test() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}複製代碼
因爲非靜態內部類或匿名內部類都會擁有所在外部類的引用,上邊的代碼,因爲 new Thread
是匿名內部類,而且執行了長時間(一直)的任務,當 Activity 銷燬後,該匿名內部類還在執行任務,致使外部的 Activity 不能被回收,致使內存泄露。
解決方法:
1.靜態化匿名內部類
// static 修飾方法
public static void test() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}複製代碼
若是是內部類,這樣寫:
static class MyThread extends Thread {
@Override
public void run() {
// ...
}
}複製代碼
雖然靜態內部類的生命週期和外部類無關,可是若是在內部類中想要引入外部成員變量的話,這個成員變量必須是靜態的了,這也可能致使內存泄露。
問題代碼:
public class MainActivity extends Activity {
private static MainActivity mMainActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMainActivity = this;
}
private static class MyThread extends Thread {
@Override
public void run() {
// 耗時操做
mMainActivity...
}
}
}複製代碼
解決方法:
使用弱引用。
public class MainActivity extends Activity {
private static WeakReference<MainActivity> activityReference;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
activityReference = new WeakReference<MainActivity>(this);;
}
private static class MyThread extends Thread {
@Override
public void run() {
// 耗時操做
MainActivity activity = activityReference.get();
if(activity != null){
activity...
}
}
}
}複製代碼
問題代碼:
public class MainActivity extends Activity {
private final Handler mLeakyHandler = 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();
}
}複製代碼
這種狀況和 非靜態或匿名內部類執行耗時任務 的緣由同樣。 在 MainActivity 中發送了一個延遲10分鐘執行的消息 Message,mLeakyHandler 將其 push 進了消息隊列 MessageQueue 裏。當該 Activity 被 finish() 掉時,Message 還會繼續存在於主線程中,Handler 是非靜態內部類,會持有該 MainActivity 的引用,因此此時 finish() 掉的 Activity 就不會被回收了從而形成內存泄漏。
解決方法:
自定義靜態的 Handler
public class MainActivity extends Activity {
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable mRunnable = new Runnable() {
@Override
public void run() { /* ... */ }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(mRunnable, 1000 * 60 * 10);
finish();
}
private static class MyHandler extends Handler {
private final WeakReference<MainActivity> mActivityReference;
public MyHandler(MainActivity activity) {
mActivityReference = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivityReference.get();
if (activity != null) {
// ...
}
}
}
}複製代碼
首先定義一個靜態的 MyHandler 類,它將不會隱式的持有 MainActivity 的引用,而且內部利用弱引用獲取外部類的引用,這樣在 MainActivity 被 finish 掉後,弱引用不會影響 MainActivity 被回收,也就避免了內存泄露。另外,成員變量 mRunnable 也是靜態的,生命週期和應用同樣,而且不持有外部類的引用。
Android 的 Context 內存泄露,其實就是由於 Activity 是有生命週期的,因此在 Activity 銷燬後,必須釋放掉全部對其的強引用,不然 GC 將不會及時回收已經再也不使用的 Activity,致使內存泄露。因此,咱們在使用 Activity Context 的時候,應該注意判斷下在 Activity 銷燬時此變量是否依然引用 Activity。
參考資料:
Android 內存泄漏總結
Android 內存泄漏分析心得
本文由 Bakumon 創做,採用 知識共享署名4.0 國際許可協議進行許可本站文章除註明轉載/出處外,均爲本站原創或翻譯,轉載前請務必署名