造成 Android 內(nèi)存泄漏的最根本原因就是生命周期較長(zhǎng)的對(duì)象持有生命周期較短的對(duì)象的引用。
1.單例造成的內(nèi)存泄漏
如果一個(gè) Activity 被一個(gè)單例對(duì)象所引用,那么當(dāng)退出這個(gè) Activity 時(shí),由于單例的對(duì)象依然存在(單例對(duì)象的生命周期跟整個(gè) App 的生命周期一致),而單例對(duì)象又持有 Activity 的引用,這就導(dǎo)致了此 Activity 無(wú)法被回收,從而造成內(nèi)存泄漏。
public class SingleTon {
private static SingleTon singleTon;
private Context context;
private SingleTon(Context context) {
this.context = context;
}
public static SingleTon getInstance(Context context) {
if (singleTon == null) {
synchronized (SingleTon.class) {
if (singleTon == null) {
singleTon = new SingleTon(context);
}
}
}
return singleTon;
}
}
這是單例模式餓漢式的雙重校驗(yàn)鎖的寫(xiě)法,這里的 singleTon 持有 Context 對(duì)象,如果 Activity 中調(diào)用 getInstance 方法并傳入 this 時(shí),singleTon 就持有了此 Activity 的引用,當(dāng)退出 Activity 時(shí),Activity 就無(wú)法回收,造成內(nèi)存泄漏。
解決方法:
private SingleTon(Context context) {
this.context = context.getApplicationContext();
}
通過(guò) getApplicationContext 來(lái)獲取 Application 的 Context,讓它被單例持有,這樣退出 Activity 時(shí),Activity 對(duì)象就能正常被回收了,而 Application 的 Context 的生命周期和單例的生命周期是一致的,所有再整個(gè) App 運(yùn)行過(guò)程中都不會(huì)造成內(nèi)存泄漏。
2.Handler 或 Runnable 作為非靜態(tài)內(nèi)部類
handler 和 runnable 都有定時(shí)器的功能,當(dāng)它們作為非靜態(tài)內(nèi)部類的時(shí)候,同樣會(huì)持有外部類的引用,如果它們的內(nèi)部有延遲操作,在延遲操作還沒(méi)有發(fā)生的時(shí)候,銷毀了外部類,那么外部類對(duì)象無(wú)法回收,從而造成內(nèi)存泄漏。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
}
}, 10 * 1000);
}
}
上面的代碼中,Handler 和 Runnable 作為匿名內(nèi)部類,都會(huì)持有 MainActivity 的引用,而它們內(nèi)部有一個(gè) 10 秒鐘的定時(shí)器,如果在打開(kāi) MainActivity 的 10 秒內(nèi)關(guān)閉了 MainActivity,那么由于 Handler 和 Runnable 的生命周期比 MainActivity 長(zhǎng),會(huì)導(dǎo)致 MainActivity 無(wú)法被回收,從而造成內(nèi)存泄漏。
解決方法:
那么應(yīng)該如何避免內(nèi)存泄漏呢?這里的一般套路就是把 Handler 和 Runnable 定義為靜態(tài)內(nèi)部類,這樣它們就不再持有 MainActivity 的引用了,從而避免了內(nèi)存泄漏:
public class MainActivity extends AppCompatActivity {
private Handler handler;
private Runnable runnable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new TestHandler();
runnable = new TestRunnable();
handler.postDelayed(runnable, 10 * 1000);
}
private static class TestHandler extends Handler {
}
private static class TestRunnable implements Runnable {
@Override
public void run() {
}
}
}
最好再在 onDestory 調(diào)用 handler 的 removeCallbacks 方法來(lái)移除 Message,這樣不但能避免內(nèi)存泄漏,而且在退出 Activity 時(shí)取消了定時(shí)器,保證 10 秒以后也不會(huì)執(zhí)行 run 方法:
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacks(runnable);
}
還有一種特殊情況,如果 Handler 或者 Runnable 中持有 Context 對(duì)象,那么即使使用靜態(tài)內(nèi)部類,還是會(huì)發(fā)生內(nèi)存泄漏:
public class MainActivity extends AppCompatActivity {
private Handler handler;
private Runnable runnable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new TestHandler(this);
runnable = new TestRunnable();
handler.postDelayed(runnable, 10 * 1000);
}
private static class TestHandler extends Handler {
private Context context;
private TestHandler(Context context) {
this.context = context;
}
}
private static class TestRunnable implements Runnable {
@Override
public void run() {
}
}
}
上面的代碼,使用 leakcanary 工具會(huì)發(fā)現(xiàn)依然會(huì)發(fā)生內(nèi)存泄漏,而且造成內(nèi)存泄漏的原因和之前用非靜態(tài)內(nèi)部類是一樣的,那么為什么會(huì)出現(xiàn)這種情況呢?
解決方法:
這是由于在 Handler 中持有 Context 對(duì)象,而這個(gè) Context 對(duì)象是通過(guò) TestHandler 的構(gòu)造方法傳入的,它是一個(gè) MainActivity 對(duì)象,也就是說(shuō),雖然 TestHandler 作為靜態(tài)內(nèi)部類不會(huì)持有外部類 MainActivity 的引用,但是我們?cè)谡{(diào)用它的構(gòu)造方法時(shí),自己傳入了 MainActivity 的對(duì)象,從而 handler 對(duì)象持有了 MainActivity 的引用,handler 的生命周期比 MainActivity 的生命周期長(zhǎng),因此會(huì)造成內(nèi)存泄漏,這種情況可以使用弱引用的方式來(lái)引用 Context 來(lái)避免內(nèi)存泄漏,代碼如下:
public class MainActivity extends AppCompatActivity {
private Handler handler;
private Runnable runnable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new TestHandler(new WeakReference<Context>(this));
runnable = new TestRunnable();
handler.postDelayed(runnable, 10 * 1000);
}
private static class TestHandler extends Handler {
private Context context;
private TestHandler(WeakReference<Context> weakContext) {
context = weakContext.get();
}
}
private static class TestRunnable implements Runnable {
@Override
public void run() {
}
}
}
3.外部類中持有非靜態(tài)內(nèi)部類的靜態(tài)對(duì)象
public class MainActivity extends AppCompatActivity {
private static Test test;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (test == null) {
test = new Test();
}
}
private class Test {
}
}
解決方法:
這個(gè)其實(shí)和單例的原理是一樣的,由于靜態(tài)對(duì)象 test 的生命周期和整個(gè)應(yīng)用的生命周期一致,而非靜態(tài)內(nèi)部類 Test 持有外部類 MainActivity 的引用,導(dǎo)致 MainActivity 退出的時(shí)候不能被回收,從而造成內(nèi)存泄漏,解決的方法也很簡(jiǎn)單,把 test 改成非靜態(tài),這樣 test 的生命周期和 MainActivity 是一樣的了,就避免了內(nèi)存泄漏?;蛘咭部梢园?Test 改成靜態(tài)內(nèi)部類,讓 test 不持有 MainActivity 的引用,不過(guò)一般沒(méi)有這種操作。
4.非靜態(tài)內(nèi)部類造成的內(nèi)存泄漏
非靜態(tài)內(nèi)部類會(huì)持有外部類的引用,如果這個(gè)非靜態(tài)的內(nèi)部類的生命周期比它的外部類的生命周期長(zhǎng),那么當(dāng)銷毀外部類的時(shí)候,它無(wú)法被回收,就會(huì)造成內(nèi)存泄漏。
解決方法:
1.通過(guò)靜態(tài)內(nèi)部類來(lái)去除隱式引用
2.修改靜態(tài)內(nèi)部類的構(gòu)造方式,手動(dòng)引入其外部類引用
3.當(dāng)內(nèi)存不可用時(shí),不執(zhí)行不可控代碼(Android可以結(jié)合智能指針,WeakReference包裹外部類實(shí)例)
(注意:并不是所有的內(nèi)部類只能使用靜態(tài)內(nèi)部類,只有在該內(nèi)部類中的生命周期不可控制的情況下,我們要采用靜態(tài)內(nèi)部類,其它時(shí)候大家可以照舊。)
5.其他內(nèi)存泄漏情況
還有一些其他的會(huì)導(dǎo)致內(nèi)存泄漏的情況,比如 BraodcastReceiver 未取消注冊(cè),InputStream 未關(guān)閉等,平時(shí)寫(xiě)代碼時(shí)多多注意即可避免。