全面剖析Android消息機(jī)制源碼


在Android應(yīng)用中簿废,消息機(jī)制可謂是處于舉足輕重的地步蔚叨,因?yàn)閁I是Android的整個(gè)門面展示,而UI的展示是交由消息機(jī)制來處理悠垛。Android不允許在子線程中進(jìn)行UI處理咒吐,因?yàn)檫@樣會(huì)引發(fā)多線程的安全問題野建,而解決這個(gè)問題則需要做加鎖等操作属划,這樣會(huì)導(dǎo)致效率低下,造成UI不流暢等問題候生,這是萬(wàn)萬(wàn)不可接受的同眯。

說到Android消息機(jī)制的用途,你可能會(huì)想到子線程和主線程的通信唯鸭、延遲發(fā)送一個(gè)消息或執(zhí)行一個(gè)Runnable等须蜗,但你有沒有想過,它是如何實(shí)現(xiàn)子線程和主線程的通信目溉?子線程和子線程之間是否能通過消息機(jī)制來進(jìn)行通信明肮?延遲發(fā)送或執(zhí)行的內(nèi)部原理又是如何實(shí)現(xiàn)的?另外你可能聽過這樣的問題——主線程在Looper.loop()中開啟了一個(gè)死循環(huán)停做,為什么不會(huì)造成ANR(Application Not Responding)晤愧?從MessageQueue中取出消息時(shí)可能會(huì)阻塞大莫,為什么該阻塞也不會(huì)造成ANR蛉腌?這些問題歸根結(jié)底就是原理問題,在看完本篇文章后都會(huì)茅塞頓開只厘,so follow me!

Android消息機(jī)制的簡(jiǎn)單圖解

image

消息的發(fā)送到處理可以大致分為5個(gè)步驟烙丛,分別是初始化準(zhǔn)備工作發(fā)送消息羔味、消息入隊(duì)河咽、Looper循環(huán)和消息出隊(duì),以及消息處理赋元,我們一步一步來看忘蟹。

1. 初始化準(zhǔn)備工作

平時(shí)我們?cè)谑褂?code>Handler發(fā)送消息時(shí),只需要?jiǎng)?chuàng)建一個(gè)Handler對(duì)象搁凸,然后調(diào)用相應(yīng)的發(fā)送方法即可媚值,使用起來特別簡(jiǎn)單。但其實(shí)在創(chuàng)建Handler對(duì)象之前护糖,主線程已經(jīng)做了一些準(zhǔn)備工作褥芒,其中就有MessageQueueLooper的創(chuàng)建初始化,并且將它們存放在主線程的私有內(nèi)存中嫡良。接下來從源碼中分析锰扶,首先來看Handler的構(gòu)造方法:

1.1 Handler中的初始化工作

// 構(gòu)造方法1
public Handler() {
    this(null, false);
}

// 構(gòu)造方法2
public Handler(Callback callback) {
    this(callback, false);
}

// 構(gòu)造方法3
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;
}

// 構(gòu)造方法4
public Handler(Looper looper) {
    this(looper, null, false);
}

// 構(gòu)造方法5
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}

// 構(gòu)造方法6
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

有6個(gè)構(gòu)造方法,我們先主要看構(gòu)造方法1構(gòu)造方法3寝受,其余構(gòu)造方法會(huì)在后面講解坷牛。其中,構(gòu)造方法1調(diào)用了構(gòu)造方法3很澄,然后在構(gòu)造方法3中京闰,注意mLooper = Looper.myLooper()這行代碼锨亏,獲取了一個(gè)Looper對(duì)象,然后接下來就對(duì)該Looper對(duì)象進(jìn)行了null判斷忙干,如果為null則拋出RunTime異常

Can't create handler inside thread xxx that has not called Looper.prepare()
因?yàn)闆]有調(diào)用Looper.preapre()方法器予,所以在xxx這個(gè)線程中不能創(chuàng)建Handler對(duì)象

你會(huì)想哎這不對(duì)啊捐迫?我平時(shí)創(chuàng)建Handler時(shí)也沒遇到過啊乾翔。其實(shí)前面說過了,主線程早已幫我們做了這些初始化的準(zhǔn)備工作了施戴,具體的代碼需要去Looper類里看看反浓。

1.2 Looper的初始化工作

首先看下Looper類的構(gòu)造方法

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

Looper的構(gòu)造方法里,創(chuàng)建了一個(gè)MessageQueue對(duì)象赞哗,獲取了當(dāng)前的Thread對(duì)象雷则。但該構(gòu)造方法是私有的,如何創(chuàng)建Looper對(duì)象呢肪笋?其實(shí)在上一小結(jié)中的Runtime異常中已經(jīng)告訴了答案月劈,即調(diào)用Looper.prepare()方法:

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()方法調(diào)用了prepare(boolean quitAllowed)方法,而該方法里也只有3行代碼藤乙。首先判斷當(dāng)前線程是否已經(jīng)創(chuàng)建了Looper對(duì)象猜揪,如果是則拋異常:

