閱讀Android消息機(jī)制源碼的隨手筆記

Looper疫剃、Message、Handler、MessageQueue是Android消息機(jī)制的幾個(gè)主要要素:

  • Looper:循環(huán),不停的循環(huán)從MessageQueue讀取消息
  • MessageQueue:消息隊(duì)列壁拉,通過(guò)一個(gè)單鏈表數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)消息隊(duì)列
  • Handler:用來(lái)發(fā)送和處理消息
  • Message:消息,包含必要的描述和屬性數(shù)據(jù)

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
  * {@link #prepare} in the thread that is to run the loop, and then
  * {@link #loop} to have it process messages until the loop is stopped.
  *
  * <p>Most interaction with a message loop is through the
  * {@link Handler} class.
  *
  * <p>This is a typical example of the implementation of a Looper thread,
  * using the separation of {@link #prepare} and {@link #loop} to create an
  * initial Handler to communicate with the Looper.
  *
  * <pre>
  *  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();
  *      }
  *  }</pre>
  */

這是在Looper類中的注釋柏靶,新線程默認(rèn)是沒(méi)有關(guān)聯(lián)Looper對(duì)象弃理,所以首先需要調(diào)用prepare()創(chuàng)建一個(gè)Looper對(duì)象,然后調(diào)用loop()循環(huán)處理消息直到Looper執(zhí)行退出操作屎蜓。

prepare()和prepare(boolean quitAllowed)方法

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

在上面源碼中主要關(guān)注這個(gè)成員變量sThreadLocal痘昌,這是一個(gè)ThreadLocal的實(shí)例。

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

ThreadLocal是什么?ThreadLocal是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類炬转,它可以在指定的線程中存儲(chǔ)數(shù)據(jù)辆苔,數(shù)據(jù)存儲(chǔ)以后,只有在指定線程中可以獲取到存儲(chǔ)的數(shù)據(jù)扼劈,其它線程無(wú)法獲取到該線程存儲(chǔ)數(shù)據(jù)驻啤。

如果執(zhí)行prepare()的線程已經(jīng)有了一個(gè)Looper實(shí)例就拋出RuntimeException異常,否則創(chuàng)建一個(gè)Looper實(shí)例并保存到sThreadLocal中荐吵。這也是為什么一個(gè)線程只能創(chuàng)建一個(gè)Looper實(shí)例骑冗。

下面我們看下Looper的構(gòu)造函數(shù)

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

在構(gòu)造函數(shù)中赊瞬,創(chuàng)建一個(gè)MessageQueue消息隊(duì)列實(shí)例mQueue,并且保存當(dāng)前線程的對(duì)象贼涩,參數(shù)quitAllowed表示該線程是否允許Looper退出循環(huán)巧涧。

loop()方法

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the 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();
    }
}

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
  • 調(diào)用myLooper()方法返回當(dāng)前線程關(guān)聯(lián)的Looper對(duì)象,如果當(dāng)前線程沒(méi)有任何關(guān)聯(lián)的Looper對(duì)象遥倦,loop()方法會(huì)拋出異常谤绳,異常信息提示我們?cè)趫?zhí)行l(wèi)oop()方法前,需要先執(zhí)行prepare()方法谊迄。
  • 執(zhí)行for無(wú)限循環(huán)闷供,在循環(huán)中調(diào)用mQueue.next()讀取消息隊(duì)列中的消息,當(dāng)讀取的消息為空時(shí)统诺,表示消息隊(duì)列正在執(zhí)行退出操作歪脏,直接return終止循環(huán)。
  • 調(diào)用msg.target.dispatchMessage(msg)方法處理消息粮呢。
  • 調(diào)用msg.recycleUnchecked()方法回收消息婿失,進(jìn)入下一次循環(huán)。
  • 根據(jù)loop()方法的注釋啄寡,當(dāng)我們需要終止消息循環(huán)時(shí)豪硅,可以調(diào)用Looper.quit()方法。

