Handler機(jī)制(Android消息機(jī)制)

自我理解鏈接

handler機(jī)制大致流程圖.png

1. 首先有一個(gè)Looper對(duì)象已經(jīng)存在,并處于輪詢(xún)的狀態(tài);
相對(duì)應(yīng)的代碼卿樱,Looper.prepare()-->Looper.loop()

2. Handler發(fā)送Message,加入到MQ中
各種發(fā)送消息的方法-->equeueMessage()-->MessageQueue.equeueMessage()

3. 輪詢(xún)?nèi)〕鯩Q的隊(duì)頭Message空盼,通過(guò)Handler進(jìn)行回調(diào)
MessageQueue.next()-->(handler)msg.target.dispatchMessage()-->handleMessage()[自定義TODO]

Message 【繼承Parcelable】

封裝了任務(wù)攜帶的參數(shù)和處理該任務(wù)的Handler

/**
 * User-defined message code so that the recipient can identify
 * what this message is about. Each {@link Handler} has its own name-space
 * for message codes, so you do not need to worry about yours conflicting
 * with other handlers.
 */
public int what; //標(biāo)識(shí)位肢扯,用來(lái)區(qū)別消息

/**
 * arg1 and arg2 are lower-cost alternatives to using
 * {@link #setData(Bundle) setData()} if you only need to store a
 * few integer values.
 */
public int arg1; //用來(lái)存儲(chǔ)一些整數(shù)值芦倒,替代setData()存儲(chǔ)

/**
 * arg1 and arg2 are lower-cost alternatives to using
 * {@link #setData(Bundle) setData()} if you only need to store a
 * few integer values.
 */
public int arg2; //用來(lái)存儲(chǔ)一些整數(shù)值轰豆,替代setData()存儲(chǔ)

/**
 * An arbitrary object to send to the recipient.  When using
 * {@link Messenger} to send the message across processes this can only
 * be non-null if it contains a Parcelable of a framework class (not one
 * implemented by the application).   For other data transfer use
 * {@link #setData}.
 *
 * <p>Note that Parcelable objects here are not supported prior to
 * the {@link android.os.Build.VERSION_CODES#FROYO} release.
 */
//發(fā)送給收件人的任意對(duì)象雪营。當(dāng)使用{@link Messenger}跨進(jìn)程發(fā)送消息時(shí)弓千,
//如果它包含框架類(lèi)的Parcelable(不是應(yīng)用程序?qū)崿F(xiàn)的框架類(lèi)),則它只能是非null献起。
//對(duì)于其他數(shù)據(jù)傳輸洋访,請(qǐng)使用{@link #setData}
//2.2版本之前不支持實(shí)現(xiàn)Parceable的實(shí)體類(lèi)
public Object obj;
  • Message的創(chuàng)建(2種方法)

    1. 直接實(shí)例化一個(gè)Message镣陕,然后設(shè)置其參數(shù)
      Message msg = new Message;
      msg.what = 0;
      msg.arg0 = 1; 
      ...
      
    2. Message.obtain() --【推薦】
      在許多情況下可以避免分配新的對(duì)象;避免重復(fù)創(chuàng)建Message
  • Message注意點(diǎn):

    1. 盡量通過(guò)Message.obtain()的方式構(gòu)建Message對(duì)象姻政,防止Message的多次創(chuàng)建呆抑;
    2. 僅有int型參數(shù)時(shí) 最好使用arg1和arg2,減少內(nèi)存的使用汁展;

Looper:消息通道(使普通線(xiàn)程變成Looper線(xiàn)程)

在Activity中鹊碍,系統(tǒng)會(huì)自動(dòng)啟動(dòng)Looper對(duì)象;而在自定義類(lèi)中食绿,需要自己手動(dòng)調(diào)用侈咕;
下面是Looper線(xiàn)程實(shí)現(xiàn)的典型示例(源碼中的注釋中有寫(xiě)到):

class LooperThread extends Thread {
  public Handler mHandler;
  public void run() {
      Looper.prepare();
      mHandler = new Handler() {
          public void handleMessage(Message msg) {
              // process incoming messages here
          }
      };
      Looper.loop();
  }
}