Only one Looper may be created per thread

否則創(chuàng)建一個(gè),并且將其存放到當(dāng)前線程的私有內(nèi)存中坛梁。如果你對(duì)ThreadLocal不太熟悉且想進(jìn)一步了解的話而姐,可以閱讀 Java之ThreadLocal詳解 這篇文章。

prepare()方法的作用就是在當(dāng)前線程中創(chuàng)建一個(gè)Looper對(duì)象划咐,并且創(chuàng)建關(guān)聯(lián)一個(gè)MessageQueue對(duì)象拴念,然后通過ThreadLocal將這個(gè)關(guān)聯(lián)了MessageQueue對(duì)象的Looper對(duì)象存放到當(dāng)前線程的私有內(nèi)存中,請(qǐng)記住褐缠,這是實(shí)現(xiàn)線程間通信的根本政鼠。文章后面會(huì)將這塊同整個(gè)消息機(jī)制串聯(lián)起來,屆時(shí)就會(huì)很清楚地理解了整個(gè)消息機(jī)制邏輯送丰。

另外缔俄,主線程的初始化Looper對(duì)象的方法如下,基本上和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();
    }
}

該方法是在ActivityThread類中的main()方法中調(diào)用器躏,這是應(yīng)用的入口方法俐载,啟動(dòng)時(shí)便會(huì)調(diào)用。所以說主線程的消息發(fā)送不需要手動(dòng)調(diào)用Looper.prepare()方法登失,因?yàn)橹骶€程早就做了這些準(zhǔn)備工作遏佣。

// ActivityThread類,此方法為應(yīng)用程序的入口方法
public static void main(String[] args) {
    ...省略部分代碼
    // 創(chuàng)建初始化Looper
    Looper.prepareMainLooper();

    ...省略部分代碼

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

    ...省略部分代碼
    // 開啟消息循環(huán)
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

注意到該方法中倒數(shù)第二行調(diào)用了Looper.loop()方法揽浙,它是一個(gè)死循環(huán)状婶,會(huì)一直調(diào)用消息隊(duì)列MessageQueuenext()方法獲取Message意敛,然后交由Handler處理。此處先知道其作用即可膛虫,后面第4章節(jié)會(huì)詳細(xì)介紹Looper.loop()方法草姻。

1.3 消息機(jī)制的初始化準(zhǔn)備工作小結(jié)

現(xiàn)在我們來整理下,一個(gè)完整的消息機(jī)制的初始化準(zhǔn)備工作基本上有以下3個(gè)步驟:

  1. 調(diào)用Looper.prepare()方法稍刀,創(chuàng)建一個(gè)關(guān)聯(lián)了MessageQueueLooper對(duì)象撩独,并通過ThreadLocal將其存放在當(dāng)前線程的私有內(nèi)存中,這是保證多線程間通信的根本账月;
  2. 創(chuàng)建一個(gè)Handler對(duì)象综膀;
  3. 調(diào)用Looper.loop()方法,開啟死循環(huán)從MessageQueue中獲取消息局齿,該方法的調(diào)用時(shí)機(jī)也可以放在步驟2之前剧劝。

以上便是消息機(jī)制的初始化準(zhǔn)備工作,接下來便可以進(jìn)行發(fā)送消息的操作了抓歼。

2. 發(fā)送消息

初始化準(zhǔn)備過程已經(jīng)完成了讥此,接下來就可以發(fā)送消息。在發(fā)送一條消息時(shí)锭部,我們可以調(diào)用Handler的以下send方法來實(shí)現(xiàn):

2.1 發(fā)送消息源碼分析

// 發(fā)送方法1.發(fā)送一條消息
public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

// 發(fā)送方法2.發(fā)送一條延遲處理的消息
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

// 發(fā)送方法3.發(fā)送一條空消息
public final boolean sendEmptyMessage(int what){
    return sendEmptyMessageDelayed(what, 0);
}

// 發(fā)送方法4.發(fā)送一條延遲處理的空消息
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

也可以調(diào)用post方法來投遞一個(gè)Runnable暂论,但其本質(zhì)上也是發(fā)送了一條消息:

// 發(fā)送方法5.投遞一個(gè)Runnable
public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}

