Android內(nèi)存泄漏(五):Handler

上一節(jié)我們介紹了非靜態(tài)內(nèi)部類作為靜態(tài)變量造成的內(nèi)存泄漏情況盹舞,這一節(jié)我們介紹一下Handler的使用造成的內(nèi)存泄漏情況

知識點(diǎn)

非靜態(tài)內(nèi)部類匿名類內(nèi)部類的實(shí)例都會潛在持有它們所屬的外部類的強(qiáng)引用儡遮,但是靜態(tài)內(nèi)部類卻不會

使用匿名內(nèi)部類

我們來看一段代碼:

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                textView.setText("Finished");
            }
        }, 5000000L);
    }
}

這個Activity中new Runnable()是一個匿名內(nèi)部類富雅,這個內(nèi)部類持有外部類Activity的強(qiáng)引用,內(nèi)部類被封裝成消息Message被傳遞到Handler的消息隊列MessageQueue中灵再,即消息持有Activity的強(qiáng)引用边翁。在Message消息沒有被Handler處理之前,Activity實(shí)例不會被銷毀了橄妆,于是導(dǎo)致內(nèi)存泄漏衙伶。發(fā)送postDelayed這樣的消息,你輸入延遲多少秒害碾,它就會泄露至少多少秒矢劲。而發(fā)送沒有延遲的消息的話,當(dāng)隊列中的消息過多時慌随,也會照成一個臨時的泄露芬沉。可參考Android內(nèi)存泄漏之匿名內(nèi)部類

使用靜態(tài)內(nèi)部類

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new MyRunnable(textView), 5000000L);
    }

    private static final class MyRunnable implements Runnable {
        private final TextView mTextView;

        protected MyRunnable(TextView textView) {
            mTextView = textView;
        }

        @Override
        public void run() {
            mTextView.setText("Finished");
        }
    }
}

這段代碼中阁猜,我們使用的是靜態(tài)內(nèi)部類丸逸,那么這樣還會內(nèi)存泄漏嗎?
答案:會
上面知識點(diǎn)中我們提到了靜態(tài)內(nèi)部類不會持有外部類的引用剃袍,那么為什么這里還會內(nèi)存泄漏呢黄刚。
因?yàn)門extView持有Activity的強(qiáng)引用,我們都知道View都持有Context的引用民效,這里的Context就是Activity憔维。new MyRunnable(textView)持有TextView的強(qiáng)引用涛救,這樣MyRunnable也就持有Activity的強(qiáng)引用了,所以消息為處理之前埋同,Activity實(shí)例不會被銷毀州叠,于是導(dǎo)致內(nèi)存泄漏。

解決方案1:弱引用+靜態(tài)內(nèi)部類

匿名內(nèi)部類因?yàn)槌钟蠥ctivity的強(qiáng)引用凶赁,所以會導(dǎo)致內(nèi)存泄漏咧栗。
靜態(tài)內(nèi)部類中的TextView持有Activity的強(qiáng)引用,所以也會導(dǎo)致內(nèi)存泄漏
Android內(nèi)存泄漏和引用的關(guān)系中虱肄,我們有講到弱引用:如果一個對象具有弱引用致板,在GC線程掃描內(nèi)存區(qū)域的過程中,不管當(dāng)前內(nèi)存空間足夠與否咏窿,都會回收內(nèi)存斟或。
代碼如下:

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);

        handler.postDelayed(new MyRunnable(textView), 5000000L);
    }

    private static final class MyRunnable implements Runnable {
        private final WeakReference<TextView> wr;

        protected MyRunnable(TextView textView) {
            wr = new WeakReference<TextView>(textView);
        }

        @Override
        public void run() {
            final TextView tv = wr.get();
            if (tv != null) {
                tv.setText("Finished");
            }
        }
    }
}

這里我們把靜態(tài)內(nèi)部類的TextView改成弱引用了,這樣雖然textView持有activity的強(qiáng)引用集嵌,但是new MyRunnable(textView)持有的是TextView的弱引用萝挤,這樣MyRunnable持有Activity的引用也是弱引用,所以內(nèi)存回收的時候根欧,是可以回收的怜珍,我們可以通過wr.get()是否為空判斷是否已經(jīng)回收。

解決方案2:在onDestory的時候凤粗,手動清除Message

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                textView.setText("Finished");
            }
        }, 5000000L);
    }

    @Override
    protected void onDestroy() {
        handler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }
}

如上方法酥泛,我們在ondestory的時候,清除所有未處理的Message嫌拣,就不會有哪個消息持有Activity的強(qiáng)引用了柔袁,這樣也不會導(dǎo)致內(nèi)存泄漏。

解決方案3:使用第三方控件WeakHandler