quit()和quitSafely()方法

public void quit() {
    mQueue.quit(false);
}

public void quitSafely() {
    mQueue.quit(true);
}

這兩個(gè)方法實(shí)際上都是調(diào)用MessageQueue的quit(boolean safe)方法挺物,該方法會(huì)將消息隊(duì)列標(biāo)識(shí)為正在退出并移除消息隊(duì)列中的消息懒浮,導(dǎo)致loop()方法中讀取的消息為空終止循環(huán)。
這兩個(gè)方法的區(qū)別识藤,我們等看到MeaageQueue的quit(boolean safe)方法源碼時(shí)在來(lái)分析砚著。

除了上述方法外,Looper還提供了一些其他的方法痴昧。

prepareMainLooper()方法

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

通過(guò)方法注釋稽穆,可以知道調(diào)用該方法創(chuàng)建的Looper對(duì)象,會(huì)被當(dāng)做應(yīng)用程序主線程的Looper對(duì)象赶撰。Android系統(tǒng)會(huì)調(diào)用該方法為我們創(chuàng)建主線程的Looper舌镶,我們不需要自己手動(dòng)去調(diào)用。
與prepare()方法不同該方法傳遞的quitAllowed參數(shù)為false豪娜,表示該線程的Looper.loop()方法不能被終止餐胀,即主線程的消息循環(huán)不允許被終止。

getMainLooper()方法

/**
 * Returns the application's main looper, which lives in the main thread of the application.
 */
public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

返回主線程的Looper對(duì)象瘤载。

isCurrentThread()方法

/**
 * Returns true if the current thread is this looper's thread.
 */
public boolean isCurrentThread() {
    return Thread.currentThread() == mThread;
}

判斷當(dāng)前線程是否是創(chuàng)建Looper對(duì)象的線程骂澄。

MessageQueue部分源碼

MessageQueue消息隊(duì)列,主要包含2個(gè)操作:插入和讀取惕虑。插入和讀取對(duì)應(yīng)的方法分別為enqueueMessage(Message msg, long when)next()坟冲。
雖然MessageQueue叫消息隊(duì)列磨镶,但是它實(shí)際上它是通過(guò)一個(gè)單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)消息列表,單鏈表在插入和刪除上比較有優(yōu)勢(shì)健提。

enqueueMessage(Message msg, long when)方法

