[譯]Android防止內(nèi)存泄漏的八種方法(下)

原文地址。

在上一篇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ì)的檢查,很容易解決并防范于未然井联。

是時候做最佳實踐者了卜壕!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市烙常,隨后出現(xiàn)的幾起案子轴捎,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侦副,死亡現(xiàn)場離奇詭異侦锯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)秦驯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門尺碰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汇竭,你說我怎么就攤上這事葱蝗。” “怎么了细燎?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵两曼,是天一觀的道長。 經(jīng)常有香客問我玻驻,道長悼凑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任璧瞬,我火速辦了婚禮户辫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嗤锉。我一直安慰自己渔欢,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布瘟忱。 她就那樣靜靜地躺著奥额,像睡著了一般。 火紅的嫁衣襯著肌膚如雪访诱。 梳的紋絲不亂的頭發(fā)上垫挨,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機(jī)與錄音触菜,去河邊找鬼九榔。 笑死,一個胖子當(dāng)著我的面吹牛涡相,可吹牛的內(nèi)容都是我干的哲泊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼漾峡,長吁一口氣:“原來是場噩夢啊……” “哼攻旦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起生逸,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤牢屋,失蹤者是張志新(化名)和其女友劉穎且预,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烙无,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡锋谐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了截酷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涮拗。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖迂苛,靈堂內(nèi)的尸體忽然破棺而出三热,到底是詐尸還是另有隱情,我是刑警寧澤三幻,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布就漾,位于F島的核電站,受9級特大地震影響念搬,放射性物質(zhì)發(fā)生泄漏抑堡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一朗徊、第九天 我趴在偏房一處隱蔽的房頂上張望首妖。 院中可真熱鬧,春花似錦爷恳、人聲如沸有缆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妒貌。三九已至,卻和暖如春铸豁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背菊碟。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工节芥, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逆害。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓头镊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親魄幕。 傳聞我的和親對象是個殘疾皇子相艇,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容