Hanlder 機(jī)制詳解(線程間通訊)

概述

  1. 什么是Android消息機(jī)制
    Android中的消息機(jī)制主要是指Handler的運(yùn)行機(jī)制以及Handler所附帶的MessageQueue和Looper的工作過(guò)程,這三個(gè)實(shí)際上是一個(gè)整體。

  2. Handler的作用,為什么有Handler
    Handler的主要作用:負(fù)責(zé)跨線程通信。

    因?yàn)樵谥骶€程不能做耗時(shí)操作,而子線程不能更新UI,所以當(dāng)子線程中進(jìn)行耗時(shí)操作后需要更新UI時(shí),通過(guò)Handler將有關(guān)UI的操作切換到主線程中執(zhí)行。

系統(tǒng)為什么不允許在子線程中訪問(wèn)UI?
因?yàn)锳ndroid的UI控件不是線程安全的,如果在多線程中并發(fā)訪問(wèn)可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài)。
為什么系統(tǒng)不對(duì)UI控件的訪問(wèn)加上鎖機(jī)制呢?
加上鎖機(jī)制會(huì)讓UI訪問(wèn)的邏輯變得復(fù)雜
鎖機(jī)制會(huì)降低UI訪問(wèn)的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行。
鑒于這兩個(gè)缺點(diǎn),最簡(jiǎn)單且高效的方法就是采用單線程模型來(lái)處理UI操作。


  1. 幾個(gè)相關(guān)概念
  • Message:消息,被傳遞和處理的數(shù)據(jù)。其中包含了消息ID,消息處理對(duì)象以及處理的數(shù)據(jù)等,由MessageQueue統(tǒng)一列隊(duì),終由Handler處理。

  • Handler:處理者,負(fù)責(zé)Message的發(fā)送及處理。
    Handler的主要作用:(有兩個(gè)主要作用)
    1. Handler.sendMessage(),在工作線程中發(fā)送消息;
    2. Handler.handleMessage(),在主線程中獲取、并處理消息。

  • MessageQueue:消息隊(duì)列,本質(zhì)是一個(gè)單鏈表,用來(lái)存放Handler發(fā)送過(guò)來(lái)的消息,并按照FIFO規(guī)則執(zhí)行。當(dāng)然,存放Message并非實(shí)際意義的保存,而是將Message串聯(lián)起來(lái),等待Looper的抽取。

  • Looper:消息泵或循環(huán)器,通過(guò)Looper.loop()不斷從MessageQueue中抽取Message。因此,一個(gè)MessageQueue需要一個(gè)Looper。

  • Thread:線程,負(fù)責(zé)調(diào)度整個(gè)消息循環(huán),即消息循環(huán)的執(zhí)行場(chǎng)所。
    一個(gè)Thread只能有一個(gè)Looper,一個(gè)MessageQueue,可以有多個(gè)Handler。

  1. 工作流程


    handler工作流程.jpg
  • Handler通過(guò)sendMessage()發(fā)送Message時(shí),Looper的成員變量MessageQueue會(huì)通過(guò)enqueueMessage()向MessageQueue中添加一條消息。

此時(shí)Message會(huì)將Handler對(duì)象賦值給Message對(duì)象的target參數(shù)。

  • Looper通過(guò)loop()方法開(kāi)啟循環(huán)后,不斷輪詢(xún)調(diào)用MessageQueue的next()方法來(lái)獲取Message。

  • 通過(guò)調(diào)用Messag目標(biāo)Handler的dispatchMessage()方法去傳遞消息,目標(biāo)Handler收到消息后調(diào)用handleMessage()方法處理消息

    簡(jiǎn)單來(lái)說(shuō),Handler通過(guò)sendMessage()方法將Message發(fā)送到Looper的成員變量MessageQueue中,之后Looper通過(guò)loop()方法不斷循環(huán)遍歷MessageQueue從中讀取Message,最終回調(diào)給Handler處理。

消息機(jī)制分析

  1. ThreadLocal
  • 概念:
    ThreadLocal是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類(lèi),通過(guò)它可以在指定的線程中存儲(chǔ)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)以后,只有在指定線程中可以獲取到存儲(chǔ)的數(shù)據(jù),對(duì)于其他線程來(lái)說(shuō)則無(wú)法獲取到數(shù)據(jù)。

  • 使用場(chǎng)景:

    • 一般來(lái)說(shuō),當(dāng)某些數(shù)據(jù)是以線程為作用域并且不同線程具有 不同的數(shù)據(jù)副本的時(shí)候,就可以考慮采用ThreadLocal。
    • 復(fù)雜邏輯下的對(duì)象傳遞。
  • ThreadLocal 的set和get方法原理

    • set原理


      Threadlocal.set
    • get原理


      Threadlocal.get
  1. 消息隊(duì)列
    消息隊(duì)列(MessageQueue)主要包含了兩個(gè)操作:插入和讀取。盡管被稱(chēng)為隊(duì)列,但是它的內(nèi)部實(shí)現(xiàn)是通過(guò)一個(gè)單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)的消息列表,單鏈表在插入和刪除上比較有優(yōu)勢(shì)。
  • 插入
    插入的方法是enqueueMessage,作用是往消息隊(duì)列中插入一條消息。主要操作是單鏈表的插入操作。