// 發(fā)送方法6.投遞一個(gè)延遲處理的Runnable
public final boolean postDelayed(Runnable r, long delayMillis){
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

// 將Runnable轉(zhuǎn)為Message
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

方法5方法6雖然是投遞一個(gè)Runnable,但實(shí)質(zhì)上是通過getPostMessage(Runnable r)方法拌禾,將Runnable封裝到了Messagecallback變量中,最終也是發(fā)送了一個(gè)Message展哭。

上面6種發(fā)送消息的方法湃窍,其中

方法1內(nèi)部調(diào)用了方法2
方法3調(diào)用了方法4匪傍,而方法4內(nèi)部也調(diào)用了方法2您市;
方法5方法6內(nèi)部也是調(diào)用了方法2

可以看到send方法或post方法最終都指向了方法2役衡,那么接下來就分析方法2——sendMessageDelayed(Message msg, long delayMillis):

public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

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

// 消息入隊(duì)
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // MessageQueue的消息入隊(duì)
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到茵休,sendMessageDelayed(Message msg, long delayMillis)方法內(nèi)部調(diào)用了sendMessageAtTime(Message msg, long uptimeMillis)方法,其中參數(shù)uptimeMillis是一個(gè)時(shí)間參考手蝎,用來表示什么時(shí)候該Message會(huì)被執(zhí)行榕莺。

一條延遲處理的消息,其對(duì)應(yīng)的執(zhí)行時(shí)間uptimeMillis等于開機(jī)運(yùn)行時(shí)間SystemClock.uptimeMillis()加上延遲執(zhí)行的時(shí)間delayMillis(非延遲消息的delayMillis值為0)棵介,最終調(diào)用enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)方法钉鸯。

接下來在enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)方法中,會(huì)將當(dāng)前Handler對(duì)象封裝至Messagetarget變量邮辽,注意此處唠雕,后面在第五章節(jié)消息處理時(shí)會(huì)再回顧這行代碼贸营。最后調(diào)用MessageQueueenqueueMessage(Message msg, long when)方法中,進(jìn)行消息入隊(duì)操作岩睁。至此钞脂,Handler中的消息發(fā)送過程已經(jīng)完成了。

2.2 發(fā)送消息過程小結(jié)

發(fā)送消息的過程還是比較簡(jiǎn)單的捕儒,簡(jiǎn)單整理如下:

  1. 通過post系列方法或send系列方法發(fā)送一個(gè)消息Message芳肌;
  2. 如果是延遲消息,則該消息的執(zhí)行時(shí)間=開機(jī)運(yùn)行時(shí)間+延遲執(zhí)行時(shí)間肋层,否則執(zhí)行時(shí)間=開機(jī)運(yùn)行時(shí)間亿笤;
  3. 最后將當(dāng)前Handler對(duì)象封裝至Messagetarget中,再調(diào)用MessageQueueenqueueMessage(Message msg, long when)方法進(jìn)行入隊(duì)操作栋猖。

3. 消息入隊(duì)

消息發(fā)送完畢净薛,接下來就是消息入隊(duì)操作,對(duì)應(yīng)的代碼是MessageQueueenqueueMessage()方法:

3.1 消息入隊(duì)源碼分析

