安卓消息機(jī)制詳解

寫在前面的話

提起安卓的消息機(jī)制挽鞠,我們馬上就會聯(lián)想到Handler媚赖,而Handler在日常的開發(fā)中經(jīng)常會用到查排,因此了解安卓的消息機(jī)制還是很有必要的凳枝,畢竟知己知彼,百戰(zhàn)不殆跋核。

所謂的消息機(jī)制,實質(zhì)上是線程之間通信的一種機(jī)制叛买。在平常的開發(fā)中砂代,我們都知道子線程中不能更新UI,我們的做法就是在子線程要更新UI的地方通知主線程率挣,讓主線程完成UI的更新刻伊。

與消息機(jī)制相關(guān)的類

Handler
負(fù)責(zé)發(fā)送和接收消息
Message
消息的載體
MessageQueue
消息隊列
Looper
負(fù)責(zé)循環(huán)消息隊列
ThreadLocal<T>
線程內(nèi)部數(shù)據(jù)存儲類,ThreadLocal通過set方法存儲數(shù)據(jù)椒功,通過get方法獲取數(shù)據(jù)捶箱。在消息機(jī)制中,就是通過它來存儲每一個線程的Looper對象

 public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}

 public T get() {
    // Optimized for the fast path.
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values != null) {
        Object[] table = values.table;
        int index = hash & values.mask;
        if (this.reference == table[index]) {
            return (T) table[index + 1];
        }
    } else {
        values = initializeValues(currentThread);
    }

    return (T) values.getAfterMiss(this);
}

消息機(jī)制具體流程

接下來动漾,我就以子線程如何通知主線程更新UI這一例子來詳細(xì)介紹一下安卓的消息機(jī)制丁屎。

1. 調(diào)用Looper.prepare方法
 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));
}

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

由代碼可知,在prepare方法中旱眯,會創(chuàng)建一個Looper對象晨川,并且一個線程也只會創(chuàng)建一個证九。
同時在Looper的構(gòu)造方法中,會創(chuàng)建一個消息隊列共虑,即MessageQueue愧怜。

2. 封裝一條需要發(fā)送的消息

  Message msg = Message.obtain();
  msg.what = 0;
  msg.obj= obj;

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

創(chuàng)建消息,我們用obtain方法妈拌,該方法的原則是拥坛,如果消息池中有Message,則直接取出尘分,沒有才會新創(chuàng)建一個Message猜惋。

what,消息的標(biāo)記音诫,類型為int
obj惨奕,消息需要傳遞的對象,類型為Object

3. 調(diào)用Handler的 send或者post 的方法發(fā)送消息

3.1首先創(chuàng)建一個Handler對象mHandler
private Handler mHandler=new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

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

 public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

在Handler的構(gòu)造方法竭钝,我們可以看到梨撞,mHandler與Looper和消息隊列建立了關(guān)聯(lián)

3.2 調(diào)用send或者post方法
  • send
    sendEmptyMessage(int what)
    sendEmptyMessageDelayed(int what, long delayMillis)
    sendEmptyMessageAtTime(int what, long uptimeMillis)
    sendMessage(Message msg)
    sendMessageDelayed(Message msg, long delayMillis)
    sendMessageAtTime(Message msg, long uptimeMillis)
    sendMessageAtFrontOfQueue(Message msg)

  • post
    post(Runnable r)
    postDelayed(Runnable r, long delayMillis)
    postAtTime(Runnable r, long uptimeMillis)
    postAtTime(Runnable r, Object token, long uptimeMillis)
    postAtFrontOfQueue(Runnable r)

經(jīng)過查看post方法的源碼,發(fā)現(xiàn)post方法實際上也是調(diào)用的send類的方法在發(fā)送消息香罐,區(qū)別在于post方法的參數(shù)是Runnable卧波。

下面是post方法相關(guān)的源碼

 public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

 private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

看上面代碼我們知道,post方法中傳遞的參數(shù)雖然不是Message庇茫,但最終傳遞的對象依然是Message港粱,Runnable對象成為了這個消息的一個屬性

通過對Handler源碼的分析,發(fā)現(xiàn)除了sendMessageAtFrontOfQueue方法之外旦签,其余任何send的相關(guān)方法查坪,都經(jīng)過層層包裝走到了sendMessageAtTime方法中,我們來看看源碼:
(實際上宁炫,sendMessageAtFrontOfQueue方法除了uptimeMillis為0外偿曙,和sendMessageAtTime 一模一樣)

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

此時,mHandler會將消息通過enqueueMessage方法羔巢,放入消息隊列

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