boolean enqueueMessage(Message msg, long when) {
    // 如果msg沒(méi)有指明一個(gè)用來(lái)處理它的Handler則拋出異常
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    // 如果msg已經(jīng)被標(biāo)識(shí)為使用中則拋出異常
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    // 同步鎖
    synchronized (this) {
        // 如果MessageQueue正在退出則拋出異常琳猫,并將msg回收
        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();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        // 如果當(dāng)前MessageQueue的鏈表頭結(jié)點(diǎn)為空,或者msg觸發(fā)時(shí)間為0私痹,或者msg的觸發(fā)時(shí)間
        // 小于頭結(jié)點(diǎn)的觸發(fā)時(shí)間脐嫂,則將msg插入到鏈表頭部作為整個(gè)MessageQueue的頭結(jié)點(diǎn)
        // 同時(shí)線程如果是阻塞的,把needWake設(shè)為true
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // 插入msg到鏈表的中間
            // 如果線程阻塞紊遵、鏈表頭結(jié)點(diǎn)是同步屏障消息账千、msg是異步消息,把needWake設(shè)為true
            // 循環(huán)消息鏈表暗膜,如果鏈表節(jié)點(diǎn)為空或者節(jié)點(diǎn)觸發(fā)時(shí)間長(zhǎng)于msg匀奏,則將msg插入到該鏈表
            // 節(jié)點(diǎn)前面
            // 如果在msg之前已經(jīng)有異步消息且needWake已經(jīng)標(biāo)識(shí)為true,將needWake設(shè)為false

            // 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.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            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;
        }

        // 根據(jù)needWake標(biāo)識(shí)判斷是否需要喚醒線程處理
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

從上面的源碼來(lái)看該方法主要是將消息插入到單鏈表中的合適位置学搜,并判斷是否需要喚醒線程娃善。

next()方法

我們?cè)谇懊娼榻BLooper源碼時(shí)了解到,該方法會(huì)在Looper.loop()方法中反復(fù)被調(diào)用瑞佩。

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.
    // mPtr它保存著對(duì)應(yīng)的Native的消息隊(duì)列實(shí)例的地址
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    // 無(wú)限for循環(huán)
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        // nextPollTimeoutMillis >= 0 表示距離該消息處理時(shí)間的總時(shí)長(zhǎng)
        // nextPollTimeoutMillis = -1 表示沒(méi)有消息
        // 阻塞線程直到有新消息或者到消息需要處理的時(shí)間
        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;
            // 如果鏈表頭結(jié)點(diǎn)是同步消息屏障聚磺,則跳過(guò)同步消息,查找最先要處理的異步消息
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                // 如果當(dāng)前還沒(méi)到待處理消息的觸發(fā)時(shí)間炬丸,設(shè)置激活等待時(shí)間瘫寝,否則處理這個(gè)消
                // 息,將MessageQueue設(shè)置為非blocked狀態(tài)稠炬,并將消息從鏈表中移除焕阿,然后為
                // 消息設(shè)置FLAG_IN_USE的標(biāo)識(shí)并返回該消息
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a 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 {
                // 沒(méi)有消息,將nextPollTimeoutMillis 設(shè)置為-1
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // 如果當(dāng)前沒(méi)有待處理消息或者還沒(méi)到待處理消息觸發(fā)時(shí)間并且MessageQueue要求
            // 退出酸纲,則銷毀并返回null
            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // 在第一次進(jìn)入for循環(huán)且當(dāng)前沒(méi)有消息需要處理捣鲸,獲取其他待處理事務(wù)的數(shù)量
            // 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();
            }
            // 如果沒(méi)有其他利用隊(duì)列空閑要處理的事務(wù)瑟匆,則將MessageQueue設(shè)置為blocked闽坡,
            // 進(jìn)入下次循環(huán)
            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);
        }

        // 利用隊(duì)列空閑處理其它事務(wù)
        // 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 {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

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

        // 設(shè)置pendingIdleHandlerCount為0,以后不需要在處理這些事務(wù)
        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // 設(shè)置nextPollTimeoutMillis為0愁溜,因?yàn)楫?dāng)我們?cè)谔幚砥渌聞?wù)的時(shí)候疾嗅,新的Message
        // 可能已經(jīng)到來(lái)了,所以我們不需要等待冕象,立即開(kāi)始下次循環(huán)來(lái)檢查
        // 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;
    }
}

通過(guò)上面源碼我們知道代承,該方法會(huì)不停地去循環(huán)讀取MessageQueue中待處理的消息。
如果當(dāng)前MessageQueue中沒(méi)有消息渐扮,該方法會(huì)暫時(shí)阻塞等待消息的到來(lái)论悴,從而導(dǎo)致Looper.loop()方法也阻塞掖棉。如果這時(shí)發(fā)送一條消息會(huì)喚醒線程獲取該消息。
當(dāng)該方法讀取到待處理消息膀估,如果待處理消息的觸發(fā)時(shí)間長(zhǎng)于當(dāng)前時(shí)間就設(shè)置合理的等待時(shí)間幔亥,否則返回該消息,并將其從單鏈表中移除察纯。

postSyncBarrier()方法和removeSyncBarrier(int token)方法

在上面next()方法中帕棉,獲取消息時(shí)檢查了鏈表頭結(jié)點(diǎn)是否是同步消息屏障,那什么是同步消息屏障饼记?
同步消息屏障(SyncBarrier)是一個(gè)特殊的Message香伴,它的targer為null。當(dāng)消息隊(duì)列遍歷到這種消息類型的時(shí)候具则,它會(huì)跳過(guò)后面的同步Message獲取最先要處理的異步Message即纲。
我們可以調(diào)用postSyncBarrier()方法向單鏈表中插入一條SyncBarrier

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