boolean enqueueMessage(Message msg, long when) {
    ...省略部分代碼

    synchronized (this) {
        ...省略部分代碼

        msg.markInUse();
        msg.when = when;
        // 獲取Message隊(duì)列的頭部
        // 注意:此隊(duì)列實(shí)質(zhì)上是一個(gè)單向鏈表蒲拉,目的是為了更方便地插入和移除消息
        Message p = mMessages;
        boolean needWake;
        // 滿足以下3個(gè)條件之一肃拜,便會(huì)將當(dāng)前Message設(shè)為隊(duì)列頭:
        // 1.隊(duì)列頭為空,即該隊(duì)列為空雌团;
        // 2.when為0燃领,該值可以手動(dòng)賦值,一般我們用不到锦援;
        // 3.當(dāng)前要入隊(duì)的消息執(zhí)行的時(shí)間早于隊(duì)列頭
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            // 一個(gè)新的隊(duì)列頭猛蔽,如果當(dāng)前隊(duì)列阻塞則喚醒,mBocked為true表示隊(duì)列是阻塞狀態(tài)
            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.
            // 一般來說不需要喚醒隊(duì)列的阻塞狀態(tài)灵寺,除非隊(duì)列頭是一個(gè)同步屏障(barrier)曼库,且當(dāng)前的Message是異步的,則根據(jù)阻塞狀態(tài)決定是否需要喚醒隊(duì)列
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            // 該循環(huán)的目的是按照when從小到大的順序略板,找到Message的位置
            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;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        // mPtr是native層的MessageQueue的引用地址毁枯,是在MessageQueue的構(gòu)造方法里初始化的
        // 這樣便可以將native層和java層的對(duì)象關(guān)聯(lián)起來
        // 如果needWake=true,則通過nativeWake(mPtr)方法喚醒阻塞中的隊(duì)列叮称,喚醒之后的操作种玛,將在下節(jié)消息出隊(duì)中講解
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

消息入隊(duì)的操作還是相對(duì)來說比較簡(jiǎn)單的,即:

如果當(dāng)前消息隊(duì)列為空瓤檐,或插入的Message執(zhí)行時(shí)間when早于隊(duì)列頭的Message赂韵,則將其置為消息隊(duì)列首部,并且將隊(duì)列從阻塞狀態(tài)中喚醒距帅;
否則按照Message的執(zhí)行時(shí)間排序右锨,將該Message插入到隊(duì)列中。

注意到needWake = mBlocked && p.target == null && msg.isAsynchronous()這行代碼碌秸,涉及到消息機(jī)制的同步屏障绍移,這里簡(jiǎn)單講解一下悄窃。

3.2 同步屏障(Sync Barrier)

在UI線程中,其主要目的就是保證及時(shí)有效地刷新UI蹂窖。假設(shè)現(xiàn)在需要刷新UI轧抗,但主線程的消息隊(duì)列中還存在其它的消息,那么就需要保證優(yōu)先執(zhí)行UI刷新的消息瞬测,屏蔽其它非UI相關(guān)的横媚,同步屏障就起到了這樣的作用。

3.2.1 源碼分析

一般來說我們發(fā)送消息時(shí)月趟,最終會(huì)在HandlerenqueueMessage()方法中將當(dāng)前Handler對(duì)象封裝至Messagetarget中灯蝴,但同步屏障消息是沒有Handler的,可以調(diào)用MessageQueuepostSyncBarrier()來發(fā)送一個(gè)消息屏障:

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

可以看到內(nèi)部并沒有設(shè)置給Message設(shè)置Handler孝宗,而且依舊是按照消息的執(zhí)行時(shí)間when來排序插入到隊(duì)列中穷躁。移除同步屏障調(diào)用MessageQueueremoveSyncBarrier(int token)方法即可,其內(nèi)部源碼就不貼出來了因妇,感興趣可自行查看问潭。

3.2.1 同步屏障和同步、異步消息

一般我們發(fā)送的消息是同步(synchronous)的婚被,有兩種方式可以設(shè)置發(fā)送異步消息:

  • 一是通過Handler構(gòu)造方法3狡忙、構(gòu)造方法6,將構(gòu)造參數(shù)async設(shè)為true即可址芯。通過這種方式灾茁,發(fā)送的所有消息都是異步的。
  • 另一種是調(diào)用MessagesetAsynchronous(boolean async)方法設(shè)置為true是复。通過這種方式删顶,當(dāng)前發(fā)送的消息是異步的。

同步屏障的作用就是屏蔽消息隊(duì)列中該同步屏障之后的所有同步消息淑廊,只處理異步消息,保證異步消息優(yōu)先執(zhí)行特咆,其具體代碼邏輯見4.2 消息出隊(duì)季惩。

3.2.3 同步屏障的應(yīng)用

同步屏障用于UI繪制,在ViewRootImpl類的scheduleTraversals()方法中調(diào)用:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // UI繪制之前設(shè)置一個(gè)同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 發(fā)送繪制的消息腻格,保證優(yōu)先執(zhí)行mTraversalRunnable
        // 最終會(huì)將該Runnable對(duì)象封裝至Message中画拾,并設(shè)置該Message為異步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

當(dāng)優(yōu)先執(zhí)行了mTraversalRunnable,調(diào)用其run()方法后菜职,run()方法內(nèi)部會(huì)調(diào)用doTraversal()方法青抛,該方法內(nèi)移除了之前設(shè)置的同步屏障,然后執(zhí)行UI繪制操作方法performTraversals()

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 移除之前設(shè)置的同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        // 進(jìn)行UI繪制
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

4. Looper循環(huán)和消息出隊(duì)

在1.3小節(jié)的消息機(jī)制初始化準(zhǔn)備小節(jié)中酬核,我們提到了Looper.loop()調(diào)用蜜另,其作用是開啟一個(gè)消息循環(huán)适室,然后從MessageQueue隊(duì)列中取出消息交由Handler處理。把它放到現(xiàn)在來講是因?yàn)?code>loop()方法和消息出隊(duì)next()操作緊密相連举瑰,我們先看loop()方法內(nèi)的實(shí)現(xiàn):

4.1 Looper循環(huán)

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;

    ...省略部分代碼

    for (;;) {
        // 當(dāng)消息隊(duì)列中沒有消息或延遲執(zhí)行消息時(shí)捣辆,MessageQueue的next()方法會(huì)阻塞
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        ...省略部分代碼

        try {
            // 進(jìn)行消息處理
            // 此target便是Handler#enqueueMessage(MessageQueue, Message, long)方法中第一行代碼 msg.target = this
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        
        ...省略部分代碼
        
        // Message回收
        msg.recycleUnchecked();
    }
}

該方法內(nèi)部實(shí)現(xiàn)還是比較簡(jiǎn)單的:首先做了一些檢驗(yàn)工作,然后開啟一個(gè)死循環(huán)此迅。在死循環(huán)中調(diào)用MessageQueuenext()方法獲取消息汽畴,如果有則交由其封裝的Handler處理(其處理邏輯見5. 消息處理),沒有則阻塞耸序。具體的阻塞和消息出隊(duì)忍些,都在MessageQueuenext()方法實(shí)現(xiàn),進(jìn)去看一看吧坎怪。

4.2 消息出隊(duì)next()

Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    // 在3.1 消息入隊(duì)源碼分析章節(jié)中罢坝,我們知道了mPtr是native層的MessageQueue的引用地址
    // 通過這個(gè)引用地址,可以將native層和java層關(guān)聯(lián)起來
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    // 用于統(tǒng)計(jì)當(dāng)前閑置Handler數(shù)量
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    // 阻塞的時(shí)長(zhǎng)
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // 實(shí)現(xiàn)阻塞芋忿,阻塞時(shí)長(zhǎng)為nextPollTimeoutMillis炸客,Looper.loop()方法中的might block就是來自這里
        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;
            // msg.target == null表示該Message是一個(gè)屏障(barrier)。
            // 如果是屏障戈钢,則跳過該屏障之后所有的同步消息痹仙,只執(zhí)行異步消息
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                // 從隊(duì)列中找出下一個(gè)異步Message
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    // 該Message執(zhí)行時(shí)間還未到,所以需要設(shè)置阻塞時(shí)長(zhǎng)
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    // 取出需要執(zhí)行的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.
                // 如果當(dāng)前隊(duì)列沒有消息殉了,則將nextPollTimeoutMillis設(shè)為-1
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // 消息隊(duì)列為空或Message未到執(zhí)行時(shí)間時(shí)开仰,則開始處理IdleHandler
            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                // 執(zhí)行IdleHandler中的queueIdle()方法
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

next()方法代碼內(nèi)部也是一個(gè)死循環(huán),代碼比較長(zhǎng)薪铜,我們分成兩部分邏輯來分析:一部分是前半段查找獲取消息的邏輯众弓,另一部分是為后半段處理IdleHandler的邏輯。

4.2.1 查找獲取消息

在死循環(huán)內(nèi)隔箍,首先判斷隊(duì)列頭是否為消息屏障谓娃,是則找出下一個(gè)異步的消息,否則取隊(duì)列頭消息蜒滩。然后判斷取出的消息執(zhí)行時(shí)間when

如果執(zhí)行時(shí)間沒到滨达,則設(shè)置阻塞時(shí)長(zhǎng),等下次循環(huán)時(shí)進(jìn)行阻塞俯艰,否則取出該消息并立刻返回捡遍。

阻塞的代碼為nativePollOnce(ptr, nextPollTimeoutMillis),這是一個(gè)native方法竹握,nextPollTimeoutMillis表示延遲時(shí)長(zhǎng):

  • nextPollTimeoutMillis=0:首次執(zhí)行next()方法的死循環(huán)時(shí)画株,調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis)方法,會(huì)立刻返回不會(huì)阻塞,然后繼續(xù)執(zhí)行后面的代碼谓传;
  • nextPollTimeoutMillis=-1:當(dāng)隊(duì)列為空時(shí)蜈项,nativePollOnce(ptr, nextPollTimeoutMillis)會(huì)一直阻塞,除非有消息入隊(duì)則觸發(fā)喚醒良拼;
  • nextPollTimeoutMillis>0:阻塞nextPollTimeoutMillis毫秒战得,在這期間如果有新的消息入隊(duì)則可能觸發(fā)喚醒(新的消息執(zhí)行時(shí)間早于nextPollTimeoutMillis則會(huì)喚醒)。

