Handler工作機(jī)制

本文和大家聊聊Handler工作機(jī)制這檔子事因篇。

什么是Handler沪停?

官方定義:

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

通俗的講,咱們可以把Handler理解為Android中線程與線程間通信的工具约计。

Handler結(jié)構(gòu)圖

Handler如何工作诀拭?

咱們先來通過一個常見的場景,分析Handler是如何工作的煤蚌。

第一步耕挨,咱們創(chuàng)建一個Handler實(shí)例,代碼如下:

public Handler mHandler = new Handler(){
      @Override
      public void handleMessage(Message msg) {
          if(msg.what == GUI_UPDATE_TEXT) {
              String strValue = (String) msg.obj;
              mTextView.setText(strValue);
          }
      }
    };

第二步尉桩,咱們定義一個工作者線程筒占,用來處理一些阻塞操作,代碼如下:

class WorkerThread extends Thread{

        private Handler mHandler;

        public WorkerThread(Handler handler){
            mHandler = handler;
        }

        @Override
        public void run(){
            //do something

            Message msg = mHandler.obtainMessage(GUI_UPDATE_TEXT, strValue);
            mHandler.sendMessage(msg);
        }
    }

第三步蜘犁,執(zhí)行線程赋铝,開始干活,代碼如下:

WorkerThread workerThread = new WorkerThread(mHandler); 
workerThread.start();

第一步中咱們創(chuàng)建了Handler沽瘦,看似很簡單明了的一個操作革骨,其實(shí)Handler背后做了很多工作。

Handler被創(chuàng)建時析恋,需要從當(dāng)前線程中獲取looper良哲,并獲取該looper中的消息隊列(MessageQueue)。代碼段如下:

// Handler源碼
 public Handler(Callback callback, boolean async) {
       ...........
        mLooper = Looper.myLooper(); //獲取當(dāng)前線程的looper
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue; //獲取looper中的消息隊列
        .............
        .............
    }

第二步中線程執(zhí)行完阻塞的事情后助隧,會調(diào)動Handler的sendMessage筑凫,那sendMessage又做了什么呢滑沧?

sendMessage最終會調(diào)用sendMessageAtTime(是所有send方法的終點(diǎn)),sendMessageAtTime將消息(message)加入消息隊列(MessageQueue)中巍实,開始休息傳遞滓技。代碼段如下:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        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, uptimeMillis); //將消息加入消息隊列
    }

到這里,咱們要傳遞的消息已經(jīng)在消息隊列中了棚潦,那是如何通知Handler的handleMessage的呢令漂?

這就要談到looper了,looper中有一個loop()方法丸边,它會一直循環(huán)(不是輪詢)檢查消息隊列中是否有可用的消息叠必,若有可用的消息,則從消息中獲取其所屬的Handler妹窖,并調(diào)用Handler的dispatchMessage纬朝,由dispatchMessage將消息送達(dá)hanndleMessage。

Looper代碼段如下:

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;       
        .................
        for (;;) {
            Message msg = queue.next(); // 獲取可用的消息骄呼,此方法是阻塞的
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
        ...................         
            try {
                msg.target.dispatchMessage(msg);//獲取message所屬的Handler共苛,調(diào)用其dispatchMessage
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
           ..................
           ...................
            msg.recycleUnchecked();
        }
    }

Handler代碼段如下:

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

至此,主線程就可以執(zhí)行UI更新蜓萄,結(jié)束消息傳遞工作隅茎。

分析到這里,仍然有一些問題困惑著我們绕德,例如:

  • Looper是如何與當(dāng)前線程關(guān)聯(lián)?
  • Handler的延時發(fā)送是如何實(shí)現(xiàn)摊阀?
    下面咱們來具體談?wù)?/li>

Looper是如何與當(dāng)前線程關(guān)聯(lián)耻蛇?

眾所周知,Looper的初始化是通過調(diào)用prepare()方法胞此,機(jī)敏的讀者馬上就會意識到臣咖,Looper與線程的關(guān)聯(lián)一定與這個方法有關(guān)系。