msg.target = this望忆,就相當(dāng)于給該消息貼上了mHandler的標(biāo)簽(誰發(fā)送的消息,誰接收處理)

這里的enqueueMessage方法是MessageQueue的方法竿秆,在該方法中會將Message根據(jù)時間排序启摄,放入到消息隊列中。

4. 調(diào)用Looper.loop方法

 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;

    // 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 (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }
}

在這個方法中幽钢,有一個for的死循環(huán)歉备,不斷地調(diào)用queue.next()方法,將Message從消息隊列中取出

然后調(diào)用msg.target.dispatchMessage(msg)方法搅吁,msg.target實際上就是mHandler

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

分析這個方法威创,有兩個分支handleCallback和handleMessage落午,回憶前面所說的send和post方法,當(dāng)調(diào)用的是send類的方法時肚豺,明顯走h(yuǎn)andleMessage這個分支溃斋,此時,子線程已經(jīng)成功將消息傳遞至主線程吸申,在這里我們就可以更新UI了

當(dāng)調(diào)用的是post方法時梗劫,msg.callback就是Runnable對象,此時會走h(yuǎn)andleCallback分支

 private static void handleCallback(Message message) {
    message.callback.run();
}

此時調(diào)用了run方法截碴,走到這梳侨,子線程也已經(jīng)將消息成功傳至主線程,在這里我們就可以更新UI了

總結(jié)一下

任何線程在用到Handler處理消息時日丹,都需要經(jīng)過上面說的4個步驟走哺,缺一不可,具體代碼如下

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

一個線程只有一個Looper,一個消息隊列
Handler在什么線程創(chuàng)建實例哲虾,這個Handler就屬于該線程

順便提一下在子線程中更新UI的方法
1.handler.sendMessage
2.handler.post
3.view.post
4.activity.runOnUiThread

view.post

 public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
}

activity.runOnUiThread

 public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

查看源碼發(fā)現(xiàn)丙躏,實際上2,3束凑,4的原理和1是一樣的晒旅,都是利用Handler來發(fā)送消息。

有人會疑問汪诉,我們平時在用Handler解決子線程不能更新UI的問題時废恋,只是在主線程中創(chuàng)建了一個Handler對象,然后在子線程用這個Handler對象發(fā)送了一個消息扒寄,最后在Handler的回調(diào)方法中成功更新了UI鱼鼓,并沒有經(jīng)過1和4兩個步驟。實際上在主線程中该编,1和4兩個步驟蚓哩,系統(tǒng)已經(jīng)幫我們做了,下面是ActivityThread的main方法

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    SamplingProfilerIntegration.start();

    // CloseGuard defaults to true and can be quite spammy.  We
    // disable it here, but selectively enable it later (via
    // StrictMode) on debug builds, but using DropBox, not logs.
    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser();

    // Set the reporter for event logging in libcore
    EventLogger.setReporter(new EventLoggingReporter());

    // Make sure TrustedCertificateStore looks in the right place for CA certificates
    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    TrustedCertificateStore.setDefaultUserDirectory(configDir);

    Process.setArgV0("<pre-initialized>");

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末上渴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子喜颁,更是在濱河造成了極大的恐慌稠氮,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件半开,死亡現(xiàn)場離奇詭異隔披,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)寂拆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進(jìn)店門奢米,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抓韩,“玉大人,你說我怎么就攤上這事鬓长≮怂” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵涉波,是天一觀的道長英上。 經(jīng)常有香客問我,道長啤覆,這世上最難降的妖魔是什么苍日? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮窗声,結(jié)果婚禮上相恃,老公的妹妹穿的比我還像新娘。我一直安慰自己笨觅,他們只是感情好拦耐,可當(dāng)我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著屋摇,像睡著了一般揩魂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上炮温,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天火脉,我揣著相機(jī)與錄音,去河邊找鬼柒啤。 笑死倦挂,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的担巩。 我是一名探鬼主播方援,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼涛癌!你這毒婦竟也來了犯戏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤拳话,失蹤者是張志新(化名)和其女友劉穎先匪,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弃衍,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡呀非,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岸裙。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡猖败,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出降允,到底是詐尸還是另有隱情恩闻,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布拟糕,位于F島的核電站判呕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏送滞。R本人自食惡果不足惜侠草,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望犁嗅。 院中可真熱鬧边涕,春花似錦、人聲如沸褂微。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宠蚂。三九已至式撼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間求厕,已是汗流浹背著隆。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留呀癣,地道東北人美浦。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像项栏,于是被迫代替她去往敵國和親浦辨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,585評論 2 359

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