喚醒的操作由第3節(jié)消息入隊(duì)的nativeWake(mPtr)方法實(shí)現(xiàn)庸推。入隊(duì)喚醒和出隊(duì)阻塞的方法都是native方法常侦,由Linuxepoll機(jī)制實(shí)現(xiàn),感興趣可閱讀《深入理解Android 卷III》第二章 深入理解Java Binder和MessageQueue 這篇文章中的2.3小節(jié)贬媒。

4.2.2 處理IdleHandler

當(dāng)消息隊(duì)列為空或Message未到執(zhí)行時(shí)間時(shí)聋亡,則處理IdleHandlerIdleHandler可用于消息隊(duì)列閑置時(shí)的處理际乘,例如ActivityThread中的GcIdler坡倔,用于觸發(fā)主線程中的GC垃圾回收,當(dāng)主線程沒有消息處理時(shí)脖含,就會(huì)有可能觸發(fā)GC罪塔。

// ActivityThread類中的GcIdler內(nèi)部類
final class GcIdler implements MessageQueue.IdleHandler {
    @Override
    public final boolean queueIdle() {
        doGcIfNeeded();
        return false;
    }
}

4.3 Looper循環(huán)和消息出隊(duì)小結(jié)

在這一章節(jié)中,Looper.loop()循環(huán)方法主要是調(diào)用MessageQueuenext()方法獲取Message养葵,然后交由對(duì)應(yīng)的Hanlder處理征堪。

