Android之Handler消息傳遞機制詳解

NO IMAGE

前言

  • 在Android開發中,多線程應用是非常頻繁的,其中Handler機制隨處可見.
  • 下面就本人對Handle的一些理解與大家一起分享,共同回顧下Handle異步消息傳遞機制

1.Handler是什麼?

  • Handler是一套在 Android開發中 進行異步消息傳遞的機制。

2.Handler在Android中的作用

  • 在Android開發中多線程的應用中,將工作線程中需更新UI的操作信息 傳遞到 UI主線程,從而實現 工作線程對UI的更新處理,最終實現異步消息的處理。

3. 我們為什麼要使用Handler去處理更新UI操作呢?

  • 在多個線程併發更新UI的同時 保證線程安全。

4.Handler異步消息傳遞所涉及的相關概念

  • MainThread (主線程)UI線程,程序啟動時自動創建。
  • 工作線程,開發者自己開啟的線程,執行耗時操作等。
  • Handler(處理者) UI線程與子線程通信的媒介,添加消息到消息隊列(Message Queue)處理循環器分發過來的消息(Looper)。
  • Message (消息) Handler接受&處理的對象,存儲需要操作的消息。
  • Message Queue(消息隊列) 數據存儲結構,採用先進先出方式,存儲Handler發過來的消息。
  • Looper(循壞器)消息隊列與處理者的媒介,從消息隊列中循環取出消息併發送給Handler處理。

5.使用方式

  • Handler的使用方式 因發送消息到消息隊列的方式不同而不同(兩種)
  • 使用Handler.sendMessage()、使用Handler.post()

1.使用 Handler.sendMessage()方式

/** 
  * 方式1:新建Handler子類(內部類)
  */

    // 步驟1:自定義Handler子類(繼承Handler類) & 複寫handleMessage()方法
    class mHandler extends Handler {

        // 通過複寫handlerMessage() 從而確定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
         ...// 執行UI操作
            
        }
    }

    // 步驟2:在主線程中創建Handler實例
        private Handler mhandler = new mHandler();

    // 步驟3:創建所需的消息對象
        Message msg = Message.obtain(); // 實例化消息對象
        msg.what = 1; // 消息標識
        msg.obj = "AA"; // 消息內容存放

    // 步驟4:在工作線程中 通過Handler發送消息到消息隊列中
        mHandler.sendMessage(msg);


/** 
  * 方式2:匿名內部類
  */
   // 步驟1:在主線程中 通過匿名內部類 創建Handler類對象
            private Handler mhandler = new  Handler(){
                // 通過複寫handlerMessage()
                @Override
                public void handleMessage(Message msg) {
                        ...// 需執行UI操作
                    }
            };

  // 步驟2:創建消息對象
    Message msg = Message.obtain(); // 實例化消息對象
  msg.what = 1; // 消息標識
  msg.obj = "AA"; // 消息內容存放
  // 步驟3:在工作線程中 通過Handler發送消息到消息隊列中
   mHandler.sendMessage(msg);

2.使用Handler.post()

 new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 通過psot()發送,傳入1個Runnable對象
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        // 指定操作UI內容
                   
                    }

                });
            }
        }.start();


6.Handler底層原理及源碼分析

在源碼分析前,先來了解Handler機制中的幾個核心類

  • 處理器 (Handler)
  • 消息隊列 (MessageQueue)
  • 循環器 (Looper)
    關於這幾個類的具體作用前面已經介紹過了就不再過多闡述了。

下面開始源碼分析,注意力集中了
上文中我們提到過Handler發送消息有兩種方式,分別是

  • Handler.sendMessage()
  • 使用Handler.post()
    下面先從第一種開始分析:

方式1:使用 Handler.sendMessage()

  //通過匿名內部類 創建Handler類對象
    private Handler mhandler = new  Handler(){
        // 通過複寫handlerMessage()從而確定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
                ...// 需執行的UI操作
            }
    };

---------->>開始源碼分析

public Handler() {

            this(null, false);
            // ->>此處this指代的就是當前的Handler實例,調用有參構造

    }

public Handler(Callback callback, boolean async) {

            ...// 無關代碼我就不貼了

            // 1. 指定Looper對象
                mLooper = Looper.myLooper();
                if (mLooper == null) {
                    throw new RuntimeException(
                        "Can't create handler inside thread that has not called Looper.prepare()");
                }
                // Looper.myLooper()作用:獲取當前線程的Looper對象;若線程無Looper對象則拋出異常

                // 可通過執行Loop.getMainLooper()方法獲得主線程的Looper對象

            // 2. 綁定消息隊列對象(MessageQueue)
                mQueue = mLooper.mQueue;
                // 獲取該Looper對象中保存的消息隊列對象(MessageQueue)
                // 至此,完成了handler 與 Looper對象中MessageQueue的關聯
    }

  • 從上面的源碼來看,當我們創建Handler對象後,通過Handler的構造方法系統就已經幫我們自動綁定了looper和對應的MessageQueue消息隊列。我們只需拿著這個Handler對象執行我們所需的操作就可以了
  • 但是,你肯定有疑問了,當前線程的Looper對象 & 對應的消息隊列對象(MessageQueue) 是哪來的呢?我既沒有獲取也沒有創建啊?
