Android 消息處理機制

Android 消息處理機制

Android中常說的消息處理機制, 其實就是android下線程間的通信機制, 也就是Handler的工作機制.

Handler, 我想對于每一個有android開發(fā)經驗的人來說都不會陌生, 經常使用到.

比如, 我們要在子線程中更新界面UI, 會通過handler.sendMessage()發(fā)送一個消息到主線程; 然后在handler的回調方法handleMessage()中去處理這個消息.

我們都知道, android中更新UI只能在主線程進行, 那么, 不知道你有沒有想過, 為什么在子線程中可以通過handler發(fā)送消息給主線程, 來通知主線程更新UI呢?他們是怎么通信的呢?是不是只有主線程才能用Handler的消息機制呢?

一, android消息機制主要由Handler, Message, MessageQueue, Looper幾大角色組成:
以前網絡收藏的圖

Handler: 負責發(fā)送和處理消息

Message: 消息的載體, 封裝了what, arg, target等相關的信息.

MessageQueue: 消息隊列, 存放由Handler發(fā)送過來待處理的Message.

Lopper: 初始化Looper的同時會創(chuàng)建一個MessageQueue, Looper啟動后會一直不斷的從MessageQueue中取出消息Message交給對應的Handler來處理

二, 我們以mainThread為例, 來分析下Handler機制的工作流程
1. Looper初始化

系統(tǒng)啟動時, 在ActivityThread.java的main()中就已經初始化好了, 然后通過Looper.loop()來開啟looper

public static void main(String[] args) {
        //...
        //創(chuàng)建Looper
        Looper.prepareMainLooper();
        // ...
        //啟動Looper
        Looper.loop();
}

接下來先看下Looper.java中創(chuàng)建looper的代碼

   /** 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()}.
      */
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        //如果looper已存在, 拋異常,,保證looper的唯一
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

   /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

由以上代碼可以看出, 在prepare(boolean quitAllowed) 中我們new了一個Looper對象, 然后保存在sThreadLocal中, 然后通過myLooper()來創(chuàng)建sMainLooper, 即我們的主線程Looper.

代碼邏輯很簡單, 主要是這個sThreadLocal是什么東東?
sThreadLocal 是 ThreadLocal的一個實例. ThreadLocal又是神馬, 不是很懂, 只知道是用來提供thread-local的, 提供了get, set方法. set會保存looper和當前線程的一些信息, get返回looper, 看這個類的注釋了解下

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 *
 * <p>For example, the class below generates unique identifiers local to each
 * thread.
 * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
 * and remains unchanged on subsequent calls.
 * <pre>
 * import java.util.concurrent.atomic.AtomicInteger;
 *
 * public class ThreadId {
 *     // Atomic integer containing the next thread ID to be assigned
 *     private static final AtomicInteger nextId = new AtomicInteger(0);
 *
 *     // Thread local variable containing each thread's ID
 *     private static final ThreadLocal&lt;Integer&gt; threadId =
 *         new ThreadLocal&lt;Integer&gt;() {
 *             &#64;Override protected Integer initialValue() {
 *                 return nextId.getAndIncrement();
 *         }
 *     };
 *
 *     // Returns the current thread's unique ID, assigning it if necessary
 *     public static int get() {
 *         return threadId.get();
 *     }
 * }
 * </pre>
 * <p>Each thread holds an implicit reference to its copy of a thread-local
 * variable as long as the thread is alive and the {@code ThreadLocal}
 * instance is accessible; after a thread goes away, all of its copies of
 * thread-local instances are subject to garbage collection (unless other
 * references to these copies exist).
 *
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */
public class ThreadLocal<T> {  
    ...
    public void set(T value) {
        Thread t = Thread.currentThread();
        //ThreadLocalMap is a customized hash map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    public T get() {
        Thread t = Thread.currentThread();
        //ThreadLocalMap is a customized hash map
        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();
    }
    ...
 }

順便, 我們看下new Looper()做了什么

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

灰常簡單, 就2行代碼, 一個是初始化我們的MessageQueue, 然后就是獲取到當前的線程mThread .
注意下這里的private構造方法, 即looper是單實例的, 也就是說, 只能通過Looper.prepare()來創(chuàng)建looper對象, 回過去看下prepare()的代碼, 在創(chuàng)建looper前會先判斷下looper是否存在, 如果是已經存在了會直接拋異常的, 這樣就保證了一個線程只能有一個looper, 同時, 也就只能有一個MessageQueue;

2. 啟動looper