MessageQueue隊(duì)列如果為空,則一直阻塞关拒,等待下次消息入隊(duì)喚醒隊(duì)列佃蚜;不為空時(shí),當(dāng)消息的執(zhí)行時(shí)間未到着绊,則進(jìn)行nextPollTimeoutMillis>0時(shí)長(zhǎng)的阻塞谐算,直到阻塞時(shí)間結(jié)束,或有新的消息入隊(duì)归露,且其執(zhí)行時(shí)間早于當(dāng)前阻塞的消息執(zhí)行時(shí)間洲脂,則喚醒隊(duì)列。

接下來則看最后一個(gè)步驟剧包,關(guān)于消息的處理邏輯腮考。

5. 消息處理

Looper.loop()方法中,從MessageQueue中獲取到一條不為空的消息時(shí)玄捕,調(diào)用了msg.target.dispatchMessage(msg)進(jìn)行消息分發(fā)處理,此時(shí)又回到了Handler中棚放,看下dispatchMessage(Message msg)方法:

// Handler.java

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

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

首先會(huì)判斷msg.callback是否為null枚粘,這個(gè)callback就是封裝的Runnable對(duì)象,即Hanlder.post系列方法投遞的Runnable飘蚯。如果不為空馍迄,則執(zhí)行Runnablerun()方法福也。

否則,則判斷mCallback是否為null攀圈。這個(gè)mCallback是什么東西呢暴凑?可以回顧下Handler的構(gòu)造方法,其中構(gòu)造方法2赘来、3现喳、5、6都有一個(gè)構(gòu)造參數(shù)Callback犬辰,這個(gè)一個(gè)接口:

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

如果Callback接口的方法handleMessage(Message msg)返回為true嗦篱,則不再繼續(xù)分發(fā)消息,否則調(diào)用HandlerhandlerMessage(Message msg)方法幌缝,這是一個(gè)空方法灸促,一般選擇在Handler的子類實(shí)現(xiàn):

public void handleMessage(Message msg) {
}

一句話總結(jié)消息處理的邏輯:

  • 如果是post系列方法,則執(zhí)行其Runnablerun()方法涵卵;
  • 否則判斷Handler構(gòu)造方法里傳入的Callback是否返回為ture
    • true則消息處理結(jié)束浴栽;
    • false則繼續(xù)分發(fā)給HandlerhandleMessage(Message msg),然后結(jié)束轿偎。

6. Handler移除消息源碼分析

當(dāng)需要移除一個(gè)MessageRunnable時(shí)典鸡,調(diào)用Handler對(duì)應(yīng)的remove方法即可,其內(nèi)部調(diào)用的是MessageQueue對(duì)應(yīng)的remove方法贴硫。我們選擇Handler.removeMessages(int what)這個(gè)方法來分析椿每,其它移除邏輯基本一致。

// Handler.java

// 移除消息隊(duì)列中所有滿足Message.what=what的消息
public final void removeMessages(int what) {
    mQueue.removeMessages(this, what, null);
}

// MessageQueue.java

