Android異步消息處理機(jī)制

關(guān)于Android中的異步消息處理機(jī)制爸舒,平時(shí)在項(xiàng)目中應(yīng)該算是用的很多了蟋字。最近看了一些這方面的源碼,記錄一下扭勉。

首先鹊奖,來(lái)看一下平時(shí)是怎么用的吧。最常見(jiàn)的使用場(chǎng)景涂炎,可能就是在子線程中處理一些耗時(shí)操作忠聚,然后更新UI了设哗。那么,這是怎么做的呢两蟀?

在子線程中處理耗時(shí)操作网梢,然后新建了一個(gè)Message(這個(gè)Message里面會(huì)包含很多內(nèi)容,一會(huì)兒再細(xì)說(shuō))赂毯,通過(guò)一個(gè)Handler將Message發(fā)送出去战虏。

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 執(zhí)行耗時(shí)操作

                // 發(fā)送消息
                Message message = Message.obtain();
                message.what = 1;
                message.arg1 = 2;
                handler.sendMessage(message);
            }
        }).start();

在主線程中,我們有一個(gè)Handler党涕,并且重寫(xiě)了handleMessage方法活烙,用來(lái)接收并處理消息。

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                // 更新UI

            }
        }
    };

其實(shí)我們常見(jiàn)的就是Handler和Message這兩個(gè)東西遣鼓,那么就先來(lái)看一下他們到底是什么吧。

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.

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

這個(gè)就是官方對(duì)Handler的定義重贺,簡(jiǎn)單來(lái)說(shuō)骑祟,Handler就是用來(lái)發(fā)送和處理Message的,而且很重要的一點(diǎn)气笙,每個(gè)Handler的實(shí)例都關(guān)聯(lián)了一個(gè)線程和這個(gè)線程的MessageQueue次企。

Message

Defines a message containing a description and arbitrary data object that can be sent to a Handler. This object contains two extra int fields and an extra object field that allow you to not do allocations in many cases.

什么是Message?Message就是一個(gè)包含了描述和任意數(shù)據(jù)的對(duì)象潜圃,可以被發(fā)送給Handler進(jìn)行處理缸棵。

While the constructor of Message is public, the best way to get one of these is to call Message.obtain() or one of the Handler.obtainMessage() methods, which will pull them from a pool of recycled objects.

官方在這里明確表示,雖然Message的構(gòu)造函數(shù)是公有的谭期,但是最好的方式是通過(guò)Message.obtain()或者Handler.obtainMessage()之類(lèi)的方法來(lái)創(chuàng)建Message堵第,因?yàn)檫@樣會(huì)從一個(gè)全局的池中來(lái)復(fù)用Message,而不是每次都新建一個(gè)隧出。

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

MessageQueue

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

前面說(shuō)了每個(gè)Handler的實(shí)例都關(guān)聯(lián)了一個(gè)MessageQueue踏志,那么MessageQueue是什么?其實(shí)從名字就能很容易的看出胀瞪,MessageQueue就是存儲(chǔ)Message的一個(gè)隊(duì)列针余,先進(jìn)先出。那么MessageQueue是在哪呢凄诞?接下來(lái)看另一個(gè)很重要的東西Looper圆雁。

Looper

Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.

其實(shí)Looper就是一個(gè)無(wú)限循環(huán)的任務(wù),他不斷的從MessageQueue中嘗試取出Message帆谍,如果沒(méi)有取到就繼續(xù)嘗試伪朽,如果取到了就交給Hander去處理,直到這個(gè)循環(huán)被停止既忆。

好了驱负,說(shuō)了這么多概念嗦玖,讓我們來(lái)看看源碼。還是從Handler開(kāi)始跃脊,畢竟是直接和我們打交道的宇挫。

無(wú)論使用哪種方式創(chuàng)建Handler,最后其實(shí)都離不開(kāi)這兩個(gè)構(gòu)造函數(shù)酪术。

1 使用當(dāng)前線程默認(rèn)的Looper

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

2 使用指定的Looper而不是默認(rèn)的

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

區(qū)別就在于是使用當(dāng)前線程的默認(rèn)Looper還是使用指定的Looper了器瘪。這里我們可以看到,如果使用的是默認(rèn)Looper绘雁,會(huì)調(diào)用Looper.myLooper()方法橡疼。
讓我們來(lái)看看Handler中有哪些常用的方法,基本可以分為兩大類(lèi)post(Runnable r)sendMessage(Message msg)庐舟,而他們的區(qū)別在于post類(lèi)的方法會(huì)調(diào)用下面這個(gè)方法欣除,指定Message的callback。

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

然后返回一個(gè)Message挪略,最終基本上都會(huì)調(diào)用到這個(gè)方法

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

其中MessageQueue queue = mQueue;指定了Handler對(duì)應(yīng)的MessageQueue历帚,然后調(diào)用入隊(duì)列的方法

    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是將Message和Handler綁定在了一起,而queue.enqueueMessage(msg, uptimeMillis)就是將Message入到隊(duì)列MessageQueue中去杠娱。

Message就是我們需要發(fā)送和處理的消息挽牢,其中大概包含了這么幾個(gè)我們需要用到的東西。

  • public int what
    我們自定義的一個(gè)類(lèi)似編號(hào)的東西摊求,可以用來(lái)與其他Message進(jìn)行區(qū)分
  • public int arg1禽拔,arg2
    可以存一些輕量級(jí)的數(shù)據(jù)
  • public Object obj
    可以存對(duì)象
  • Bundle data
    用來(lái)存儲(chǔ)復(fù)雜的數(shù)據(jù)
  • Handler target
    與Message關(guān)聯(lián)的Handler,也就是負(fù)責(zé)處理消息的Handler
  • Runnable callback
    可以指定運(yùn)行在某個(gè)Runnable上

