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

我們都知道募寨,系統(tǒng)是不允許在子線程中訪問UI的族展,但如果在主線程中進(jìn)行耗時操作,又會極大地妨礙用戶體驗(yàn)拔鹰。
所以仪缸,我們可以在子線程中進(jìn)行耗時操作,操作完畢后通知主線程更新UI格郁。
為了方便在線程之間進(jìn)行通信腹殿,官方提供了一種很好的解決方法——Handler独悴。有效地解決了在子線程中無法訪問UI的矛盾。

使用Handler锣尉,通常需要繼承Handler類刻炒,并重寫handleMessage()方法。
舉一個簡單的用法例子:

// 在主線程中

 MyHandler mHandler = new MyHandler();

 private void getDataFromNet() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //耗時操作自沧,完成之后發(fā)送消息給Handler坟奥,完成UI更新;
                mHandler.sendEmptyMessage(0);
     
                // 如果想發(fā)送帶參消息拇厢,則用sendMessage()方法
            }

        }).start();
    }

 class MyHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 這個方法需要自己寫內(nèi)容
            switch (msg.what) {
                case 0:
                    //完成主界面更新,拿到數(shù)據(jù)
                    imageView.setImageResource(R.mipmap.ic_launcher);
                    break;
                default:
                    break;
            }
        }
    }

在以上的例子中爱谁,我們在主線程創(chuàng)建了一個MyHandler類型的對象mHandler,在子線程完成耗時操作(如網(wǎng)絡(luò)請求獲取圖片)之后孝偎,用sendEmptyMessage()/sendMessage()方法發(fā)送消息访敌,隨后在主線程中會調(diào)用mhandler的handleMessage()方法來處理消息(如顯示圖片)。
怎么樣衣盾,是不是很簡單寺旺?

接下來我們就來看看它內(nèi)部構(gòu)造。

相關(guān)角色

  • Handler :負(fù)責(zé)向消息隊列發(fā)送消息势决,以及處理從消息隊列中分發(fā)出來的消息阻塑。
  • Looper:負(fù)責(zé)無限循環(huán)檢查消息隊列中的消息。如果有消息則把它取出交給handler處理果复,同時把該消息從消息隊列中移除陈莽。
  • MessageQueue(消息隊列):負(fù)責(zé)存儲消息。以單鏈表的結(jié)構(gòu)對外提供插入和刪除的工作虽抄。

角色關(guān)系

Handler走搁、Looper、MessageQueue的關(guān)系

在Handler內(nèi)部有mLooper和mQueue兩個成員變量极颓,其中mQueue是mLooper的成員變量朱盐。
由此我們可以知道群嗤,如果在一個線程中沒有Looper菠隆,那么handler便無法向消息隊列中發(fā)送消息,更不用說從中取出消息并處理了狂秘。

但是從開頭的例子中我們也可以看到骇径,在主線程中,并沒有一個Looper被顯式地創(chuàng)建者春,那么為什么在可以直接創(chuàng)建Handler并被使用呢破衔?
這是因?yàn)椋瑧?yīng)用在啟動的時候先執(zhí)行了ActivityThreadMain钱烟,用Looper.prepareMainLooper()創(chuàng)建了一個在主線程進(jìn)行循環(huán)的looper晰筛,并用Looper.loop()開啟消息隊列嫡丙,從而handler可以正常使用。

public static void main(string[] args){ //在這里面創(chuàng)建了一個MainLooper读第,創(chuàng)建的過程如下:
//初始化
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if(sMainThreadHandler == null){
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
//動起來
Looper.loop();
}

以上這段代碼還可以說明一個問題曙博,并不是只有主線程中才能創(chuàng)建Handler,在子線程中也同樣可以怜瞒。
只要在子線程調(diào)用Looper.prepare()父泳,和Looper.loop(),無限循環(huán)讀取消息隊列的looper吴汪,就可以創(chuàng)建Handler了惠窄。

可是,Looper和Handler是怎么關(guān)聯(lián)到一起的呢漾橙?當(dāng)遇到多個繼承了Handler的類時杆融,Looper取出的消息又是怎樣交給正確的Handler的呢?這就需要看一下他們的調(diào)用關(guān)系了霜运。

調(diào)用關(guān)系

Handler在創(chuàng)建的時候擒贸,我們可以看到,它有兩個構(gòu)造方法觉渴。第一個是傳入一個looper變量介劫,為其內(nèi)部的mLooper賦值。

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

第二個比較常用案淋,不往里面?zhèn)鱨ooper座韵,而是自動獲取該線程的looper為其賦值。