插入的規(guī)則:
會(huì)根據(jù)message的延遲時(shí)間來(lái)進(jìn)行插入,無(wú)延遲的會(huì)放到鏈表的頭部,
有延遲的,延遲時(shí)間越長(zhǎng)越靠后

  • 讀取
    讀取對(duì)應(yīng)的方法是next,作用是從消息隊(duì)列中取出一條消息并將其從消息隊(duì)列中移除。next方法是一個(gè)無(wú)限循環(huán)的方法,如果消息隊(duì)列沒(méi)有消息,那么next方法會(huì)一直阻塞,當(dāng)有新的消息到來(lái)時(shí),next方法會(huì)返回這條信息并將其從單鏈表中移除。
  1. Looper
    Looper在Android的消息機(jī)制中扮演者消息循環(huán)的角色,具體來(lái)說(shuō),它會(huì)不停地從MessageQueue中查看是否有新的消息,如果有新消息就會(huì)立刻處理,否則就會(huì)一直阻塞在那里。
  • Looper的使用
    UI線程會(huì)自動(dòng)創(chuàng)建Looper,子線程需自行創(chuàng)建。
    通過(guò)Looper.prepare()即可為當(dāng)前線程創(chuàng)建一個(gè)Looper。
//子線程中需要自己創(chuàng)建一個(gè)Looper
new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();//為子線程創(chuàng)建Looper   
                Handler handler = new Handler();
                Looper.loop(); //開(kāi)啟消息輪詢(xún)
            }
        }).start();

prepare()

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

所創(chuàng)建的Looper會(huì)保存在ThreadLocal(線程本地存儲(chǔ)區(qū))中,它不是線程,作用是幫助Handler獲得當(dāng)前線程的Looper。

ThreadLocal.set()

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

獲取當(dāng)前線程,然后獲取當(dāng)前線程的ThreadLocalMap,通過(guò)map.set方法,將ThreadLocal和當(dāng)前的Lopper對(duì)象存到map中。

ThreadLocal.get()

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

所以一個(gè)線程只會(huì)有一個(gè)ThreadLocal和一個(gè)Lopper,并且Looper是唯一的,不可被替換的。

在這里我們看到了Looper的構(gòu)造方法

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

我們看到它會(huì)創(chuàng)建一個(gè)MessageQueue,然后將當(dāng)前線程的對(duì)象保存起來(lái)。

  • 除了prepare(),還提供prepareMainLooper(),本質(zhì)也是通過(guò)prepare(),主要是給主線程也就是ActivityThread創(chuàng)建Looper使用的。
  • 由于主線程Looper比較特殊,所以Looper提供了一個(gè)getMainLooper的方法,通過(guò)它可以在任何地方獲取到主線程的Looper。
  • 無(wú)論是主線程還是子線程,Looper只能被創(chuàng)建一次,即一個(gè)Thread只有一個(gè)Looper。

  • Looper的退出
    • quit:直接退出Looper
    • quitSafely:只是設(shè)定一個(gè)退出標(biāo)記,然后把消息隊(duì)列中的已有消息處理完畢后才安全地退出。

在子線程中,如果手動(dòng)為其創(chuàng)建了Looper,那么在所有的事情完成以后應(yīng)該調(diào)用quit方法來(lái)終止消息循環(huán),否則這個(gè)子線程就會(huì)一直處于等待的狀態(tài),而如果退出Looper以后,這個(gè)線程就會(huì)立刻終止,因此建議不需要的時(shí)候終止Looper。


  • loop方法原理


    looper原理.jpg
  1. Handler
    Handler的主要工作包含消息的發(fā)送和接收過(guò)程。
  • 發(fā)送有兩種方式 send 和 post
//send方式的Handler創(chuàng)建
Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        //如UI操作
        mTextView.setText(new_str);
    }
};
//send
mHandler.sendEmptyMessage(0);
//post方式
private Handler mHandler = new Handler();
mHandler.post(new Runnable() {
    @Override
    public void run() {
        mTextView.setText(new_str);
    }
});

post方式最終還是通過(guò)一系列send方法實(shí)現(xiàn)的

public final boolean sendMessageAtFrontOfQueue(Message msg) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, 0);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
       msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

從上邊的代碼可以發(fā)現(xiàn),Handler發(fā)送消息的過(guò)程僅僅是向消息隊(duì)列插入了一條信息,MessageQueue的next方法就會(huì)返回這條信息給Looper,Looper收到消息后就開(kāi)始處理了,最終消息由Looper交給Handler處理,即Handler的dispatchMessage方法會(huì)被調(diào)用,這是Handler就進(jìn)入了處理消息的階段。

  • 處理消息
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