// 上面Handler的remove方法調(diào)用的是該方法
void removeMessages(Handler h, int what, Object object) {
    if (h == null) {
        return;
    }

    synchronized (this) {
        Message p = mMessages;

        // Remove all messages at front.
        // 此while循環(huán)移除回收了從隊(duì)列頭開始英遭,連續(xù)滿足移除條件的消息
        while (p != null && p.target == h && p.what == what
               && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }

        // Remove all messages after front.
        // 否則在此while循環(huán)中移除回收之后的消息
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                if (n.target == h && n.what == what
                    && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

主要是用了兩個(gè)while循環(huán)來移除消息间护,第一個(gè)移除前面連續(xù)滿足移除條件的消息,后面則依次判斷移除滿足條件的消息挖诸。

注意這里面有個(gè)暗坑汁尺,如果隊(duì)列里有延遲執(zhí)行的消息,其中有通過sendDelay發(fā)送的what=0的消息多律,也有通過postDelay投遞的Runnable痴突,如果調(diào)用Handler.removeMessages(0)方法來移除what=0的所有消息,很不幸你會(huì)發(fā)現(xiàn)狼荞,隊(duì)列中的所有Runnable封裝的消息也會(huì)被移除辽装。原因是封裝RunnableMessage,其what默認(rèn)為0相味,正好滿足移除what=0消息的邏輯拾积,所以定義what時(shí)需要注意,避免定義為0。

7. 消息發(fā)送到處理完整過程

一個(gè)完整的消息機(jī)制從開始到結(jié)束的細(xì)節(jié)差不多就分析完了拓巧,現(xiàn)在我們將整個(gè)過程串起來斯碌,簡(jiǎn)要回顧一番:

  1. 初始化準(zhǔn)備:手動(dòng)調(diào)用Looper.prepare()方法初始化創(chuàng)建一個(gè)Looper對(duì)象,其內(nèi)部會(huì)同時(shí)創(chuàng)建了一個(gè)與之關(guān)聯(lián)的MessageQueue對(duì)象肛度,然后通過ThreadLocal將該Looper對(duì)象存放至當(dāng)前線程的私有內(nèi)存中傻唾。接著手動(dòng)創(chuàng)建一個(gè)Handler,用于發(fā)送和處理消息承耿,可以通過構(gòu)造方法傳入之前創(chuàng)建的Looper帆精;也可以不傳丈探,則會(huì)使用當(dāng)前線程私有內(nèi)存中存放的Looper對(duì)象。接著手動(dòng)調(diào)用Looper.loop()方法,開啟一個(gè)死循環(huán)岸售,會(huì)一直調(diào)用MessageQueuenext()方法獲取消息草冈。
  2. 發(fā)送消息:手動(dòng)調(diào)用send系列方法或post方法瞎领,最終會(huì)將消息的延遲時(shí)間加上當(dāng)前開機(jī)后的時(shí)長(zhǎng)黎做,作為該消息的執(zhí)行時(shí)間;進(jìn)入sendMessageAtTime()方法阳堕,將當(dāng)前Handler封裝至Message中跋理,然后調(diào)用MessageQueueenqueueMessage()方法進(jìn)行入隊(duì)操作。
  3. 消息入隊(duì):按照上步中的執(zhí)行時(shí)間排序恬总,將消息插入到MessageQueue隊(duì)列中前普,如果隊(duì)列為空,或者該消息的執(zhí)行時(shí)間早于隊(duì)列中的所有消息執(zhí)行時(shí)間壹堰,則喚醒隊(duì)列的阻塞狀態(tài)
  4. 消息出隊(duì):由于初始化準(zhǔn)備工作中已經(jīng)開啟了Looper循環(huán)拭卿,所以當(dāng)MessageQueue中有消息到了需要執(zhí)行的時(shí)候,則會(huì)通過next()方法返回一個(gè)Message進(jìn)行消息分發(fā)贱纠。在next()方法中峻厚,如果隊(duì)列為空或者隊(duì)列中的消息執(zhí)行時(shí)間都未到,則會(huì)導(dǎo)致死循環(huán)進(jìn)入阻塞狀態(tài)谆焊。
  5. 消息處理:如果是post系列的方法惠桃,則調(diào)用其Runnable對(duì)象的run()方法,否則判斷Handler構(gòu)造方法傳入的Callback接口實(shí)現(xiàn)方法handleMessage()是否返回true辖试,是則結(jié)束消息處理辜王,否則再交由HandlerdispatchMessage()方法進(jìn)行最后的處理。

8. Q & A

現(xiàn)在可以回答文章開頭的問題了:

  1. Q: 主線程和子線程之間是如何實(shí)現(xiàn)通信的罐孝?

    A: 在主線程創(chuàng)建的Handler關(guān)聯(lián)了主線程私有的LooperMessageQueue呐馆,然后Handler在子線程發(fā)送的Message進(jìn)入到了主線程的MessageQueue,最終在主線程里通過Looper.loop()方法從MessageQueue中獲取Message莲兢,交由Handler處理摹恰。

  2. Q: 子線程和子線程之間能否通過消息機(jī)制來通信辫继?

    A: 能。需要在接收消息的子線程里俗慈,創(chuàng)建Handler之前需要手動(dòng)調(diào)用Looper.prepare(),之后調(diào)用Looper.loop()方法遣耍,這樣便可以在另一個(gè)子線程中發(fā)送消息到該子線程了闺阱。

  3. Q: 延遲發(fā)送或執(zhí)行的內(nèi)部原理又是如何實(shí)現(xiàn)的?

    A: 延遲的消息會(huì)將開機(jī)運(yùn)行時(shí)間加上延遲時(shí)間所得到的時(shí)間作為消息的執(zhí)行時(shí)間舵变,進(jìn)入消息隊(duì)列后按照?qǐng)?zhí)行時(shí)間來排序插入隊(duì)列中酣溃,出隊(duì)時(shí)會(huì)通過nativePollOnce()方法在底層實(shí)現(xiàn)阻塞狀態(tài),阻塞時(shí)長(zhǎng)為消息執(zhí)行時(shí)間減去當(dāng)前開機(jī)時(shí)長(zhǎng)的差值纪隙,待阻塞狀態(tài)結(jié)束后便會(huì)讓該消息出隊(duì)赊豌,并且交由Handler來分發(fā)處理。

  4. Q: 主線程在Looper.loop()中開啟了一個(gè)死循環(huán)绵咱,為什么不會(huì)造成ANR碘饼?從MessageQueue中取出消息時(shí)可能會(huì)阻塞,為什么該阻塞也不會(huì)造成ANR悲伶?

    A: 首先艾恼,ANR是因?yàn)檩斎胧录貌坏郊皶r(shí)處理,此外還有Serveice麸锉、Broadcast等钠绍,我們統(tǒng)一稱之為消息事件。當(dāng)消息事件發(fā)送了卻在規(guī)定的時(shí)間內(nèi)無法得到處理花沉,就會(huì)產(chǎn)生ANR現(xiàn)象柳爽。主線程調(diào)用Looper.loop()方法開啟一個(gè)死循環(huán),其目的就是用于分發(fā)處理這些消息事件碱屁,所以自然不會(huì)造成ANR磷脯,除非有其它消息事件做了耗時(shí)操作,才會(huì)有可能導(dǎo)致ANR發(fā)生忽媒。

    MessageQueue中取出消息時(shí)可能會(huì)阻塞争拐,什么情況下會(huì)阻塞呢?隊(duì)列為空或沒有需要及時(shí)處理的消息時(shí)晦雨,才會(huì)發(fā)生阻塞架曹,這是為了節(jié)約CUP資源不讓它空轉(zhuǎn)。如果你此時(shí)輸入一個(gè)消息時(shí)間闹瞧,阻塞狀態(tài)就會(huì)被喚醒绑雄,該事件會(huì)進(jìn)行入隊(duì)出隊(duì)分發(fā)處理操作,也就談不上不及時(shí)處理奥邮,自然不會(huì)導(dǎo)致ANR發(fā)生万牺。

