Handler的使用、內(nèi)存泄漏和解決

Handler的使用:

?Handler是Android線程間通訊的一種方式,它常被我們用來更新UI,是的,我是這么用,還有延時,只有拿出來總結(jié)的時候,才會發(fā)現(xiàn)有時候使用的時候是有缺漏的。所以總結(jié)很重要啊!
?目前為止總結(jié)的一些使用情況如下:
?1.子線程發(fā)送消息到主線程
?2.在子線程中更新UI
?3.在子線程中使用Handler
?4.使用HandlerThread
?5.Handler的callback回調(diào)

  1. 子線程發(fā)送消息到主線程
Handler mainHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            Toast.makeText(HandlerActivity.this, "接收到啦", Toast.LENGTH_SHORT).show();

        }
    };
new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                /*子線程傳給主線程*/
               mainHandler.sendEmptyMessage(0);
             }
        }).start();

?這里是在子線程中Handler對象發(fā)送一個空消息,然后在handleMessage方法中進(jìn)行操作,此時Toast執(zhí)行已經(jīng)是在UI線程了。
?然后剛剛測試了一下,不僅僅是子線程往主線程發(fā)消息,主線程也可以向子線程發(fā)消息,子線程也可以向子線程發(fā)消息,自己手動去試一下才會理解Handler這個線程間通信是怎么回事。


  1. 在子線程中更新UI
 Handler updateHandler = new Handler();
    new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                /*在子線程中更新UI*/
                updateHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        /*
                         *更新UI的操作
                         * */
                    }
                });
            }
        }).start();

?這里的代碼都是一些局部的代碼塊,這里的updateHandler是在主線程聲明的,子線程是開在主線程下的, 然后updateHandler對象在子線程使用post方法,new了一個Runnable去切換線程到主線程執(zhí)行更新UI的代碼。當(dāng)然,也可以像上面那樣發(fā)送一個消息在Handler的handleMessage里更新UI喔~!


  1. 在子線程中使用Handler

匿名內(nèi)部類實現(xiàn)

new Thread(new Runnable() {//創(chuàng)建一個子線程
            @Override
            public void run() {

                Looper.prepare();//創(chuàng)建與當(dāng)前線程相關(guān)的Looper
                myThreadTwoHandler = new Handler() {    //創(chuàng)建一個子線程里的Handler
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        //log日志測試結(jié)果,是在子線程的Handler
                        Log.e(TAG, "當(dāng)前存在線程為" + Thread.currentThread().getName());
                    }
                };
                Looper.loop();//調(diào)用此方法,消息才會循環(huán)處理
            }
        }).start();

        myThreadTwoHandler.sendEmptyMessageDelayed(0, 5000);//主線程調(diào)用子線程的Handler對象發(fā)送消息

子類繼承Thread實現(xiàn)

//MyThread 子類繼承 Thread
 public class MyThread extends Thread {
        public Looper childLooper;

        @Override
        public void run() {
            Looper.prepare();//創(chuàng)建與當(dāng)前線程相關(guān)的Looper
            childLooper = Looper.myLooper();//獲取當(dāng)前線程的Looper對象
            Looper.loop();//調(diào)用此方法,消息才會循環(huán)處理
        }
    }
  /*在子線程使用Handler*/
        MyThread myThread = new MyThread();
        myThread.start();
        myThreadHandler = new Handler(myThread.childLooper) {//與MyThread線程綁定
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.e(TAG, "當(dāng)前存在線程為" + Thread.currentThread().getName());
            }
        };
        //主線程調(diào)用子線程的Handler對象發(fā)送消息
        myThreadHandler.sendEmptyMessageDelayed(0, 5000);

HandlerThread實現(xiàn)

        HandlerThread handlerThread = new HandlerThread("ceshi");
        handlerThread.start();
        //通過HandlerThread的getLooper方法可以獲取Looper
        Looper looper = handlerThread.getLooper();
        //通過Looper我們就可以創(chuàng)建子線程的handler了
        Handler handler = new Handler(looper) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //測試結(jié)果是在ceshi這個線程內(nèi)
                Log.e(TAG, "這個是HandlerThread線程哦 : " + Thread.currentThread().getName());
            }
        };
        handler.sendEmptyMessageDelayed(0, 1000);
  1. 使用HandlerThread

