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

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

知識(shí)點(diǎn)

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

使用匿名內(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);
    }
}

這個(gè)Activity中new Runnable()是一個(gè)匿名內(nèi)部類,這個(gè)內(nèi)部類持有外部類Activity的強(qiáng)引用,內(nèi)部類被封裝成消息Message被傳遞到Handler的消息隊(duì)列MessageQueue中,即消息持有Activity的強(qiáng)引用。在Message消息沒有被Handler處理之前,Activity實(shí)例不會(huì)被銷毀了,于是導(dǎo)致內(nèi)存泄漏。發(fā)送postDelayed這樣的消息,你輸入延遲多少秒,它就會(huì)泄露至少多少秒。而發(fā)送沒有延遲的消息的話,當(dāng)隊(duì)列中的消息過多時(shí),也會(huì)照成一個(gè)臨時(shí)的泄露。可參考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)部類,那么這樣還會(huì)內(nèi)存泄漏嗎?
答案:會(huì)
上面知識(shí)點(diǎn)中我們提到了靜態(tài)內(nèi)部類不會(huì)持有外部類的引用,那么為什么這里還會(huì)內(nèi)存泄漏呢。
因?yàn)門extView持有Activity的強(qiáng)引用,我們都知道View都持有Context的引用,這里的Context就是Activity。new MyRunnable(textView)持有TextView的強(qiáng)引用,這樣MyRunnable也就持有Activity的強(qiáng)引用了,所以消息為處理之前,Activity實(shí)例不會(huì)被銷毀,于是導(dǎo)致內(nèi)存泄漏。

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

匿名內(nèi)部類因?yàn)槌钟蠥ctivity的強(qiáng)引用,所以會(huì)導(dǎo)致內(nèi)存泄漏。
靜態(tài)內(nèi)部類中的TextView持有Activity的強(qiáng)引用,所以也會(huì)導(dǎo)致內(nèi)存泄漏
Android內(nèi)存泄漏和引用的關(guān)系中,我們有講到弱引用:如果一個(gè)對(duì)象具有弱引用,在GC線程掃描內(nèi)存區(qū)域的過程中,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收內(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)存回收的時(shí)候,是可以回收的,我們可以通過wr.get()是否為空判斷是否已經(jīng)回收。

解決方案2:在onDestory的時(shí)候,手動(dòng)清除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();
    }
}

如上方法,我們?cè)趏ndestory的時(shí)候,清除所有未處理的Message,就不會(huì)有哪個(gè)消息持有Activity的強(qiáng)引用了,這樣也不會(huì)導(dǎo)致內(nèi)存泄漏。

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

WeakHandler是一個(gè)第三方庫,我們看看他是怎么使用的:

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)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏大家都不陌生了,簡單粗俗的講,...
    宇宙只有巴掌大閱讀 2,494評(píng)論 0 12
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏...
    _痞子閱讀 1,703評(píng)論 0 8
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏...
    apkcore閱讀 1,310評(píng)論 2 7
  • 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏大家都不陌生了,簡單粗俗的講,...
    DreamFish閱讀 875評(píng)論 0 5
  • 指尖蝶舞的花園閱讀 412評(píng)論 0 2

友情鏈接更多精彩內(nèi)容