1,內存泄漏到本質是該釋放的對象被持久化的對象引用了,造成持久化的常見情況有1,靜態持久化 2,線程持久化
線程持久化
因為存活的線程是有dvk虛擬久直接持有,所以存活的線程都是持久化的
內存泄漏1:靜態Activities(static Activities)
代碼如下:
MainActivity.Java
public class MainActivity extends AppCompatActivity { private static MainActivity activity; TextView saButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); saButton = (TextView) findViewById(R.id.text); saButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setStaticActivity(); nextActivity(); } }); } void setStaticActivity() { activity = this; } void nextActivity(){ startActivity(new Intent(this,RegisterActivity.class)); SystemClock.sleep(1000); finish(); } @Override protected void onDestroy() { super.onDestroy(); //使用LeakCanary觀察是否有內存泄漏 MyApplication.getRefWatcher().watch(this); } }
LeakCanary檢測出的內存泄漏:
為什么?
在上面代碼中,我們聲明了一個靜態的Activity變量並且在TextView的OnClick事件里引用了當前正在運行的Activity實例,所以如果在activity的生命周期結束之前沒有清除這個引用,則會引起內存泄漏。因為聲明的activity是靜態的,會常駐內存,如果該對象不清除,則垃圾回收器無法回收變量。
怎么解決?
最簡單的方法是在onDestory方法中將靜態變量activity置空,這樣垃圾回收器就可以將靜態變量回收。
@Override protected void onDestroy() { super.onDestroy(); activity = null; //使用LeakCanary觀察是否有內存泄漏 MyApplication.getRefWatcher().watch(this); }
不使用靜態activity,或給靜態activity賦值時,考慮賦值的activity生命周期是不是全局的,或者在靜態activity使用完后及時釋放
內存泄漏2:靜態View
代碼如下:
MainActivity.java
...
private static View view; TextView saButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); saButton = (TextView) findViewById(R.id.text); saButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setStaticView(); nextActivity(); } }); } void setStaticView() { view = findViewById(R.id.sv_view); } ...
LeakCanary檢測到的內存泄漏
為什么?
上面代碼看似沒有問題,在Activity里聲明一個靜態變量view,然后初始化,當Activity生命周期結束了內存也釋放了,但是LeakCanary卻顯示出現了內存泄漏,為什么?問題出在這里,View一旦被加載到界面中將會持有一個Context對象的引用,在這個例子中,這個context對象是我們的Activity,聲明一個靜態變量引用這個View,也就引用了activity,所以當activity生命周期結束了,靜態View沒有清除掉,還持有activity的引用,因此內存泄漏了。
怎么解決?
在onDestroy方法里將靜態變量置空。
@Override protected void onDestroy() { super.onDestroy(); view = null; MyApplication.getRefWatcher().watch(this); }
不使用靜態view,或在activity關閉時將靜態view賦值為null
內存泄漏3:靜態內部類
代碼如下:
MainActivity.java
private static Object inner; void createInnerClass() { class InnerClass { } inner = new InnerClass(); } View icButton = findViewById(R.id.ic_button); icButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { createInnerClass(); nextActivity(); } });
使用LeakCanary檢測到的內存泄漏:
為什么?
非靜態內部類會持有外部類的引用,在上面代碼中內部類持有Activity的引用,因此inner會一直持有Activity,如果Activity生命周期結束沒有清除這個引用,這樣就發生了內存泄漏。
怎么解決?
因為非靜態內部類隱式持有外部類的強引用,所以我們將內部類聲明成靜態的就可以了。
void createInnerClass() { static class InnerClass { } inner = new InnerClass(); }
內存泄漏3:靜態Drawable
當一個Drawable附加到一個 View上時,
View會將其作為一個callback設定到Drawable上。意味着Drawable擁有一個View的引用,上面說了view會有上下文的引用
內存泄漏4:靜態集合中對象沒清理造成的內存泄漏
我們通常把一些對象的引用加入到了集合中,當我們不需要該對象時,並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。
內存泄漏5:單例導致內存泄露
單例模式在Android開發中會經常用到,但是如果使用不當就會導致內存泄露。因為單例的靜態特性使得它的生命周期同應用的生命周期一樣長,如果一個對象已經沒有用處了,但是單例還持有它的引用,那么在整個應用程序的生命周期它都不能正常被回收,從而導致內存泄露。
public class AppSettings { private static AppSettings sInstance; private Context mContext; private AppSettings(Context context) { this.mContext = context; } public static AppSettings getInstance(Context context) { if (sInstance == null) { sInstance = new AppSettings(context); } return sInstance; } }
像上面代碼中這樣的單例,如果我們在調用getInstance(Context context)
方法的時候傳入的context
參數是Activity
、Service
等上下文,就會導致內存泄露。
以Activity
為例,當我們啟動一個Activity
,並調用getInstance(Context context)
方法去獲取AppSettings
的單例,傳入Activity.this
作為context
,這樣AppSettings
類的單例sInstance
就持有了Activity
的引用,當我們退出Activity
時,該Activity
就沒有用了,但是因為sIntance
作為靜態單例(在應用程序的整個生命周期中存在)會繼續持有這個Activity
的引用,導致這個Activity
對象無法被回收釋放,這就造成了內存泄露。
為了避免這樣單例導致內存泄露,我們可以將context
參數改為全局的上下文:
private AppSettings(Context context) { this.mContext = context.getApplicationContext(); }
全局的上下文Application Context
就是應用程序的上下文,和單例的生命周期一樣長,這樣就避免了內存泄漏。
單例模式對應應用程序的生命周期,所以我們在構造單例的時候盡量避免使用Activity
的上下文,而是使用Application
的上下文。