?HandlerThread上面已經(jīng)展示過代碼了,為了和其他兩種對比,我就把HandlerThread實現(xiàn)Handler的使用寫在上面的部分了。HandlerThread其實就是系統(tǒng)封裝好的Thread子類,和自己封裝的子類不同的是,HandlerThread里面做了更好的判斷來避免一些問題。
?例如:Only one Looper may be created per thread。這個問題我在自己寫MyThread這個子類并使用的時候,子類對象調(diào)用run就報了這個錯誤,調(diào)用start可以運行了。目前為止,我覺得HandlerThread這個子類相對自己寫來說,會比較好用,在實際的項目操作中,如何需要,應(yīng)該還是看具體需求了。目前是這樣,以后有了新的理解進(jìn)行更新。

  1. Handler的Callback回調(diào)

?其實這個Handler還有一個Callback的回調(diào)這個東西,我一開始并不知道的,當(dāng)時應(yīng)該是從大佬同事那里了解到這個東西的時候,感覺很神奇,又感覺很懵逼,為什么會有布爾類型的返回值?然后在我的實驗中發(fā)現(xiàn),咦?true和false的結(jié)果是一樣的?腦子里更懵逼了,這是什么???黑人問號臉...

 Handler callBackHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            Toast.makeText(HandlerActivity.this, "這里是Callback", Toast.LENGTH_SHORT).show();
            /*
             * callback的返回值
             * */
            return true;
        }
    });

?對,就是這樣,我的Toast不論true還是false,完全沒有影響,照彈不誤!為了內(nèi)心世界的和平,肯定不能放棄治療!源碼什么的,對不起,一開始我肯定選擇了百度。百度果然是沒有辜負(fù)我的期望,什么有用的都沒有找到,找到的都是一模一樣的無數(shù)個copy的沒有用的文字~~~
?然后,在我努力追求真理的情況下,大佬告訴我其實是這樣的。。。

 Handler callBackHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            Toast.makeText(HandlerActivity.this, "這里是Callback", Toast.LENGTH_SHORT).show();
            /*
             * callback的返回值
             * */
            return true;
        }
    }) {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.e(TAG, "handleMessage: 返回true不處理");
        }
    };

?對,沒有看錯!handleMessage有兩個,一個屬于Callback,一個屬于Handler。
?然后,讓我們和源碼結(jié)合一下~~~

//這里是Handler的源碼
 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

?然后其實一看就恍然大悟了,返回值為true的時候直接return掉了,不會去執(zhí)行Handler的handleMessage這個方法了,如果為false的話就會執(zhí)行啦,當(dāng)然,好像默認(rèn)情況都是執(zhí)行false。
?這里也是暫時理解是這樣的,對于它的應(yīng)用場景,具體需求,以后遇到了再更新!
?哦對了,還有AndroidStudio中,我們平常使用的Handler其實都會可能導(dǎo)致內(nèi)存泄露的,AS會標(biāo)黃色,表示警告。然后在使用這個Handler.Callback的時候,我一開始發(fā)現(xiàn)警告消除掉了。其實并不是,在只使用Callback的handleMessage方法時,是沒有警告的,但是加上Handler的handleMessage后,警告就有了,所以,網(wǎng)上有的人說的Handler.Callback會解決內(nèi)存泄露,是錯誤的,沒有警告了只是寫法有問題罷了,哈哈哈哈哈!


Handler的內(nèi)存泄露

?Handler的內(nèi)存泄漏,一開始讓我注意到這個問題的時候,是我的AS報警告。更新以后的AS,對于Handler的這個警告,不要太兇喔,一報就是一大片屎黃色,咦~~~受不了
?比如說這樣:


image.png