方法源碼很容易理解乡洼,就是根據(jù)觸發(fā)時(shí)間when在單鏈表的合適位置插入一條target == null的消息崇裁,并返回一個(gè)token。

調(diào)用removeSyncBarrier(int token)方法從單鏈表中移除SyncBarrier束昵。

public void removeSyncBarrier(int token) {
    // Remove a sync barrier token from the queue.
    // If the queue is no longer stalled by a barrier then wake it.
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        final boolean needWake;
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked();

        // If the loop is quitting then it is already awake.
        // We can assume mPtr != 0 when mQuitting is false.
        if (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}

根據(jù)postSyncBarrier(long when)方法返回的token拔稳,從單鏈表中移除并回收指定token的SyncBarrier,如果該消息位于鏈表的頭結(jié)點(diǎn)锹雏,則將下個(gè)節(jié)點(diǎn)設(shè)為頭結(jié)點(diǎn)巴比,若該頭結(jié)點(diǎn)消息不是一個(gè)SyncBarrier并且MessageQueue不要求退出,喚醒線程礁遵。

  • SyncBarrier可以用來(lái)預(yù)加載網(wǎng)絡(luò)請(qǐng)求轻绞、預(yù)加載圖片、預(yù)加載文件佣耐、讀取數(shù)據(jù)庫(kù)等政勃。另外,為了讓View能夠有快速的布局和繪制兼砖,ViewRootImpl在執(zhí)行measure()和draw()時(shí)奸远,會(huì)向主線程的MessageQueue添加SyncBarrier。
  • 由于SyncBarrier會(huì)屏蔽后續(xù)的同步消息讽挟,所以當(dāng)執(zhí)行完任務(wù)后要記得把SyncBarrier移除消息隊(duì)列懒叛。

removeMessages和removeCallbacksAndMessages方法

void removeMessages(Handler h, int what, Object object) {
    if (h == null) {
        return;
    }

    synchronized (this) {
        Message p = mMessages;

        // Remove all messages at front.
        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 (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;
        }
    }
}

void removeMessages(Handler h, Runnable r, Object object) {
    if (h == null || r == null) {
        return;
    }

    synchronized (this) {
        Message p = mMessages;

        // Remove all messages at front.
        while (p != null && p.target == h && p.callback == r
               && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }

        // Remove all messages after front.
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                if (n.target == h && n.callback == r
                    && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

void removeCallbacksAndMessages(Handler h, Object object) {
    if (h == null) {
        return;
    }

    synchronized (this) {
        Message p = mMessages;

        // Remove all messages at front.
        while (p != null && p.target == h
                && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }

        // Remove all messages after front.
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                if (n.target == h && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

從上面源碼可以看出,這3個(gè)remove方法只是傳遞的參數(shù)不同耽梅,其方法邏輯都是相同的薛窥,即從鏈表頭結(jié)點(diǎn)開(kāi)始依次移除并回收所有匹配的消息。

quit(boolean safe)方法

在Looper源碼中我們知道眼姐,通過(guò)調(diào)用Looper.quit()和Looper.quitSafely()方法都可以退出循環(huán)诅迷,而這兩個(gè)方法都是執(zhí)行的該方法佩番。

void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;

        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }

        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}
  1. 當(dāng)mQuitAllowed為false會(huì)拋出異常,從異常信息中得知罢杉,主線程不允許退出消息循環(huán)答捕。這點(diǎn)可以在之前的Looper.prepareMainLooper()方法中提及過(guò)。
  2. 如果當(dāng)前MessageQueue已經(jīng)被標(biāo)識(shí)為正在退出直接return屑那,否則就標(biāo)識(shí)為正在退出拱镐。此時(shí)如果往消息隊(duì)列中插入消息將會(huì)直接回收該消息并返回false表示消息發(fā)送失敗。
  3. 在方法中根據(jù)參數(shù)safe持际,執(zhí)行不同的消息移除方法沃琅。(removeAllFutureMessagesLocked()和removeAllMessagesLocked())
  4. 執(zhí)行nativeWake(mPtr)方法喚醒線程。
- removeAllMessagesLocked()

Looper.quit()退出循環(huán)前的清空消息實(shí)際上調(diào)用的方法蜘欲。

private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}

依次移除并回收MessageQueue中的所有消息益眉。

- removeAllFutureMessagesLocked()

Looper.quitSafely()退出循環(huán)前的清空消息實(shí)際上調(diào)用的方法。

private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
        if (p.when > now) {
            removeAllMessagesLocked();
        } else {
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}

與removeAllMessagesLocked()方法不同姥份,該方法只會(huì)依次移除并回收MessageQueue中所有還沒(méi)到觸發(fā)時(shí)間的消息郭脂。

如果想了解關(guān)于Native looper的知識(shí),可以看看這篇博客

Message部分源碼

Message是線程之間傳遞信息的載體澈歉,包含了對(duì)消息的描述和任意的數(shù)據(jù)對(duì)象展鸡。常用屬性:arg1、arg2埃难、what莹弊、obj、target等涡尘,其中arg1和arg2可以存放整型數(shù)據(jù)忍弛,what可以用來(lái)標(biāo)識(shí)一條Message,obj可以存放Object類型的任意對(duì)象考抄,target就是處理一個(gè)Message的Handler细疚。

雖然Message的構(gòu)造函數(shù)是public的,但是最好是使用Message.obtain()方法獲取Message對(duì)象川梅,因?yàn)樵摲椒ǖ膶?shí)現(xiàn)中包含了回收再利用的機(jī)制疯兼,可以提供效率。

在了解obtain()方法和recycle()方法之前挑势,我們需要先知道sPool镇防、next啦鸣、sPoolSize這些指的是什么潮饱。

/*package*/ Message next;
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;

多個(gè)Message通過(guò)單鏈表結(jié)構(gòu)組合成一個(gè)默認(rèn)最大長(zhǎng)度為50的消息池,sPool是消息池中的第一個(gè)對(duì)象即頭結(jié)點(diǎn)诫给,sPoolSize是消息池中當(dāng)前的消息數(shù)量香拉,next是當(dāng)前Message結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)啦扬。

obtain()方法

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

如果消息池中沒(méi)有消息,我們直接調(diào)用Message的構(gòu)造方法創(chuàng)建一個(gè)新的Message對(duì)象凫碌,否則從消息池中讀取頭結(jié)點(diǎn)消息并把該消息的下一個(gè)消息設(shè)為頭結(jié)點(diǎn)扑毡,然后將消息從消息池中移除并清除所有標(biāo)識(shí)(FLAG_IN_USE和FLAG_ASYNCHRONOUS)。

recycle()和recycleUnchecked()方法

public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

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) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