9. 結(jié)束語(yǔ)

Android消息機(jī)制源碼分析基本上已經(jīng)結(jié)束了罗珍,由于技術(shù)水平有限及時(shí)間倉(cāng)促,難免會(huì)有錯(cuò)誤之處脚粟,還懇請(qǐng)指點(diǎn)出來覆旱,共同學(xué)習(xí)進(jìn)步!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末核无,一起剝皮案震驚了整個(gè)濱河市扣唱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌团南,老刑警劉巖噪沙,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異吐根,居然都是意外死亡正歼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門拷橘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來局义,“玉大人,你說我怎么就攤上這事膜楷⌒裱剩” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵赌厅,是天一觀的道長(zhǎng)穷绵。 經(jīng)常有香客問我,道長(zhǎng)特愿,這世上最難降的妖魔是什么仲墨? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮揍障,結(jié)果婚禮上目养,老公的妹妹穿的比我還像新娘。我一直安慰自己毒嫡,他們只是感情好癌蚁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著兜畸,像睡著了一般努释。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咬摇,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天伐蒂,我揣著相機(jī)與錄音,去河邊找鬼肛鹏。 笑死逸邦,一個(gè)胖子當(dāng)著我的面吹牛恩沛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缕减,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼雷客,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了烛卧?” 一聲冷哼從身側(cè)響起佛纫,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎总放,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體好爬,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡局雄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了存炮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炬搭。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖穆桂,靈堂內(nèi)的尸體忽然破棺而出宫盔,到底是詐尸還是另有隱情,我是刑警寧澤享完,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布灼芭,位于F島的核電站,受9級(jí)特大地震影響般又,放射性物質(zhì)發(fā)生泄漏彼绷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一茴迁、第九天 我趴在偏房一處隱蔽的房頂上張望寄悯。 院中可真熱鬧,春花似錦堕义、人聲如沸猜旬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)洒擦。三九已至,卻和暖如春糖耸,著一層夾襖步出監(jiān)牢的瞬間秘遏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工嘉竟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邦危,地道東北人洋侨。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像倦蚪,于是被迫代替她去往敵國(guó)和親希坚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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