// Handler類
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;
    }

值得注意的是獲得該線程的looper的語句:mLooper = Looper.myLooper();

它是怎么獲取的呢踢京?我們跟進(jìn)去看一下:

// Looper類

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

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

這里出現(xiàn)了一個很奇怪的變量——sThreadLocal誉碴。這個變量是ThreadLocal類型的,我們需要簡單了解一下ThreadLocal類瓣距,才能對整個過程有一個更加清楚的認(rèn)識黔帕。

ThreadLocal類解析

ThreadLocal是一個線程內(nèi)部的數(shù)據(jù)存儲類(只能存一個值),通過它可以在指定的線程中存儲數(shù)據(jù)蹈丸,數(shù)據(jù)存儲以后成黄,只有在指定線程中可以獲取到存儲的數(shù)據(jù),對去其他線程來說則無法獲取到該數(shù)據(jù)逻杖。
一般來說奋岁,當(dāng)某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)副本的時候,就可以考慮采用ThreadLocal荸百。
經(jīng)過前面的講解闻伶,我們可以想到Looper的作用域是線程,并且不同線程具有不同的Looper够话,這個時候通過ThreadLocal就可以輕松實(shí)現(xiàn)Looper在線程中的存取蓝翰。

可能說到這里大家還是不太明白這個類的神奇之處光绕,我舉一個直觀的例子,大家一看就知道啦畜份。

package com.example.cgj.loopertest;

import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    private ThreadLocal<Boolean> threadLocal; //存布爾類型的值

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        threadLocal = new ThreadLocal<>(); 
        threadLocal.set(true); // 在主線程把存的值設(shè)為true
        Log.d("threadLocal的值 , 當(dāng)前線程 :",threadLocal.get().toString()+"  "+Thread.currentThread().toString());

        TestAsyncTask testAsyncTask = new TestAsyncTask();
        testAsyncTask.execute();
    }


    class TestAsyncTask extends AsyncTask<Void,Integer,Void>{

        @Override
        protected Void doInBackground(Void... voids) {
            threadLocal.set(false); // 在子線程把存的值設(shè)為false
            Log.d("threadLocal的值 , 當(dāng)前線程 :",threadLocal.get().toString()+"  "+Thread.currentThread().toString());
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
        // 回到主線程奇钞,打印子線程運(yùn)行過后的值
        Log.d("threadLocal的值 , 當(dāng)前線程 :",threadLocal.get().toString()+"  "+Thread.currentThread().toString());  
        }
    }
}

請大家先不要看程序運(yùn)行結(jié)果,先推測一下打印的三個值分別會是什么漂坏。
推測完后我們再往下來驗(yàn)證結(jié)果是否正確:

09-05 22:01:35.704 21005-21005/com.example.cgj.loopertest D/threadLocal的值 , 當(dāng)前線程 :: true  Thread[main,5,main]
09-05 22:01:35.704 21005-21039/com.example.cgj.loopertest D/threadLocal的值 , 當(dāng)前線程 :: false  Thread[AsyncTask #1,5,main]
09-05 22:01:35.724 21005-21005/com.example.cgj.loopertest D/threadLocal的值 , 當(dāng)前線程 :: true  Thread[main,5,main]

是不是發(fā)生了一件奇怪的事情景埃?在子線程中明明把threadLocal中存的值設(shè)為了false,之后在主線程打印的值還是true顶别!怎么會是這樣谷徙,難道是沒有設(shè)置成功?

事實(shí)上驯绎,在一個線程對threadLocal進(jìn)行的操作完慧,完全不會對另一個線程造成任何影響。從threadLocal被創(chuàng)建的線程開始分支的每個子線程剩失,都單獨(dú)擁有threadLocal所存儲的值屈尼,當(dāng)這個值在線程內(nèi)進(jìn)行改變時,都不會影響其他線程的值拴孤。

所以脾歧,使用sThreadLocal.get()獲取該線程的Looper是一個很方便并且正確的選擇。

獲取到該線程的Looper之后演熟,再為Handler的mLooper變量賦值鞭执,就完成了該線程里Handler和Looper的關(guān)聯(lián)。

接下來我們再來看芒粹,Handler是怎樣向消息隊列發(fā)送消息的兄纺。

Handler發(fā)送消息的流程

為方便閱讀,我將部分內(nèi)容進(jìn)行了簡化化漆。

Handler發(fā)送消息的流程

最后來到了mQueue.enqueueMessage()這個方法估脆。說明它就是最終把消息加入到消息隊列的關(guān)鍵方法。我們來看一下它的源碼:

// MessageQueue類

boolean enqueueMessage(Message msg, long when) {
        // 判斷是否有接收此消息的Handler(用target值來標(biāo)記)座云,若沒有則拋出異常
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        // 判斷該消息是否已經(jīng)加入到消息隊列疙赠,若已加入則拋出異常
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            // 判斷該消息隊列是否已經(jīng)退出。如果退出了疙教,則無法發(fā)送消息棺聊,會拋出異常,并回收該消息贞谓。
            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();   // 把該消息標(biāo)記為已加入消息隊列
            msg.when = when;   // 設(shè)置該消息的傳輸時間
            Message p = mMessages;  
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                // 如果該消息隊列目前為空,則將該message作為頭節(jié)點(diǎn)
                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.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                // 將該消息插入到消息隊列(實(shí)現(xiàn)形式為單鏈表)
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

  // 中文注釋為作者注葵诈,英文為源碼注裸弦、

稍微有點(diǎn)長祟同,但一點(diǎn)點(diǎn)看完之后是不是明白了許多呢?

其實(shí)理疙,除了send系列方法晕城,還可以用post系列方法來發(fā)送消息(代碼如下)。但post系列方法最終也是通過send系列方法來實(shí)現(xiàn)的窖贤,所以這里只講述了sendMessage()方法的過程砖顷。

// Handler類
public final boolean post(Runnable r) {   returnsendMessageDelayed(getPostMessage(r), 0);   }

當(dāng)Handler向消息隊列插入一條消息后,Looper便能夠在消息隊列中讀取到該消息赃梧,并將它進(jìn)行分發(fā)滤蝠。這里涉及到兩點(diǎn)問題:讀取消息、分發(fā)消息授嘀。

Looper的工作機(jī)制

Looper是通過loop()方法來使消息循環(huán)系統(tǒng)起作用的物咳,這個方法非常重要。我們簡單地看一下它的內(nèi)容:

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

從以上代碼中可以看到蹄皱,loop()方法里面有一個死循環(huán)览闰,只有queue.next()返回null時才能退出循環(huán)。當(dāng)調(diào)用Looper的quit()/quitSafely()方法時巷折,Looper會調(diào)用MessageQueue的quit()方法來退出自己的消息隊列压鉴,當(dāng)消息隊列被標(biāo)記為退出后,它的next()方法就會返回null锻拘。
換句話說晴弃,就是如果looper不退出,它的loop()方法會一直循環(huán)下去逊拍。
Looper會調(diào)用MessageQueue的next方法來獲取新的消息上鞠,但next()是一個阻塞的方法,如果沒有新消息芯丧,next()方法會一直阻塞在那里芍阎,也導(dǎo)致loop()方法一直阻塞在那里。如果next()返回了新消息缨恒,Looper就會用msg.target.dispatchMessage(msg)方法分發(fā)消息谴咸。msg.target是一個Handler對象,用來識別這個消息應(yīng)該交給哪個Handler處理骗露。
當(dāng)一個消息被Looper讀取后岭佳,該消息會從消息隊列中銷毀。

這個過程可以用一個流程圖來簡單地概括:

Handler萧锉、MessageQueue珊随、Looper的工作流程

有一點(diǎn)不能忘記,如果在子線程中創(chuàng)建了Looper,記得在使用完畢后一定要調(diào)用Looper的quit的方法退出Looper叶洞。
Looper提供了兩種退出方法:

  1. quit() : 直接退出looper鲫凶,但消息隊列中可能還有消息沒有處理。
  2. quitSafely() : 不直接退出looper衩辟,等消息隊列中所有的消息都處理完畢后才安全地退出螟炫。

如果在該線程的任務(wù)都已經(jīng)結(jié)束后沒有調(diào)用quit方法來終止消息循環(huán),這個子線程會一直處在運(yùn)行狀態(tài)艺晴,等待新消息的到來昼钻。而如果退出了Looper,這個子線程的代碼才算全部執(zhí)行完封寞,才會終止自己然评。
所以,出于資源的考慮钥星,一定要在不需要的時候終止Looper沾瓦。

到現(xiàn)在,這個過程已經(jīng)漸具雛形了谦炒。但還少了最后一個步驟分析:分發(fā)處理消息的過程贯莺。

Handler處理消息的過程

Handler的dispatchMessage()方法的源碼如下:

// Handler類

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {  // 在Message類中有成員變量:Runnable callback;
            handleCallback(msg);
        } else {
            if (mCallback != null) {  // 在Handler類中有成員變量:final Callback mCallback;
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

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

public interface Callback {
        public boolean handleMessage(Message msg);
    }

首先,檢查Message的callback是否為null宁改。如果不是缕探,則調(diào)用handleCallback()方法來處理消息。我們通過跟蹤該變量可以知道还蹲,callback是一個Runnable對象爹耗,實(shí)際上就是在用Handler的post方法時所傳遞的Runnable參數(shù)。直接調(diào)用callback.run()即可處理谜喊。
如果是null潭兽,再檢查mCallback是否為null。不為null就調(diào)用mCallback的handMessage()方法來處理消息斗遏。
如果mCallback也是null山卦,就直接用handleMessage()方法來處理消息。
這里出現(xiàn)了一個接口:Callback诵次。通過它可以直接用如下方式創(chuàng)建Handler對象:

Handler handler = new Handler(new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    return false;
                }
            });

看到這個用法账蓉,結(jié)合源碼的注釋:“Callback interface you can use when instantiating a Handler to avoid having to implement your own subclass of Handler”,可以用Callback接口來創(chuàng)建一個Handler實(shí)例逾一,而不用派生Handler的子類铸本。
在文章開頭舉例子的時候,我們說常見的使用Handler的方法遵堵,是繼承Handler類箱玷,并重寫handleMessage()方法來處理具體的消息。
當(dāng)我們不想派生子類的時候,就可以通過Callback實(shí)現(xiàn)汪茧。

Handler處理消息的過程也可以歸納為一個流程圖:

dispatchMessage()方法的內(nèi)部流程

好了椅亚,以上就是安卓消息機(jī)制的全部內(nèi)容了限番。經(jīng)過之前的分析我們能夠看出舱污,其實(shí)Handler可以實(shí)現(xiàn)任何需要在兩個線程間進(jìn)行通信的功能,并不是只用來在子線程中通知主線程更新UI的弥虐,只是開發(fā)者比較常用它來更新UI而已扩灯。
而這種消息的傳遞也不只能用Handler實(shí)現(xiàn),只要一個標(biāo)志性變量在線程A中的改變能被線程B檢測到霜瘪,就能夠起到通知線程B的作用珠插,進(jìn)而使線程B作出響應(yīng)和改變。讀者們?nèi)绻信d趣甚至也可以仿照這種機(jī)制自己動手寫一個“Handler”出來颖对,只要具備了一個可以發(fā)送和處理消息的類捻撑,一個存儲消息的類,和一個不停循環(huán)檢查讀取消息并分發(fā)的類缤底,讓它們相互間協(xié)調(diào)配合默契顾患,就可以完成啦。

最后个唧,作者尚在學(xué)習(xí)江解,歡迎指摘~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市徙歼,隨后出現(xiàn)的幾起案子犁河,更是在濱河造成了極大的恐慌,老刑警劉巖魄梯,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桨螺,死亡現(xiàn)場離奇詭異,居然都是意外死亡酿秸,警方通過查閱死者的電腦和手機(jī)灭翔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來允扇,“玉大人缠局,你說我怎么就攤上這事】既螅” “怎么了狭园?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長糊治。 經(jīng)常有香客問我唱矛,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任绎谦,我火速辦了婚禮管闷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窃肠。我一直安慰自己包个,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布冤留。 她就那樣靜靜地躺著碧囊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纤怒。 梳的紋絲不亂的頭發(fā)上糯而,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機(jī)與錄音泊窘,去河邊找鬼熄驼。 笑死,一個胖子當(dāng)著我的面吹牛烘豹,可吹牛的內(nèi)容都是我干的瓜贾。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼吴叶,長吁一口氣:“原來是場噩夢啊……” “哼阐虚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚌卤,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤实束,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后逊彭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咸灿,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年侮叮,在試婚紗的時候發(fā)現(xiàn)自己被綠了避矢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡囊榜,死狀恐怖审胸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卸勺,我是刑警寧澤砂沛,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站曙求,受9級特大地震影響碍庵,放射性物質(zhì)發(fā)生泄漏映企。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一静浴、第九天 我趴在偏房一處隱蔽的房頂上張望堰氓。 院中可真熱鬧,春花似錦苹享、人聲如沸双絮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掷邦。三九已至白胀,卻和暖如春椭赋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背或杠。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工哪怔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人向抢。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓认境,卻偏偏與公主長得像,于是被迫代替她去往敵國和親挟鸠。 傳聞我的和親對象是個殘疾皇子叉信,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

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