public static void main(String[] args) {
            ... // 無關的代碼

            Looper.prepareMainLooper(); 
            // 1. 為主線程創建1個Looper對象,同時生成1個消息隊列對象(MessageQueue)

            ActivityThread thread = new ActivityThread(); 
            // 2. 創建主線程

            Looper.loop(); 
            // 3. 自動開啟 消息循環 

        }

  • 我們可以看到,其實在Android應用進程啟動時,會默認創建1個主線程(ActivityThread,也叫UI線程) ,創建ActivityThread的時候,會自動調用ActivityThread的1個靜態的main()方法 = 應用程序的入口,而main()方法內則會自動調用Looper.prepareMainLooper()為主線程生成1個Looper對象和MessageQueue隊列。

  • 而Handler對象創建時若不指定looper則默認綁定主線程的looper,從而可以執行主線程的UI更新操作。

  • 若是在子線程中創建Handler實例,則需要指定looper了,所以就用上了Loop.getMainLooper()方法來獲得主線程的Looper對象。

方式1: 使用Handler.post()

 public void dispatchMessage(Message msg) {

    // 1. 若msg.callback屬性不為空,則代表使用了post(Runnable r)發送消息
    // 則執行handleCallback(msg),即回調Runnable對象裡複寫的run()
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }

            // 2. 若msg.callback屬性為空,則代表使用了sendMessage(Message msg)發送消息,即回調複寫的handleMessage(msg)
            handleMessage(msg);

        }
    }


   public void handleMessage(Message msg) {  
          ... // 創建Handler實例時複寫
   } 

從以上源碼來看,使用Handler.post()時,系統會自動回調Runnable對象裡複寫的run()方法,將其打包成msg對象, 實際上和sendMessage(Message msg)發送方式相同。

至此,關於Handler的異步消息傳遞機制的解析就完成了。


7.關於Handler 內存洩露的原因

  • 在Android開發中,內存洩露是 十分常見的

  • 其中一種情況就是在Handler中發生的內存洩露

  • 為什麼會發生內存洩漏?
    1.Handler的一般用法 : 新建Handler子類(內部類) 、匿名Handler內部類,而在我們編寫代碼的時候,其實編譯器就會提示我們這種操作可能會發生內存洩漏,在android studio中就是這塊代碼會變黃。
    2.提示的原因是

  • 該Handler類由於未設置為 靜態類,從而導致了內存洩露

  • 最終的內存洩露發生在Handler類的外部類:XXXActivity類中

3.內存洩漏的原因
首先我們先要了解一些其他的知識點。

  • 主線程的Looper對象的生命週期 = 應該應用程序的生命週期
  • 在Java中,非靜態內部類 & 匿名內部類都默認持有 外部類的引用,

而在Handler處理消息的時候,Handler必須處理完所有消息才會與外部類解除引用關係,如果此時外部Activity需要提前被銷燬了,而Handler因還未完成消息處理而繼續持有外部Activity的引用。由於上述引用關係,垃圾回收器(GC)便無法回收MainActivity,從而造成內存洩漏。


8.如何解決Handler內存洩漏

1.靜態內部類+弱引用

將Handler的子類設置成 靜態內部類,同時,還可加上 使用WeakReference弱引用持有Activity實例。
原因:弱引用的對象擁有短暫的生命週期。在垃圾回收器線程掃描時,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。


    // 設置為:靜態內部類
    private static class FHandler extends Handler{

        // 定義 弱引用實例
        private WeakReference<Activity> reference;

        // 在構造方法中傳入需持有的Activity實例
        public FHandler(Activity activity) {
            // 使用WeakReference弱引用持有Activity實例
            reference = new WeakReference<Activity>(activity); }

        // 複寫handlerMessage() 
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                 //更新UI
                    break;
                case 2:
                //更新UI
                    break;

            }

2.當外部l類結束生命週期時,清空Handler內消息隊列

@Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
        // 外部類Activity生命週期結束時,同時清空消息隊列 & 結束Handler生命週期
    }

推薦使用上述解決方法一,以保證保證Handler中消息隊列中的所有消息都能被執行


總結

本文主要講述了Handler的基本原理和使用方法,以及造成內存洩漏的原因和解決方案。


歡迎關注作者darryrzhong,更多幹貨等你來拿喲.

請賞個小紅心!因為你的鼓勵是我寫作的最大動力!

更多精彩文章請關注

相關文章

Android進階學習思維大綱

AndroidRxJava:基礎介紹與使用

AndroidRetrofit2.5.0使用基礎詳解

Android自定義View:快遞時間軸實現