在recycleUnchecked()方法中盛险,首先把消息標(biāo)識(shí)為使用中瞄摊,再將其他的屬性重置初始化,最后如果消息池還沒(méi)滿就將消息插入到消息池的頭結(jié)點(diǎn)位置苦掘。
在recycle()方法中會(huì)先判斷消息是否有使用中的標(biāo)識(shí)换帜,如果沒(méi)有才會(huì)調(diào)用recycleUnchecked()方法。

另外鹤啡,在Message中還有一些其他的方法比如

getData()和peekData()

public Bundle getData() {
    if (data == null) {
        data = new Bundle();
    }
        
    return data;
}

public Bundle peekData() {
    return data;
}

這兩個(gè)方法都是返回data數(shù)據(jù)惯驼,區(qū)別是當(dāng)data == null時(shí),peekData()會(huì)返回null递瑰,而getData()會(huì)創(chuàng)建并返回一個(gè)Bundle對(duì)象祟牲。

setTarget(Handler target)和sendToTarget()

public void setTarget(Handler target) {
    this.target = target;
}

public void sendToTarget() {
    target.sendMessage(this);
}

setTarget(Handler target)設(shè)置發(fā)送和處理消息的handler,sendToTarget()利用handler發(fā)送消息抖部。

setAsynchronous(async)