最終會(huì)調(diào)用Handler的handleMessage方法來(lái)處理消息。

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

  1. Handler泄露的原因及正確寫(xiě)法
    如果直接在Activity中初始化一個(gè)Handler對(duì)象,會(huì)報(bào)如下錯(cuò)誤:

This Handler class should be static or leaks might occur


原因是:
在Java中,非靜態(tài)內(nèi)部類(lèi)會(huì)持有一個(gè)外部類(lèi)的隱式引用,可能會(huì)造成外部類(lèi)無(wú)法被GC;
比如這里的Handler,就是非靜態(tài)內(nèi)部類(lèi),它會(huì)持有Activity的引用從而導(dǎo)致Activity無(wú)法正常釋放。

當(dāng)一個(gè)消息是一個(gè)延遲消息的時(shí)候,這個(gè)Message會(huì)一直存在MessageQueue中,因?yàn)镸essage的target對(duì)應(yīng)的就是Handler,然后Handler持有Activity的引用,所以就會(huì)導(dǎo)致了Activity無(wú)法被正常釋放。

解決方法:
將 Handler 定義成靜態(tài)的內(nèi)部類(lèi),在內(nèi)部持有Activity的弱引用,并在Acitivity的onDestroy()中調(diào)用handler.removeCallbacksAndMessages(null)及時(shí)移除所有消息。

private static class MyHandler extends Handler {    
    //創(chuàng)建一個(gè)弱引用持有外部類(lèi)的對(duì)象    
    private final WeakReference<MainActivity> content;    
    private MyHandler(MainActivity content) {        
        this.content = new WeakReference<MainActivity>(content);    
    }   
 
    @Override    
    public void handleMessage(Message msg) {        
        super.handleMessage(msg);        
        MainActivity activity= content.get();       
        if (activity != null) {            
            switch (msg.what) {                
                case 0: {                    
                    activity.notifyUI();                
                }           
            }        
        }    
    }
}
  1. Message可以如何創(chuàng)建?哪種效果更好,為什么?
    • 直接生成實(shí)例Message m = new Message
    • Message msg=Message.obtain();
    • Message msg=handler.obtainMessage();

而后兩者是直接在消息池中取出一個(gè)Message實(shí)例,這樣做就可以避免多生成Message實(shí)例。

  1. Looper死循環(huán)為什么不會(huì)導(dǎo)致應(yīng)用卡死?

    • 主線程的主要方法就是消息循環(huán),一旦退出消息循環(huán),那么你的應(yīng)用也就退出了,Looer.loop()方法可能會(huì)引起主線程的阻塞,但只要它的消息循環(huán)沒(méi)有被阻塞,能一直處理事件就不會(huì)產(chǎn)生ANR異常。
    • 造成ANR的不是主線程阻塞,而是主線程的Looper消息處理過(guò)程發(fā)生了任務(wù)阻塞(即Looper中有消息,但是消息沒(méi)有得到及時(shí)的處理),無(wú)法響應(yīng)手勢(shì)操作,不能及時(shí)刷新UI。
    • 阻塞與程序無(wú)響應(yīng)沒(méi)有必然關(guān)系,雖然主線程在沒(méi)有消息可處理的時(shí)候是阻塞的,但是只要保證有消息的時(shí)候能夠立刻處理,程序是不會(huì)無(wú)響應(yīng)的。
  2. 為什么主線程可以直接new Handler()
    因?yàn)樵贏ctivityMainThread的main()方法里調(diào)用了Looper.prepareMainLooper()方法。
    在其他線程如果要使用Handler,必須要調(diào)用Looper.prepare()方法。

  3. 子線程中維護(hù)的Looper,消息隊(duì)列無(wú)消息的時(shí)候的處理方案是什么?有什么用?
    next()中的:nativePollOnce(ptr, nextPollTimeoutMillis); 阻塞線程
    quit()中的:nativeWake(mPtr); 喚醒線程

  4. 既然可以存在多個(gè)Handler往MessageQueue中添加數(shù)據(jù)(發(fā)消息時(shí)各個(gè)Handler可能處于不同線程),那它內(nèi)部是如何確保線程安全的?
    這里主要關(guān)注 MessageQueue 的消息存取即可,看源碼內(nèi)部的話,在往消息隊(duì)列里面存儲(chǔ)消息時(shí),會(huì)拿當(dāng)前的 MessageQueue 對(duì)象作為鎖對(duì)象,這樣通過(guò)加鎖就可以確保操作的原子性和可見(jiàn)性了。

消息的讀取也是同理,也會(huì)拿當(dāng)前的 MessageQueue 對(duì)象作為鎖對(duì)象,來(lái)保證多線程讀寫(xiě)的一個(gè)安全性。

參考:Android開(kāi)發(fā)藝術(shù)探索
部分圖片來(lái)源

https://juejin.im/post/5e61bf2de51d4526ea7f00bd

最后編輯于
?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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