關(guān)于Looper.prepare()系統(tǒng)源碼:

/** Initialize the current thread as a looper.
  * This gives you a chance to create handlers that then reference
  * this looper, before actually starting the loop. Be sure to call
  * {@link #loop()} after calling this method, and end it by calling
  * {@link #quit()}.
  */
"http://將當(dāng)前線(xiàn)程初始化為looper;確保在調(diào)用prepare方法之后炫欺,調(diào)用loop方法乎完;然后結(jié)束時(shí)調(diào)用quit方法;"
public static void prepare() {
    prepare(true);
}

'//一個(gè)Thread只能有一個(gè)Looper對(duì)象'
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));
}
//初始化時(shí)會(huì)創(chuàng)建一個(gè)MessageQueue對(duì)象和線(xiàn)程
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}

關(guān)于Looper.loop()系統(tǒng)源碼:

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
//在此線(xiàn)程中運(yùn)行消息隊(duì)列,確保在結(jié)束loop后調(diào)用quit
public static void loop() {
    //獲取當(dāng)前l(fā)ooper線(xiàn)程
    final Looper me = myLooper();
    //判斷是否調(diào)用了prepare
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //獲取線(xiàn)程中的消息隊(duì)列
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    ...
    '//for(;;)和while(true)  for (品洛;树姨;)指令少,不占用寄存器桥状,而且沒(méi)有判斷跳轉(zhuǎn)'
    for (;;) {
        // 獲取消息隊(duì)列中的message
        Message msg = queue.next(); // might block 
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ... log日志[省略]...
        try {
            '//交給Message對(duì)應(yīng)的Handler處理消息'
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
         ... [省略]...
        msg.recycleUnchecked();
    }
}

總結(jié):
1)一個(gè)Thread只能有一個(gè)Looper對(duì)象帽揪;
2)Looper內(nèi)部有一個(gè)消息隊(duì)列,loop()方法調(diào)用后線(xiàn)程開(kāi)始不斷從隊(duì)列中取出消息執(zhí)行辅斟;
3)Looper使一個(gè)線(xiàn)程變成Looper線(xiàn)程转晰。

MessageQueue:消息隊(duì)列

根據(jù)源碼中的注釋翻譯如下:
保存由{@link Looper}分派的消息列表的低級(jí)類(lèi)。消息不會(huì)直接添加到MessageQueue士飒,而是通過(guò)與Looper關(guān)聯(lián)的{@link Handler}對(duì)象添加查邢。
您可以使用{@link Looper#myQueue()Looper.myQueue()}檢索當(dāng)前線(xiàn)程的MessageQueue。

/**
 Low-level class holding the list of messages to be dispatched by a
 {@link Looper}.  Messages are not added directly to a MessageQueue,
 but rather through {@link Handler} objects associated with the Looper.

 <p>You can retrieve the MessageQueue for the current thread with
 {@link Looper#myQueue() Looper.myQueue()}.
 */
public final class MessageQueue {
    ...
    //獲取下一個(gè)message方法
    Message next(){
        ...
    }
    //message添加到消息隊(duì)列的方法
    boolean enqueueMessage(Message msg, long when) {
        ...
    }
}

Handler:消息操作類(lèi)【重點(diǎn)】

官方注釋?zhuān)ǔ橄蟮姆g):
Handler允許您發(fā)送和處理與線(xiàn)程{@link MessageQueue}關(guān)聯(lián)的{@link Message}和Runnable對(duì)象酵幕。
每個(gè)Handler實(shí)例都與一個(gè)線(xiàn)程和該線(xiàn)程的消息隊(duì)列相關(guān)聯(lián)扰藕。
當(dāng)您創(chuàng)建一個(gè)新的Handler時(shí),它被綁定到正在創(chuàng)建它的線(xiàn)程的線(xiàn)程/消息隊(duì)列 - 從那時(shí)起芳撒,它將消息和runnables傳遞給該消息隊(duì)列并在消息出來(lái)時(shí)執(zhí)行它們隊(duì)列邓深。