public void setAsynchronous(boolean async) {
    if (async) {
        flags |= FLAG_ASYNCHRONOUS;
    } else {
        flags &= ~FLAG_ASYNCHRONOUS;
    }
}

是否設(shè)置成異步消息说贝。

Handler部分源碼

構(gòu)造方法

Handler類雖然有多個(gè)不同參數(shù)的構(gòu)造方法,但最終調(diào)用的還是下面這兩個(gè)構(gòu)造方法慎颗。

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 Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

從上面源碼中我們可以出來(lái)這兩個(gè)構(gòu)造方法實(shí)際上都是為mLooper狂丝、mQueuemCallback哗总、mAsynchronous這4個(gè)成員變量賦值几颜,不同的是如果構(gòu)造方法參數(shù)中沒(méi)有Looper對(duì)象,需要調(diào)用Looper.myLooper()方法獲取當(dāng)前線程中的Looper對(duì)象讯屈。其中mQueue是用來(lái)接收Handler對(duì)象所發(fā)送消息的消息隊(duì)列蛋哭,mLooper所在的線程就是Handler對(duì)象處理消息的線程。

obtainMessage方法

public final Message obtainMessage()
{
    return Message.obtain(this);
}

public final Message obtainMessage(int what)
{
    return Message.obtain(this, what);
}
    
public final Message obtainMessage(int what, Object obj)
{
    return Message.obtain(this, what, obj);
}

public final Message obtainMessage(int what, int arg1, int arg2)
{
    return Message.obtain(this, what, arg1, arg2);
}
    
public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
{
    return Message.obtain(this, what, arg1, arg2, obj);
}

obtainMessage方法實(shí)際上都是調(diào)用相應(yīng)Message的obtain()方法創(chuàng)建消息涮母。

enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)方法

Handler類中所有的post系列方法谆趾,實(shí)際上都是調(diào)用相應(yīng)的sendMessage方法,而所有的sendMessage系列方法最后都是調(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);
}

該方法最后調(diào)用queue.enqueueMessage(msg, uptimeMillis)方法往mQueue中插入消息沪蓬。

-sendMessageAtFrontOfQueue(Message msg)方法

從方法名我們可以看出該sendMessage方法是將消息插入到消息隊(duì)列的隊(duì)首,我們看下源碼是不是這樣来候。

public final boolean sendMessageAtFrontOfQueue(Message msg) {
    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, 0);
}

方法最后調(diào)用enqueueMessage(queue, msg, 0)方法跷叉,我們?cè)贛essageQueue的enqueueMessage方法源碼中分析過(guò),當(dāng)when == 0會(huì)將消息插入到鏈表的頭結(jié)點(diǎn)。所以調(diào)用該方法發(fā)送的消息會(huì)插入到消息隊(duì)列的隊(duì)首云挟。

dispatchMessage(msg)方法

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
  1. 調(diào)用消息內(nèi)部的處理方法梆砸,如果沒(méi)有執(zhí)行2
  2. 調(diào)用創(chuàng)建Handler對(duì)象時(shí)指定的處理方法,如果沒(méi)有執(zhí)行3
  3. 調(diào)用Handler對(duì)象自身的處理方法

runWithScissors(final Runnable r, long timeout)方法

這個(gè)方法將會(huì)在Handler所在的線程中執(zhí)行傳入的Runnable對(duì)象园欣,同時(shí)阻塞調(diào)用線程的執(zhí)行岖瑰,直到Runnable對(duì)象的run()方法執(zhí)行完畢秃臣。

