在上一篇Android內(nèi)存泄漏的八種可能(上)中浸卦,我們討論了八種容易發(fā)生內(nèi)存泄漏的代碼署鸡。其中,尤其嚴(yán)重的是泄漏Activity
對象,因為它占用了大量系統(tǒng)內(nèi)存储玫。不管內(nèi)存泄漏的代碼表現(xiàn)形式如何侍筛,其核心問題在于:
在Activity生命周期之外仍持有其引用。
幸運(yùn)的是撒穷,一旦泄漏發(fā)生且被定位到了匣椰,修復(fù)方法是相當(dāng)簡單的。
Static Actitivities
這種泄漏
private static MainActivity activity;
void setStaticActivity() {
activity = this;
}
構(gòu)造靜態(tài)變量持有Activity
對象很容易造成內(nèi)存泄漏端礼,因為靜態(tài)變量是全局存在的禽笑,所以當(dāng)MainActivity
生命周期結(jié)束時,引用仍被持有蛤奥。這種寫法開發(fā)者是有理由來使用的佳镜,所以我們需要正確的釋放引用讓垃圾回收機(jī)制在它被銷毀的同時將其回收。
Android提供了特殊的Set
集合https://developer.android.com/reference/java/lang/ref/package-summary.html#classes
允許開發(fā)者控制引用的“強(qiáng)度”凡桥。Activity
對象泄漏是由于需要被銷毀時蟀伸,仍然被強(qiáng)引用著,只要強(qiáng)引用存在就無法被回收缅刽。
可以用弱引用代替強(qiáng)引用啊掏。
https://developer.android.com/reference/java/lang/ref/WeakReference.html.
弱引用不會阻止對象的內(nèi)存釋放,所以即使有弱引用的存在衰猛,該對象也可以被回收迟蜜。
private static WeakReference<MainActivity> activityReference;
void setStaticActivity() {
activityReference = new WeakReference<MainActivity>(this);
}
Static Views
靜態(tài)變量持有View
private static View view;
void setStaticView() {
view = findViewById(R.id.sv_button);
}
由于View
持有其宿主Activity
的引用,導(dǎo)致的問題與Activity
一樣嚴(yán)重啡省。弱引用是個有效的解決方法娜睛,然而還有另一種方法是在生命周期結(jié)束時清除引用,Activity#onDestory()
方法就很適合把引用置空卦睹。
private static View view;
@Override
public void onDestroy() {
super.onDestroy();
if (view != null) {
unsetStaticView();
}
}
void unsetStaticView() {
view = null;
}
Inner Class
這種泄漏
private static Object inner;
void createInnerClass() {
class InnerClass {
}
inner = new InnerClass();
}
與上述兩種情況相似畦戒,開發(fā)者必須注意少用非靜態(tài)內(nèi)部類,因為非靜態(tài)內(nèi)部類持有外部類的隱式引用分预,容易導(dǎo)致意料之外的泄漏兢交。然而內(nèi)部類可以訪問外部類的私有變量,只要我們注意引用的生命周期笼痹,就可以避免意外的發(fā)生配喳。
避免靜態(tài)變量
這樣持有內(nèi)部類的成員變量是可以的。
private Object inner;
void createInnerClass() {
class InnerClass {
}
inner = new InnerClass();
}
Anonymous Classes
前面我們看到的都是持有全局生命周期的靜態(tài)成員變量引起的凳干,直接或間接通過鏈?zhǔn)揭?code>Activity導(dǎo)致的泄漏晴裹。這次我們用AsyncTask
void startAsyncTask() {
new AsyncTask<Void, Void, Void>() {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}.execute();
}
Handler
void createHandler() {
new Handler() {
@Override public void handleMessage(Message message) {
super.handleMessage(message);
}
}.postDelayed(new Runnable() {
@Override public void run() {
while(true);
}
}, Long.MAX_VALUE >> 1);
}
Thread
void scheduleTimer() {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
while(true);
}
}, Long.MAX_VALUE >> 1);
}
全部都是因為匿名類導(dǎo)致的。匿名類是特殊的內(nèi)部類——寫法更為簡潔救赐。當(dāng)需要一次性特殊的子類時涧团,Java提供的語法糖能讓表達(dá)式最少化只磷。這種很贊很偷懶的寫法容易導(dǎo)致泄漏。正如使用內(nèi)部類一樣泌绣,只要不跨越生命周期钮追,內(nèi)部類是完全沒問題的。但是阿迈,這些類是用于產(chǎn)生后臺線程的元媚,這些Java線程是全局的,而且持有創(chuàng)建者的引用(即匿名類的引用)苗沧,而匿名類又持有外部類的引用刊棕。線程是可能長時間運(yùn)行的,所以一直持有Activity
的引用導(dǎo)致當(dāng)銷毀時無法回收待逞。
這次我們不能通過移除靜態(tài)成員變量解決甥角,因為線程是于應(yīng)用生命周期相關(guān)的。為了避免泄漏识樱,我們必須舍棄簡潔偷懶的寫法嗤无,把子類聲明為靜態(tài)內(nèi)部類。
靜態(tài)內(nèi)部類不持有外部類的引用牺荠,打破了鏈?zhǔn)揭谩?/p>
所以對于AsyncTask
private static class NimbleTask extends AsyncTask<Void, Void, Void> {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}
void startAsyncTask() {
new NimbleTask().execute();
}
Handler
private static class NimbleHandler extends Handler {
@Override public void handleMessage(Message message) {
super.handleMessage(message);
}
}
private static class NimbleRunnable implements Runnable {
@Override public void run() {
while(true);
}
}
void createHandler() {
new NimbleHandler().postDelayed(new NimbleRunnable(), Long.MAX_VALUE >> 1);
}
TimerTask
private static class NimbleTimerTask extends TimerTask {
@Override public void run() {
while(true);
}
}
void scheduleTimer() {
new Timer().schedule(new NimbleTimerTask(), Long.MAX_VALUE >> 1);
}
但是翁巍,如果你堅持使用匿名類,只要在生命周期結(jié)束時中斷線程就可以休雌。
private Thread thread;
@Override
public void onDestroy() {
super.onDestroy();
if (thread != null) {
thread.interrupt();
}
}
void spawnThread() {
thread = new Thread() {
@Override public void run() {
while (!isInterrupted()) {
}
}
}
thread.start();
}
Sensor Manager
這種泄漏
void registerListener() {
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}
使用Android系統(tǒng)服務(wù)不當(dāng)容易導(dǎo)致泄漏,為了Activity
與服務(wù)交互肝断,我們把Activity
作為監(jiān)聽器杈曲,引用鏈在傳遞事件和回調(diào)中形成了。只要Activity
維持注冊監(jiān)聽狀態(tài)胸懈,引用就會一直持有担扑,內(nèi)存就不會被釋放。
在Activity結(jié)束時注銷監(jiān)聽器
private SensorManager sensorManager;
private Sensor sensor;
@Override
public void onDestroy() {
super.onDestroy();
if (sensor != null) {
unregisterListener();
}
}
void unregisterListener() {
sensorManager.unregisterListener(this, sensor);
}
總結(jié)
Activity
泄漏的案例我們已經(jīng)都走過一遍了趣钱,其他都大同小異涌献。建議日后遇到類似的情況時,就使用相應(yīng)的解決方法首有。內(nèi)存泄漏只要發(fā)生過一次燕垃,通過詳細(xì)的檢查,很容易解決并防范于未然井联。
是時候做最佳實踐者了卜壕!