?然后就開始探究了啊,為什么會導(dǎo)致內(nèi)存泄露呢?哈哈哈,上面圖片里其實有說啦,這種Handler的使用其實就是將Handler聲明為Activity的內(nèi)部。而在Java語言中,非靜態(tài)內(nèi)部類會持有外部類的一個隱式引用,所以,Handler會持有Activity的引用啦,然后就會有可能造成外部類,也就是Activity無法被回收,導(dǎo)致內(nèi)存泄露~~~
?然后,經(jīng)過詢問等等等方法探究了他人的經(jīng)驗之后,發(fā)現(xiàn)其實很多開發(fā)一直使用的是可能會導(dǎo)致內(nèi)存泄露的版本的Handler,因為正面看上去并不會影響App的運行,不像圖像過大會導(dǎo)致OOM這種很致命的問題就會讓很多忽視,或者說沒有注意到這個問題,比如了解這個問題之前的那個我~
?那么,如何避免內(nèi)存泄漏,使用正確的Handler呢?先看一下AS給我們的建議

This Handler class should be static or leaks might occur (anonymous android.os.Handler) less (Ctrl+F1) 
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. 
If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. 
If the Handler is using the Looper or MessageQueue of the main thread, 
you need to fix your Handler declaration, as follows: Declare the Handler as a static class; 
In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler;
 Make all references to members of the outer class using the WeakReference object.

?哇,全是英文吶,扔谷歌翻譯進(jìn)化一下~

這個處理程序類應(yīng)該是靜態(tài)的或可能發(fā)生泄漏(匿名android.os.Handler)少...(按Ctrl+ F1)
由于此Handler被聲明為內(nèi)部類,因此可能會阻止外部類被垃圾收集。
如果處理程序?qū)χ骶€程以外的線程使用Looper或MessageQueue,則沒有問題。 
如果Handler使用主線程的Looper或MessageQueue,則需要修復(fù)Handler聲明,
如下所示:將Handler聲明為靜態(tài)類; 在外部類中,實例化WeakReference到外部類并在實例化Handler時將此對象傳遞給Handler; 
使用WeakReference對象創(chuàng)建對外部類成員的所有引用。

?然后我們就開始搞我們的正確姿勢啦~


正確使用Handler,避免內(nèi)存泄露
  • 使用靜態(tài)的匿名內(nèi)部類,并持有外部類的弱引用

?聲明靜態(tài)的Handler內(nèi)部類,持有外部類的弱引用,通過外部類實例去引用外部類的各種控件實例,參數(shù)實例等等。然后當(dāng)GC回收時,因為外部類是弱引用,所以會被回收。

/**
     * 聲明一個靜態(tài)的Handler內(nèi)部類,并持有外部類的弱引用
     */
    private static class MyHandler extends Handler {

        private final WeakReference<HandlerActivity> mActivty;

        private MyHandler(HandlerActivity mActivty) {
            this.mActivty = new WeakReference<>(mActivty);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            HandlerActivity activity = mActivty.get();
            if (activity != null) {
                Log.e("eee", "handleMessage: " + Thread.currentThread().getName());
            }
        }
    }

?在外部類中聲明MyHandler對象

 private final MyHandler mHandler = new MyHandler(this);

?然后調(diào)用發(fā)送消息,post的方式和sendMessage的方式

mHandler.post(sRunnable);
mHandler.sendMessage(message);

?如果使用sendMessage方法的話,會被MyHandler的 handleMessage方法接收。那么,若使用post方法的話,我們還需要聲明一個靜態(tài)的Runable來完成我們的post

 /**
     * 靜態(tài)的匿名內(nèi)部類不會持有外部類的引用
     */
    private static final Runnable sRunnable = new Runnable() {
        @Override
        public void run() {
            // ...你的操作
            Log.e(TAG, "這里是run");
        }
    };

結(jié)束語

?還有很多沒有講到的,講到的也都感覺沒有說的很徹底,先寫到這里,然后多鉆研再修改更新~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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