public final boolean runWithScissors(final Runnable r, long timeout) {
    if (r == null) {
        throw new IllegalArgumentException("runnable must not be null");
    }
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout must be non-negative");
    }

    if (Looper.myLooper() == mLooper) {
        r.run();
        return true;
    }

    BlockingRunnable br = new BlockingRunnable(r);
    return br.postAndWait(this, timeout);
}

private static final class BlockingRunnable implements Runnable {
    private final Runnable mTask;
    private boolean mDone;

    public BlockingRunnable(Runnable task) {
        mTask = task;
    }

    @Override
    public void run() {
        try {
            mTask.run();
        } finally {
            synchronized (this) {
                mDone = true;
                notifyAll();
            }
        }
    }

    public boolean postAndWait(Handler handler, long timeout) {
        if (!handler.post(this)) {
            return false;
        }

        synchronized (this) {
            if (timeout > 0) {
                final long expirationTime = SystemClock.uptimeMillis() + timeout;
                while (!mDone) {
                    long delay = expirationTime - SystemClock.uptimeMillis();
                    if (delay <= 0) {
                        return false; // timeout
                    }
                    try {
                        wait(delay);
                    } catch (InterruptedException ex) {
                    }
                }
            } else {
                while (!mDone) {
                    try {
                        wait();
                    } catch (InterruptedException ex) {
                    }
                }
            }
        }
        return true;
    }
}

下面我是根據(jù)方法上的注釋結(jié)合方法源碼的大致翻譯

  • 如果當(dāng)前線程和handler所在線程是同一線程兴垦,直接執(zhí)行run()方法茵汰。否則將runnable發(fā)送到handler相應(yīng)的消息隊(duì)列中并同步等待其run()方法運(yùn)行完畢。
  • 該方法是危險(xiǎn)的绑榴,使用不當(dāng)可能導(dǎo)致死鎖(因?yàn)橥降却褪强挎i實(shí)現(xiàn)的)搬男。
    永遠(yuǎn)不要在持有任何鎖時(shí)或者在重入的操作中調(diào)用此方法。
  • 該方法可能在這種情景中用到:一個(gè)后臺(tái)線程需要同步的等待handler所在線程的一個(gè)task執(zhí)行完畢彭沼。不過(guò)這往往是不良設(shè)計(jì)的一個(gè)征兆缔逛,這個(gè)時(shí)候可能改進(jìn)程序設(shè)計(jì)更合適。
  • 一種更合適的方法:你只需要啟動(dòng)一個(gè)Handler線程姓惑,然后在執(zhí)行后續(xù)操作之前褐奴,將一些初始化的操作交給Handler線程來(lái)執(zhí)行。
  • 如果超時(shí)的話于毙,該方法會(huì)返回false敦冬。但runnable依然在消息隊(duì)列中,稍后run()方法有可能會(huì)被執(zhí)行唯沮。
  • 如果使用了這個(gè)方法脖旱,那么在結(jié)束循環(huán)的時(shí)候一定要調(diào)用Looper.quitSafely(),否則會(huì)造成這個(gè)方法的永久掛起介蛉。
  • @hide隱藏方法萌庆。這個(gè)方法容易被濫用應(yīng)該會(huì)從API中拿掉。
    即使要把它當(dāng)成API的一部分币旧,我們也會(huì)先把它重命名成runUnsafe()類似這樣的名稱践险。

ActivityThread類和HandlerThread類

-ActivityThread

我們?cè)趧?chuàng)建主線程handler的時(shí)候,通常都是直接調(diào)用Handler()構(gòu)造方法吹菱,但在創(chuàng)建handler之前主線程需要先有一個(gè)Looper對(duì)象巍虫,在前面的Looper.prepareMainLooper()方法中我們提及過(guò)Android系統(tǒng)會(huì)調(diào)用該方法為我們創(chuàng)建主線程的looper,我們不需要自己手動(dòng)去調(diào)用鳍刷,但并不清楚Android系統(tǒng)是什么時(shí)候在主線程中創(chuàng)建looper的占遥。