再來(lái)看一下Looper室叉。

  • 構(gòu)造方法
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

Looper的構(gòu)造方法中只做了兩件事:
1睹栖、生成了一個(gè)MessageQueue,這里也就明白了前面說(shuō)的保存Message的這個(gè)隊(duì)列MessageQueue在哪里了茧痕,沒(méi)錯(cuò)磨淌,就是在Looper里。
2凿渊、得到當(dāng)前所在的線程梁只。
構(gòu)造方法做的這兩件事很重要,說(shuō)明了每一個(gè)Looper中都包含了一個(gè)MessageQueue埃脏,并且關(guān)聯(lián)了一個(gè)特定的線程搪锣,這也是異步消息處理的關(guān)鍵。

  • prepare()方法
     /** 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) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

prepare方法會(huì)檢查是否已經(jīng)有Looper存在彩掐,如果存在會(huì)拋出異常构舟,因?yàn)橐粋€(gè)線程中只能有一個(gè)Looper存在,如果不存在則創(chuàng)建一個(gè)新的Looper堵幽,存在一個(gè)ThreadLocal里狗超。

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

我看到網(wǎng)上很多文章在講Handler機(jī)制時(shí)并沒(méi)有過(guò)多的提到ThreadLocal弹澎,但是其實(shí)我覺(jué)得這也是很重要的一部分。到底什么是ThreadLocal?簡(jiǎn)單來(lái)說(shuō)就是將對(duì)象在不同線程中分別保存了不同的副本,在某一個(gè)線程中改變了對(duì)象的值時(shí)蕊爵,并不會(huì)影響其他線程中的副本冶忱。所以這里用來(lái)保存Looper纵东,不同線程中的MessageQueue將會(huì)互不影響。
如果我們?cè)谧泳€程中創(chuàng)建Handler之前不調(diào)用prepare方法,將會(huì)拋出異常,但是為什么我們?cè)谥骶€程中不需要調(diào)用prepare方法呢报强?那是因?yàn)橄到y(tǒng)會(huì)自動(dòng)調(diào)用prepareMainLooper方法為我們創(chuàng)建主線程的Looper。

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

而前面在Handler的構(gòu)造函數(shù)中的myLooper方法拱燃,只是從ThreadLocal中取出Looper而已秉溉。

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

looper方法就是一個(gè)無(wú)限循環(huán)的任務(wù),不斷的從MessageQueue中取出Message進(jìn)行處理碗誉。其中final MessageQueue queue = me.mQueue;就是從當(dāng)前Looper中取出了MessageQueue坚嗜,而msg.target.dispatchMessage(msg);就是真正處理消息的地方,其中msg.target就是與Message綁定在一起的那個(gè)Handler诗充。

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

callback是什么?callback就是通過(guò)post類(lèi)的方法指定的Runnable诱建,如果callback不為空就執(zhí)行

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

否則就執(zhí)行handleMessage(msg);

看到這里不難發(fā)現(xiàn)蝴蜓,就像官方的定義那樣(廢話,當(dāng)然一樣俺猿。茎匠。。)押袍,每個(gè)Handler都對(duì)應(yīng)了一個(gè)Looper和一個(gè)MessageQueue诵冒,關(guān)聯(lián)了一個(gè)Thread,所以才能實(shí)現(xiàn)異步消息處理谊惭。比如我們?cè)谥骶€程上創(chuàng)建了一個(gè)Handler汽馋,就創(chuàng)建了一個(gè)Looper和MessageQueue,關(guān)聯(lián)的就是主線程圈盔,然后在子線程中發(fā)出消息豹芯,這個(gè)消息就會(huì)存儲(chǔ)在主線程上的MessageQueue里,并且由Handler進(jìn)行處理驱敲。這樣就完成了在子線程中發(fā)送消息铁蹈,在主線程中處理的過(guò)程,當(dāng)然這只是其中的一種應(yīng)用場(chǎng)景众眨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末握牧,一起剝皮案震驚了整個(gè)濱河市容诬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沿腰,老刑警劉巖览徒,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異矫俺,居然都是意外死亡吱殉,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)厘托,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)友雳,“玉大人,你說(shuō)我怎么就攤上這事铅匹⊙荷蓿” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵包斑,是天一觀的道長(zhǎng)流礁。 經(jīng)常有香客問(wèn)我,道長(zhǎng)罗丰,這世上最難降的妖魔是什么神帅? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮萌抵,結(jié)果婚禮上找御,老公的妹妹穿的比我還像新娘。我一直安慰自己绍填,他們只是感情好霎桅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著讨永,像睡著了一般滔驶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卿闹,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天揭糕,我揣著相機(jī)與錄音,去河邊找鬼锻霎。 笑死插佛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的量窘。 我是一名探鬼主播雇寇,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了锨侯?” 一聲冷哼從身側(cè)響起嫩海,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎囚痴,沒(méi)想到半個(gè)月后叁怪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡深滚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年奕谭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痴荐。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡血柳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出生兆,到底是詐尸還是另有隱情难捌,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布鸦难,位于F島的核電站根吁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏合蔽。R本人自食惡果不足惜击敌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拴事。 院中可真熱鬧沃斤,春花似錦、人聲如沸挤聘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)组去。三九已至,卻和暖如春步淹,著一層夾襖步出監(jiān)牢的瞬間从隆,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工缭裆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留键闺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓澈驼,卻偏偏與公主長(zhǎng)得像辛燥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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