Handler源碼淺析

核心成員

  • Handler

提供各種api調(diào)用,例如接收消息左腔、發(fā)送消息耀找、創(chuàng)建一個Message等

  • Message

消息實體野芒,承載數(shù)據(jù)

  • MessageQueue

消息隊列

  • Looper

循環(huán)機(jī)制

Handler的創(chuàng)建

public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
  • looper

循環(huán)機(jī)制,如果是在主線程中收發(fā)消息可以直接將Looper.getMainLooper()作為入?yún)⒋樽ァandler中的消息隊列其實用的就會looper里的消息隊列

  • callback
   public interface Callback {
       /**
        * @param msg A {@link android.os.Message Message} object
        * @return True if no further handling is desired
        */
       boolean handleMessage(@NonNull Message msg);
   }

在handler中有這樣一個接口丹拯,里面就一個方法handleMessage,返回boolean乖酬,在消息分發(fā)的過程中會先將Message傳遞給這個callback咬像,返回true的時候,handler本身的handleMessage方法就不會接收到這個Message了

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

從源碼中可以看到Message也有一個callback肮柜,但這個callback是一個Runable审洞,比如我們通過handler.post的方法發(fā)起消息時待讳,就是將這個消息作為一個callback去處理

  • async

暫無

sendMessage(發(fā)送消息)

發(fā)送消息相關(guān)的的api有很多個:

  • sendMessage
  • sendEmptyMessage
  • sendEmptyMessageDelayed
  • sendEmptyMessageAtTime
  • sendMessageDelayed
  • post
  • postDelay

他們最終調(diào)用的都是sendMessageAtTime


  public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
  }
    
  public boolean sendMessageAtTime(@NonNull 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);
    }
    
   private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

從源碼中可以發(fā)現(xiàn)撰糠,延時消息其實就是當(dāng)前系統(tǒng)時間+延時時間辩昆,這里我們差不多也能猜到,延時消息的原理基本就是根據(jù)時間將消息進(jìn)行排序术辐,在固定的時間點進(jìn)行發(fā)送施无,但是我們發(fā)現(xiàn)Handler僅僅只是調(diào)用了隊列的enqueueMessage方法猾骡,那么這些排序操作也就是隊列去完成的了,另外在enqueueMessage中還進(jìn)行了target的賦值幢哨,就是把Handler自己傳進(jìn)去嫂便,這一步就為了當(dāng)Looper在處理消息的時候調(diào)用Handler的dispatchMessage方法進(jìn)行消息分發(fā)

Looper

Looper.loop會啟動一個死循環(huán),主線程在啟動的時候就已經(jīng)做了這些事情

    public static void loop() {
       //......
        for (;;) {
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }
    
    private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride){
         Message msg = me.mQueue.next(); // might block
         //...
          try {
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        //...
    }

在這死循環(huán)中會不斷的調(diào)用loopOnce方法践樱,loopOnce中其實也是通過調(diào)用隊列的next()方法直接拿出消息然后調(diào)用dispatchMessage進(jìn)行事件分發(fā)拷邢,那么延時消息到底是怎么做到定時發(fā)送的呢甲雅?這個答案就在mQueue.next()中

MessageQueue

通過以上的分析,整個消息收發(fā)流程中比較重要的是消息隊列,消息排序脐瑰,處理延時消息,獲取消息這些邏輯都在這里面

MessageQueue.enqueueMessage()

boolean enqueueMessage(Message msg, long when){
    //.....
    
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
    //....
}

enqueueMessage主要負(fù)責(zé)消息的插入,msg.when就是這個消息應(yīng)該觸發(fā)的時間寂恬,也就是當(dāng)前系統(tǒng)時間+延時時間,整個隊列是以時間為準(zhǔn)從小到大排列酷鸦。在這個方法中你會發(fā)現(xiàn)這樣一段代碼

// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
   nativeWake(mPtr);
}

這個與MessageQueue.next()中的nativePollOnce(ptr, nextPollTimeoutMillis)相對應(yīng)臼隔,ptr指向的是native code妄壶,通過native方法nativeInit()獲取。nativePollOnce讓程序進(jìn)入等待狀態(tài)氨淌,nativeWake則會喚醒伊磺,就像Thread.wait()和Thread.notify(),但兩者在底層機(jī)制上是有區(qū)別的蛮艰,nativePollOnce底層是運用了epoll機(jī)制,Thread.wait()是Futex即寡,這么做也是為了不浪費CPU資源袜刷。

MessageQueue.next()中的

Message next() {
    //...
    int nextPollTimeoutMillis = 0;
    for (;;) {
        //...
        nativePollOnce(ptr, nextPollTimeoutMillis);
        //...
      if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
    }
    //...
}

這里面也有一個for循環(huán)著蟹,這個循環(huán)會一直等到有消息可以處理才會停止。對于延時消息奸披,會記錄延時的時間nextPollTimeoutMillis涮雷,然后在下次循環(huán)的時候會調(diào)用nativePollOnce進(jìn)入等待。

IdleHandler

在MessageQueue.next()中样刷,除了以上的工作外置鼻,還會處理IdleHandler消息蜓竹。如果當(dāng)前沒有需要處理的消息,處于空閑階段時會處理掉所有的IdleHandler消息司蔬。

用法:

 Looper.getMainLooper().queue.addIdleHandler(object : MessageQueue.IdleHandler {
      override fun queueIdle(): Boolean {
         TODO("Not yet implemented")
      }
})
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俊啼,一起剝皮案震驚了整個濱河市左医,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌跛十,老刑警劉巖秕硝,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異坞嘀,居然都是意外死亡惊来,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門矢渊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來臂聋,“玉大人庵芭,你說我怎么就攤上這事色鸳∥杼眩” “怎么了管削?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長崎弃。 經(jīng)常有香客問我含潘,道長,這世上最難降的妖魔是什么盆均? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任泪姨,我火速辦了婚禮饰抒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仗处。我一直安慰自己,他們只是感情好吃环,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布旷档。 她就那樣靜靜地躺著,像睡著了一般范咨。 火紅的嫁衣襯著肌膚如雪厂庇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天替蛉,我揣著相機(jī)與錄音拄氯,去河邊找鬼。 笑死镣煮,一個胖子當(dāng)著我的面吹牛鄙麦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胯府,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼骂因,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了塘装?” 一聲冷哼從身側(cè)響起影所,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤猴娩,失蹤者是張志新(化名)和其女友劉穎勺阐,沒想到半個月后矛双,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡懒闷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年愤估,在試婚紗的時候發(fā)現(xiàn)自己被綠了速址。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡昔园,死狀恐怖默刚,靈堂內(nèi)的尸體忽然破棺而出逃魄,到底是詐尸還是另有隱情,我是刑警寧澤嗅钻,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布养篓,位于F島的核電站柳弄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碧注。R本人自食惡果不足惜糖赔,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一放典、第九天 我趴在偏房一處隱蔽的房頂上張望基茵。 院中可真熱鬧壳影,春花似錦、人聲如沸根灯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茬高。三九已至假抄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間熏瞄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工强饮, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留邮丰,地道東北人铭乾。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像斗蒋,于是被迫代替她去往敵國和親笛质。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容