嗯嗯,...sMainLooper創(chuàng)建好后, 回到ActivityThread.main(), 通過Looper.loop()啟動looper.

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        //拿到當前線程的looper
        final Looper me = myLooper();
        //looper為空就拋異常, 這就是為什么普通線程不能用Handler的原因了
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
       //拿到looper的MessageQueue
        final MessageQueue queue = me.mQueue;

        //...other code

        //無限for循環(huán)
        for (;;) {
            //拿到MessageQueue中的Message對象, 如果有, 主要這里的might block, 即這個方法會阻塞線程
            Message msg = queue.next(); // might block
          
           //...
            try {
                //dispatchMessage分發(fā)處理Message
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            //...other code

            //Message回收
            msg.recycleUnchecked();
         }
    }

看loop()的核心代碼, 邏輯也很簡單, 就是拿到當前線程的Looper, 拿到Looper的MessageQueue, 然后一個無限for循環(huán), 不斷的從消息隊列里取出消息, 然后msg.target.dispatchMessage(msg), 來處理消息.

我們重點分析下MessageQueue是怎么拿到消息的:

Message msg = queue.next(); // might block

就是MessageQueue.next()了

    Message next() {
        // ...other code
        int nextPollTimeoutMillis = 0;//等待執(zhí)行的時間
        for (;;) {
            //消息需要延時執(zhí)行, e.g: postDelayed, sendMessageDelayed這些操作就會有延時
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //這里, 如果沒有消息的話/時間還沒到, 會堵塞線程, 直到有消息/時間到了的時候, 才會被喚醒返回
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    //找到一個不是異步而且不為空的message.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        //時間還沒到, 計算出延時時間, 這里之后會進入下一個循環(huán), 然后在nativePollOnce()中block住
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 以下..順利取到一個消息, 然后返回這個消息
                        mBlocked = false;
                        //鏈表操作
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages. 沒有消息了
                    nextPollTimeoutMillis = -1;
                }

                // ...
            }

            // ...
        }
    }

looper的啟動流程大概就到這里了, 這里有幾個問題有注意下:
?1. 調用myLooper()后, 會先判斷下looper是否為空, 如果空會直接拋異常, 這就是為什么普通線程不能用Handler的原因了, 所有線程如果要有消息處理能力, 必須先Looper.prepare()來創(chuàng)建looper對象;
?2. 分發(fā)消息msg.target.dispatchMessage(msg), 這里的target其實就是關聯(lián)的handler對象, 是在sendMessage()的時候關聯(lián)的, 這樣能夠使Looper在分發(fā)消息的時候是不用去區(qū)分Handler對象的;
?3. queue.next()這里是會阻塞線程的, 而且這里如果是主線程的話, 為什么不會ANR呢? 這個問題要先理清楚產生anr的原因, 并不是有阻塞就會anr, anr是因為主線程的消息來不及處理而導致的. 比如, 我們在啟動一個activity的時候再主線程sleep個10s, 這樣不一定會anr的, 只有當主線程阻塞的時候, 有新的消息要處理又得不到處理, 比如我們按下手機返回鍵, 這時就會anr了.

3. obtain Message

獲取一個消息對象, 一般使用obtain, 當然new也可以.

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

private static Message sPool;

這里的sPool是一個靜態(tài)Message對象, 不為空的時候, 就直接返回這個message, 否則new一個message, 這樣可以減少對象的創(chuàng)建. 那么這個sPool是那來的呢? 就是在Looper分發(fā)消息后, 可以回過去看一下loop()方法for循環(huán)的最后一行代碼, 就是對message的回收操作.

void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            //將當前message緩存起來, MAX_POOL_SIZE = 50
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

回收前, 先把當前對象的以下狀態(tài), 參數(shù)等清除, 然后把當前對象保存在sPool. 剛才說了, 這個sPool是個靜態(tài)變量, 所有是會一直存在的. 這里Message其實也維護了一個鏈表這種數(shù)據(jù)結構的, 看下在對sPool賦值前會把之前的sPool賦值給next, 就是讓next指向之前回收的Message對象. 當obtain獲取一個Message時, 獲取到的是表頭的Message, 然后又讓sPool指向表頭的next, 即下一個Message對象, 這樣每次obtain時都能獲取到表頭的一個Message對象.
驚不驚喜, 意不意外? 以前沒看源碼前, 一直以為這邊是用對象池的方式來緩存Message對象的.

4. Handler發(fā)送和消息的分配

Handler要發(fā)送消息, 要先獲取一個Handler對象, 一般通過以下兩種方式

  1. handler = new Handler()
  2. handler = Handler(Looper looper)

需要一個Looper對象才能創(chuàng)建Handler, 第一種方法其實默認獲取當前線程的Looper的