沒錯漱牵,但是在講解prepare方法之前夺蛇,咱們先來看看ThreadLocal,因為它才是實(shí)現(xiàn)Looper與線程關(guān)聯(lián)的核心元素酣胀。

ThreadLocal是線程的本地化變量刁赦,即只有擁有它的線程才可以訪問,那它是如何做到這一點(diǎn)的呢闻镶?

ThreadLocal獲取到當(dāng)前線程對象甚脉,從該線程對象中獲得ThreadLocalMap(即以ThreadLocal為key的map),并將自身放入到這個map铆农,這樣就完成了線程的本地化存儲牺氨,即實(shí)現(xiàn)了只有擁有它的線程才可以訪問。

ThreadLocal代碼段如下:

 public void set(T value) {
        Thread t = Thread.currentThread();//獲取當(dāng)前線程對象
        ThreadLocalMap map = getMap(t);//從線程對象中獲取ThreadLocalMap
        if (map != null)
            map.set(this, value);//若map中已經(jīng)存在ThreadLocal,則放入自身和值(這里咱們假設(shè)值是Looper)
        else
            createMap(t, value);
}

咱們回到prepare()方法猴凹,該方法中將Looper存入threadlocal夷狰,完成Looper與當(dāng)前線程的關(guān)聯(lián)。Looper代碼段如下:

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));
 }

Handler的延時發(fā)送是如何實(shí)現(xiàn)郊霎?

延時消息發(fā)送的關(guān)鍵在MesaageQueue的enqueueMessage方法沼头,需要延時發(fā)送的消息,在加入消息隊列時歹篓,會根據(jù)當(dāng)前消息的when值對整個消息隊列進(jìn)行升序排序瘫证,當(dāng)when值超時,會被Looper獲取到庄撮,從而完成延時消息的發(fā)送背捌。

when值默認(rèn)是系統(tǒng)啟動到現(xiàn)在運(yùn)行時間,不包含設(shè)備休眠的時間

下面咱們結(jié)合代碼來看看:

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
          ......................
          ......................
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) { 
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // 當(dāng)前消息的when < p.when洞斯,就將p放到當(dāng)前消息的后面
                prev.next = msg;
            }
           .....................
           .....................
        }
        return true;
    }

我是青嵐之峰毡庆,如果讀完后有所收獲,歡迎點(diǎn)贊加關(guān)注烙如!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末么抗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子亚铁,更是在濱河造成了極大的恐慌蝇刀,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徘溢,死亡現(xiàn)場離奇詭異吞琐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)然爆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門站粟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人曾雕,你說我怎么就攤上這事奴烙。” “怎么了剖张?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵切诀,是天一觀的道長。 經(jīng)常有香客問我搔弄,道長趾牧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任肯污,我火速辦了婚禮翘单,結(jié)果婚禮上吨枉,老公的妹妹穿的比我還像新娘。我一直安慰自己哄芜,他們只是感情好貌亭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著认臊,像睡著了一般圃庭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上失晴,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天剧腻,我揣著相機(jī)與錄音,去河邊找鬼涂屁。 笑死书在,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拆又。 我是一名探鬼主播儒旬,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼帖族!你這毒婦竟也來了栈源?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤竖般,失蹤者是張志新(化名)和其女友劉穎甚垦,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涣雕,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡艰亮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了胞谭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片垃杖。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡男杈,死狀恐怖丈屹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情伶棒,我是刑警寧澤旺垒,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站肤无,受9級特大地震影響先蒋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宛渐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一竞漾、第九天 我趴在偏房一處隱蔽的房頂上張望眯搭。 院中可真熱鬧,春花似錦业岁、人聲如沸鳞仙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棍好。三九已至,卻和暖如春允耿,著一層夾襖步出監(jiān)牢的瞬間借笙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工较锡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留业稼,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓念链,卻偏偏與公主長得像盼忌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子掂墓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355