WeakHandler是一個第三方庫异逐,我們看看他是怎么使用的:

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private WeakHandler handler = new WeakHandler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                textView.setText("Finished");
            }
        }, 5000000L);
    }
}

它用起來很簡單捶索,不需要考慮弱應(yīng)用的情況,你只需要把以前的Handler替換成WeakHandler就行了应役。
我們看看WeakHandler的源代碼:

private final WeakHandler.ExecHandler mExec;
public final boolean postDelayed(Runnable r, long delayMillis) {
    return this.mExec.postDelayed(this.wrapRunnable(r), delayMillis);
}
private WeakHandler.WeakRunnable wrapRunnable(@NonNull Runnable r) {
    if(r == null) {
        throw new NullPointerException("Runnable can\'t be null");
    } else {
        WeakHandler.ChainedRef hardRef = new WeakHandler.ChainedRef(this.mLock, r);
        this.mRunnables.insertAfter(hardRef);
        return hardRef.wrapper;
    }
}
static class WeakRunnable implements Runnable {
    private final WeakReference<Runnable> mDelegate;
    private final WeakReference<WeakHandler.ChainedRef> mReference;

    WeakRunnable(WeakReference<Runnable> delegate, WeakReference<WeakHandler.ChainedRef> reference) {
        this.mDelegate = delegate;
        this.mReference = reference;
    }

    public void run() {
        Runnable delegate = (Runnable)this.mDelegate.get();
        WeakHandler.ChainedRef reference = (WeakHandler.ChainedRef)this.mReference.get();
        if(reference != null) {
            reference.remove();
        }

        if(delegate != null) {
            delegate.run();
        }

    }
}
private static class ExecHandler extends Handler {
    private final WeakReference<Callback> mCallback;

    ExecHandler() {
        this.mCallback = null;
    }

    ExecHandler(WeakReference<Callback> callback) {
        this.mCallback = callback;
    }

    ExecHandler(Looper looper) {
        super(looper);
        this.mCallback = null;
    }

    ExecHandler(Looper looper, WeakReference<Callback> callback) {
        super(looper);
        this.mCallback = callback;
    }

    public void handleMessage(@NonNull Message msg) {
        if(this.mCallback != null) {
            Callback callback = (Callback)this.mCallback.get();
            if(callback != null) {
                callback.handleMessage(msg);
            }
        }
    }
}

源代碼中可以清楚的看到它將Handler和Runnable做了弱引用封裝情组,而ExecHandler和WeakRunnable也就是封裝之后的內(nèi)部類。

上一節(jié):Android內(nèi)存泄漏(四):非靜態(tài)內(nèi)部類作為靜態(tài)變量

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末箩祥,一起剝皮案震驚了整個濱河市院崇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌袍祖,老刑警劉巖底瓣,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡捐凭,警方通過查閱死者的電腦和手機(jī)拨扶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茁肠,“玉大人患民,你說我怎么就攤上這事】寻穑” “怎么了匹颤?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長托猩。 經(jīng)常有香客問我印蓖,道長,這世上最難降的妖魔是什么京腥? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任赦肃,我火速辦了婚禮,結(jié)果婚禮上公浪,老公的妹妹穿的比我還像新娘他宛。我一直安慰自己,他們只是感情好欠气,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布堕汞。 她就那樣靜靜地躺著,像睡著了一般晃琳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上琐鲁,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天卫旱,我揣著相機(jī)與錄音,去河邊找鬼围段。 笑死顾翼,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奈泪。 我是一名探鬼主播适贸,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼涝桅!你這毒婦竟也來了拜姿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤冯遂,失蹤者是張志新(化名)和其女友劉穎蕊肥,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蛤肌,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡壁却,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年批狱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片展东。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡赔硫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盐肃,到底是詐尸還是另有隱情爪膊,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布恼蓬,位于F島的核電站惊完,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏处硬。R本人自食惡果不足惜小槐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荷辕。 院中可真熱鬧凿跳,春花似錦、人聲如沸疮方。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骡显。三九已至疆栏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惫谤,已是汗流浹背壁顶。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留溜歪,地道東北人若专。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像蝴猪,于是被迫代替她去往敵國和親调衰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359

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

  • 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題自阱。內(nèi)存泄漏大家都不陌生了嚎莉,簡單粗俗的講,...
    宇宙只有巴掌大閱讀 2,364評論 0 12
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題沛豌。內(nèi)存泄漏...
    _痞子閱讀 1,639評論 0 8
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題萝喘。內(nèi)存泄漏...
    apkcore閱讀 1,222評論 2 7
  • 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏大家都不陌生了,簡單粗俗的講阁簸,...
    DreamFish閱讀 795評論 0 5
  • 金指尖的花園閱讀 358評論 0 2