public Handler(Callback callback, boolean async) {
       ...
       mLooper = Looper.myLooper();
       if (mLooper == null) {
           throw new RuntimeException(
               "Can't create handler inside thread " + Thread.currentThread()
                       + " that has not called Looper.prepare()");
       }
       mQueue = mLooper.mQueue;
       mCallback = callback;
       mAsynchronous = async;
   }

Looper.myLooper()獲取的就是當前線程的Looper. 看下那個異常, 當looper為空的時候拋異常的, 這就是為什么不能再子線程用handler的原因了.

Looper在啟動后, 會一直在死循環(huán)中, 等待消息進入消息隊列.
那么, 消息是怎么進入消息隊列的呢? 就是我們經常用的handler的一些方法了, 比如

sendMessage, sendMessageDelayed, post, postDelayed...
這些方法最終都會執(zhí)行到sendMessageAtTime()這里

  public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this; //關聯(lián)handler
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

然后就到MessageQueue.enqueueMessage()加入消息隊列了

boolean enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            //消息執(zhí)行的時間杖刷,隊列是按照消息執(zhí)行時間排序的
            //when = SystemClock.uptimeMillis() + delayMillis
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //列表里沒有消息 / 消息的執(zhí)行時間== 0 / 要入隊列的消息執(zhí)行時間 < 表頭消息的時間
            //把這個消息當做表頭, next指向之前的那個消息(mMessages)
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                //需要喚醒線程, 因為在不用處理消息的時候線程是block的, 前面分析過了
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    //從表頭開始遍歷消息, 根據(jù)執(zhí)行時間找出前面(要先執(zhí)行)的那個消息prev
                    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;
            }

            // 喚醒線程
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

代碼注釋都有, 不難理解.
接下來分配消息, 看下Handler.dispatchMessage(msg)這部分代碼:

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

這部分代碼邏輯很簡單, 大概解釋下: handler.post()時msg.callback != null, 會調用Runnable.run();
如果是通過Handler(Callback callback)實例化handler的話, mCallback != null; 否則直接調用 handleMessage(msg), 這個是空方法, 就是我們初始化Handler時重寫的那個handleMessage()方法.

三, 利用Handler機制來實現(xiàn)子線程間的通信

根據(jù)主線程的消息處理機制的原理, 我們完全可以在子線程中來實現(xiàn)我們自己的消息處理機制.
大概代碼是這樣的:

class MyThread extends Thread {
 
        @Override
        public void run() {
            super.run();
            //prepare looper
            Looper.prepare();

            //handler
            handler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    
                }
            };
 
            ...

            //start looper
            Looper.loop();
        }
    }

這樣, 只要在其他子線程能拿到MyThread的Handler或是Looper(創(chuàng)建Handler)就能向MyThread發(fā)送消息了, MyThread的looper會自行處理這些消息.





ps: 這幾天在重新理了下Handler機制這塊, 寫下來是為了加深印象. 第一次寫, 如有不對的地方, 請幫忙指正,感謝!感謝!!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市名船,隨后出現(xiàn)的幾起案子止潘,更是在濱河造成了極大的恐慌别威,老刑警劉巖炸站,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亡电,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機偏瓤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門杀怠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人厅克,你說我怎么就攤上這事赔退。” “怎么了证舟?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵硕旗,是天一觀的道長。 經常有香客問我女责,道長卵渴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任鲤竹,我火速辦了婚禮浪读,結果婚禮上,老公的妹妹穿的比我還像新娘辛藻。我一直安慰自己碘橘,他們只是感情好,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布吱肌。 她就那樣靜靜地躺著痘拆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氮墨。 梳的紋絲不亂的頭發(fā)上纺蛆,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音规揪,去河邊找鬼桥氏。 笑死,一個胖子當著我的面吹牛猛铅,可吹牛的內容都是我干的字支。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼奸忽,長吁一口氣:“原來是場噩夢啊……” “哼堕伪!你這毒婦竟也來了?” 一聲冷哼從身側響起栗菜,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤欠雌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后疙筹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體富俄,經...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡检号,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛙酪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡翘盖,死狀恐怖桂塞,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情馍驯,我是刑警寧澤阁危,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站汰瘫,受9級特大地震影響狂打,放射性物質發(fā)生泄漏。R本人自食惡果不足惜混弥,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一趴乡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝗拿,春花似錦晾捏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至仓手,卻和暖如春胖齐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嗽冒。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工呀伙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人添坊。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓区匠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親帅腌。 傳聞我的和親對象是個殘疾皇子驰弄,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內容