ActivityThread類管理應(yīng)用進(jìn)程的主線程的執(zhí)行,ActivityThread中的main()方法相當(dāng)于普通Java程序的main()方法就是作為Android程序的入口输瓜,主線程的Looper對(duì)象就是在這里創(chuàng)建的瓦胎。

public static void main(String[] args) {

    // ...

    Looper.prepareMainLooper();

    // ...

    Looper.loop();

    // ...
}

方法中先執(zhí)行Looper.prepareMainLooper()方法創(chuàng)建looper芬萍,然后再執(zhí)行Looper.loop()方法啟動(dòng)消息循環(huán)。

-HandlerThread

如果我們想讓主線程通知子線程執(zhí)行一些任務(wù)時(shí)凛捏,我們可以在主線程將要通知的子線程中的Looper對(duì)象當(dāng)做參數(shù)調(diào)用Handler(looper)構(gòu)造方法創(chuàng)建handler,然后在主線程中調(diào)用handler發(fā)送消息到子線程的消息隊(duì)列中芹缔,通知子線程執(zhí)行任務(wù)坯癣。

HandlerThread是一個(gè)線程類繼承了Thread,它有自己的內(nèi)部Looper對(duì)象最欠,可以進(jìn)行消息循環(huán)示罗。

public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }
        
    // If the thread has been started, wait until the looper has been created.
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}

我們可以先在主線程中創(chuàng)建啟動(dòng)一個(gè)HandlerThread線程,然后調(diào)用HandlerThread線程的getLooper()方法獲取looper芝硬,再在主線程中調(diào)用Handler(looper)創(chuàng)建handler蚜点,handler會(huì)往HandlerThread線程的消息隊(duì)列中發(fā)送消息,并在該線程中處理消息拌阴。
此外绍绘,因?yàn)镠andler對(duì)象是在主線程中創(chuàng)建的,所以getLooper()方法是在主線程中調(diào)用的迟赃,而Looper.myLooper()方法是在HandlerThread線程中執(zhí)行的陪拘,因此主線程需要等待HandlerThread線程的Looper.myLooper()執(zhí)行完畢后才返回Looper對(duì)象。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纤壁,一起剝皮案震驚了整個(gè)濱河市左刽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酌媒,老刑警劉巖欠痴,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異秒咨,居然都是意外死亡喇辽,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)雨席,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)茵臭,“玉大人,你說(shuō)我怎么就攤上這事舅世〉┪” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵雏亚,是天一觀的道長(zhǎng)缨硝。 經(jīng)常有香客問(wèn)我,道長(zhǎng)罢低,這世上最難降的妖魔是什么查辩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任胖笛,我火速辦了婚禮,結(jié)果婚禮上宜岛,老公的妹妹穿的比我還像新娘长踊。我一直安慰自己,他們只是感情好萍倡,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布身弊。 她就那樣靜靜地躺著,像睡著了一般列敲。 火紅的嫁衣襯著肌膚如雪阱佛。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天戴而,我揣著相機(jī)與錄音凑术,去河邊找鬼。 笑死所意,一個(gè)胖子當(dāng)著我的面吹牛淮逊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扶踊,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼壮莹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了姻檀?” 一聲冷哼從身側(cè)響起命满,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绣版,沒(méi)想到半個(gè)月后胶台,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡杂抽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年诈唬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缩麸。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡铸磅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出杭朱,到底是詐尸還是另有隱情阅仔,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布弧械,位于F島的核電站八酒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏刃唐。R本人自食惡果不足惜羞迷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一界轩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧衔瓮,春花似錦浊猾、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至碍现,卻和暖如春幅疼,著一層夾襖步出監(jiān)牢的瞬間米奸,已是汗流浹背昼接。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悴晰,地道東北人慢睡。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像铡溪,于是被迫代替她去往敵國(guó)和親漂辐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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