Handler有兩個(gè)主要用途:
(1)將消息和runnable安排在將來(lái)的某個(gè)點(diǎn)上執(zhí)行;
(2)將要在不同于自己的線(xiàn)程上執(zhí)行的動(dòng)作排入隊(duì)列。

源碼分析:
/**
  *默認(rèn)的構(gòu)造方法會(huì) 關(guān)聯(lián)一個(gè)looper
  */
public Handler(Callback callback, boolean async) {
    ...
    //關(guān)聯(lián)looper笔刹,不能為null
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    //直接獲取looper的消息隊(duì)列芥备,
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
handler發(fā)送消息:

可傳遞參數(shù)包括Message和Runnable,但即使傳遞Runnable對(duì)象舌菜,最終也被處理為Message對(duì)象萌壳,然后執(zhí)行sendMessageAtTime()方法

/**
 * 在所有待處理消息之后將消息排入消息隊(duì)列。
 *  如果消息成功的加入到MQ中,就返回true
 */
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);
}
handler消息的處理:

dispatchMessage(msg);此方法在Looper.loop()中調(diào)用msg.target.dispatchMessage()讶凉;這里的msg.traget就是指向當(dāng)前的handler染乌;
然后Handler中調(diào)用handleMessage(msg)方法山孔,來(lái)觸發(fā)我們需要實(shí)現(xiàn)的具體邏輯懂讯。

延伸:

1. Handler內(nèi)部如何獲取到當(dāng)前線(xiàn)程的Looper?

答:是通過(guò) ThreadLocal

Handler內(nèi)部.png
myLooper方法.png
2. 系統(tǒng)為什么不允許子線(xiàn)程中訪(fǎng)問(wèn)UI台颠?

答:Android中的UI控件不是線(xiàn)程安全的褐望,如果在多線(xiàn)程中并發(fā)的訪(fǎng)問(wèn),UI顯示狀態(tài)不可預(yù)計(jì)串前。

3. 為何系統(tǒng)不對(duì)UI的訪(fǎng)問(wèn)加上鎖機(jī)制瘫里?

答:上鎖會(huì)讓UI訪(fǎng)問(wèn)的邏輯變得復(fù)雜,會(huì)降低UI訪(fǎng)問(wèn)的效率荡碾,也會(huì)阻塞某些線(xiàn)程的執(zhí)行谨读,體現(xiàn)在界面上會(huì)顯得卡頓運(yùn)行不暢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坛吁,一起剝皮案震驚了整個(gè)濱河市劳殖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拨脉,老刑警劉巖哆姻,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異玫膀,居然都是意外死亡矛缨,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)帖旨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)箕昭,“玉大人,你說(shuō)我怎么就攤上這事解阅÷渲瘢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵瓮钥,是天一觀的道長(zhǎng)筋量。 經(jīng)常有香客問(wèn)我,道長(zhǎng)碉熄,這世上最難降的妖魔是什么桨武? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮锈津,結(jié)果婚禮上呀酸,老公的妹妹穿的比我還像新娘。我一直安慰自己琼梆,他們只是感情好性誉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布窿吩。 她就那樣靜靜地躺著,像睡著了一般错览。 火紅的嫁衣襯著肌膚如雪纫雁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天倾哺,我揣著相機(jī)與錄音轧邪,去河邊找鬼。 笑死羞海,一個(gè)胖子當(dāng)著我的面吹牛忌愚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播却邓,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼硕糊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了腊徙?” 一聲冷哼從身側(cè)響起简十,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昧穿,沒(méi)想到半個(gè)月后勺远,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡时鸵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年胶逢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饰潜。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡初坠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出彭雾,到底是詐尸還是另有隱情碟刺,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布薯酝,位于F島的核電站半沽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吴菠。R本人自食惡果不足惜者填,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望做葵。 院中可真熱鬧占哟,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蜜暑,卻和暖如春铐姚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背史煎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工谦屑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驳糯,地道東北人篇梭。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像酝枢,于是被迫代替她去往敵國(guó)和親恬偷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

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