Handler的使用、記憶體洩漏和解決

weixin_34320159發表於2018-06-14

Handler的使用:

 Handler是Android執行緒間通訊的一種方式,它常被我們用來更新UI,是的,我是這麼用,還有延時,只有拿出來總結的時候,才會發現有時候使用的時候是有缺漏的。所以總結很重要啊!
 目前為止總結的一些使用情況如下:
 1.子執行緒傳送訊息到主執行緒
 2.在子執行緒中更新UI
 3.在子執行緒中使用Handler
 4.使用HandlerThread
 5.Handler的callback回撥

  1. 子執行緒傳送訊息到主執行緒
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物件傳送一個空訊息,然後在handleMessage方法中進行操作,此時Toast執行已經是在UI執行緒了。
 然後剛剛測試了一下,不僅僅是子執行緒往主執行緒發訊息,主執行緒也可以向子執行緒發訊息,子執行緒也可以向子執行緒發訊息,自己手動去試一下才會理解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去切換執行緒到主執行緒執行更新UI的程式碼。當然,也可以像上面那樣傳送一個訊息在Handler的handleMessage裡更新UI喔~!


  1. 在子執行緒中使用Handler

匿名內部類實現

new Thread(new Runnable() {//建立一個子執行緒
            @Override
            public void run() {

                Looper.prepare();//建立與當前執行緒相關的Looper
                myThreadTwoHandler = new Handler() {    //建立一個子執行緒裡的Handler
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        //log日誌測試結果,是在子執行緒的Handler
                        Log.e(TAG, "當前存線上程為" + Thread.currentThread().getName());
                    }
                };
                Looper.loop();//呼叫此方法,訊息才會迴圈處理
            }
        }).start();

        myThreadTwoHandler.sendEmptyMessageDelayed(0, 5000);//主執行緒呼叫子執行緒的Handler物件傳送訊息

子類繼承Thread實現

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

        @Override
        public void run() {
            Looper.prepare();//建立與當前執行緒相關的Looper
            childLooper = Looper.myLooper();//獲取當前執行緒的Looper物件
            Looper.loop();//呼叫此方法,訊息才會迴圈處理
        }
    }
  /*在子執行緒使用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, "當前存線上程為" + Thread.currentThread().getName());
            }
        };
        //主執行緒呼叫子執行緒的Handler物件傳送訊息
        myThreadHandler.sendEmptyMessageDelayed(0, 5000);

HandlerThread實現

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

 HandlerThread上面已經展示過程式碼了,為了和其他兩種對比,我就把HandlerThread實現Handler的使用寫在上面的部分了。HandlerThread其實就是系統封裝好的Thread子類,和自己封裝的子類不同的是,HandlerThread裡面做了更好的判斷來避免一些問題。
 例如:Only one Looper may be created per thread。這個問題我在自己寫MyThread這個子類並使用的時候,子類物件呼叫run就報了這個錯誤,呼叫start可以執行了。目前為止,我覺得HandlerThread這個子類相對自己寫來說,會比較好用,在實際的專案操作中,如何需要,應該還是看具體需求了。目前是這樣,以後有了新的理解進行更新。

  1. Handler的Callback回撥

 其實這個Handler還有一個Callback的回撥這個東西,我一開始並不知道的,當時應該是從大佬同事那裡瞭解到這個東西的時候,感覺很神奇,又感覺很懵逼,為什麼會有布林型別的返回值?然後在我的實驗中發現,咦?true和false的結果是一樣的?腦子裡更懵逼了,這是什麼???黑人問號臉...

 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,完全沒有影響,照彈不誤!為了內心世界的和平,肯定不能放棄治療!原始碼什麼的,對不起,一開始我肯定選擇了百度。百度果然是沒有辜負我的期望,什麼有用的都沒有找到,找到的都是一模一樣的無數個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。
 然後,讓我們和原始碼結合一下~~~

//這裡是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掉了,不會去執行Handler的handleMessage這個方法了,如果為false的話就會執行啦,當然,好像預設情況都是執行false。
 這裡也是暫時理解是這樣的,對於它的應用場景,具體需求,以後遇到了再更新!
 哦對了,還有AndroidStudio中,我們平常使用的Handler其實都會可能導致記憶體洩露的,AS會標黃色,表示警告。然後在使用這個Handler.Callback的時候,我一開始發現警告消除掉了。其實並不是,在只使用Callback的handleMessage方法時,是沒有警告的,但是加上Handler的handleMessage後,警告就有了,所以,網上有的人說的Handler.Callback會解決記憶體洩露,是錯誤的,沒有警告了只是寫法有問題罷了,哈哈哈哈哈!


Handler的記憶體洩露

 Handler的記憶體洩漏,一開始讓我注意到這個問題的時候,是我的AS報警告。更新以後的AS,對於Handler的這個警告,不要太凶喔,一報就是一大片屎黃色,咦~~~受不了
 比如說這樣:


4016460-fe12bad9b70683c5.png
image.png

 然後就開始探究了啊,為什麼會導致記憶體洩露呢?哈哈哈,上面圖片裡其實有說啦,這種Handler的使用其實就是將Handler宣告為Activity的內部。而在Java語言中,非靜態內部類會持有外部類的一個隱式引用,所以,Handler會持有Activity的引用啦,然後就會有可能造成外部類,也就是Activity無法被回收,導致記憶體洩露~~~
 然後,經過詢問等等等方法探究了他人的經驗之後,發現其實很多開發一直使用的是可能會導致記憶體洩露的版本的Handler,因為正面看上去並不會影響App的執行,不像影象過大會導致OOM這種很致命的問題就會讓很多忽視,或者說沒有注意到這個問題,比如瞭解這個問題之前的那個我~
 那麼,如何避免記憶體洩漏,使用正確的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.

 哇,全是英文吶,扔谷歌翻譯進化一下~

這個處理程式類應該是靜態的或可能發生洩漏(匿名android.os.Handler)少...(按Ctrl+ F1)
由於此Handler被宣告為內部類,因此可能會阻止外部類被垃圾收集。
如果處理程式對主執行緒以外的執行緒使用Looper或MessageQueue,則沒有問題。 
如果Handler使用主執行緒的Looper或MessageQueue,則需要修復Handler宣告,
如下所示:將Handler宣告為靜態類; 在外部類中,例項化WeakReference到外部類並在例項化Handler時將此物件傳遞給Handler; 
使用WeakReference物件建立對外部類成員的所有引用。

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


正確使用Handler,避免記憶體洩露
  • 使用靜態的匿名內部類,並持有外部類的弱引用

 宣告靜態的Handler內部類,持有外部類的弱引用,通過外部類例項去引用外部類的各種控制元件例項,引數例項等等。然後當GC回收時,因為外部類是弱引用,所以會被回收。

/**
     * 宣告一個靜態的Handler內部類,並持有外部類的弱引用
     */
    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);

 然後呼叫傳送訊息,post的方式和sendMessage的方式

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

 如果使用sendMessage方法的話,會被MyHandler的 handleMessage方法接收。那麼,若使用post方法的話,我們還需要宣告一個靜態的Runable來完成我們的post

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

結束語

 還有很多沒有講到的,講到的也都感覺沒有說的很徹底,先寫到這裡,然後多鑽研再修改更新~~~

相關文章