handler源碼解析與面試擴(kuò)展

Handle在Android中的定位

1.所有的事件都是通過handle message 分發(fā)的。
通過launcher (app) 管理其他應(yīng)用APP的啟動拾给,利用zygote進(jìn)程兔沃,fork一個新進(jìn)程,分配jvm虛擬機(jī)(保證數(shù)據(jù)完整性窄锅,安全性)。每個java類追驴,都是通過一個main()函數(shù)啟動疏之,我們的應(yīng)用APP也是通過ActivityThread.main() 函數(shù)啟動,并且在此函數(shù)中丙曙,構(gòu)建了Looper實(shí)例對象其骄,執(zhí)行了Looper.loop() 函數(shù),用來不斷循環(huán)的取出消息拯爽。所以主線程中默認(rèn)有Looper毯炮,創(chuàng)建new Handle,不需要自定義Looper對象篮幢。
ActivityThread main()

 public static void main(String[] args) {
       ....
        Looper.prepareMainLooper(); //構(gòu)造Looper實(shí)例對象

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

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler(); //返回 new handler()創(chuàng)建的實(shí)例對象
        }

      .....
        Looper.loop(); //死循環(huán)为迈,輪播消息,分發(fā)給handle.handleMessage

     ....
    }
333.png

Handle工作流程

1.子線程中 Handle發(fā)送消息
2.MessageQueue把消息加入隊(duì)列(MessageQueue只是一個容器赋续,沒有線程)
3.主線程 中的 Looper把消息從隊(duì)列中 取出來另患,分發(fā)給handle
4.handle在回調(diào)函數(shù)中 處理數(shù)據(jù)(主線程)
5.如此一個循環(huán)步驟昆箕,實(shí)現(xiàn)了線程之間的通信

777.png
111.png

源碼解析

1.handler 發(fā)送消息 ,最終會調(diào)到sendMessageAtTime--》enqueueMessage()
public boolean sendMessageAtTime(@NonNull 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);
    }

  private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
 //msg消息 擁有handle,如果handle是內(nèi)部類鹏倘,那么有可能造成內(nèi)存泄露
        msg.target = this;
        ....
        return queue.enqueueMessage(msg, uptimeMillis);
    }
2.MessageQueue enqueueMessage() 把消息加入隊(duì)列

MessageQueue 是個單鏈接優(yōu)先級隊(duì)列

666.png
999.png
 boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {//同步鎖薯嗤,保證數(shù)據(jù)隊(duì)列的線程安全
           ....

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
           //判斷消息的優(yōu)先級,確定插入隊(duì)列位置
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
              
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) { //messageQueue為null時纤泵,進(jìn)入睡眠狀態(tài)
                nativeWake(mPtr);
            }
        }
        return true;
    }
3.Looper .prepare() 構(gòu)建Looper實(shí)例對象骆姐,加入ThreadLocal

Looper .prepare() 構(gòu)造Looper()實(shí)例對象镜粤,Looper()是私有構(gòu)造函數(shù)
整個APP 只有一個ThreadLocal 對象實(shí)例,利用ThreadLocalMap存儲Looper實(shí)例肉渴,保證一個線程,只有一個Looper

888.png
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//sThreadLocal  存儲Looper實(shí)例的實(shí)現(xiàn)函數(shù)
 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
      //key是當(dāng)前線程带射,value是Looper實(shí)例同规,所以一個線程只有一個Looper
            createMap(t, value); 
    }


 public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) { //一個線程只有一個Looper
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

  //構(gòu)建 主線程的Looper,在ActivityThread.main()調(diào)用窟社,
 //所以主線程new handle券勺,不需要對Looper進(jìn)行實(shí)例化
    @Deprecated
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

//以thread為key,在ThreadLocalMap中取出存儲的Looper實(shí)例對象
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

    /**
     * 獲取主線程的Looper對象實(shí)例
     */
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
4. Looper.loop() 循環(huán)遍歷messageQueue灿里,取出Message
 public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        ...

        for (;;) { //利用死循環(huán)关炼,循環(huán)取出messageQueue中的message,分發(fā)給handle的接口函數(shù)handleMessage()
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }

 private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
        Message msg = me.mQueue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return false;
        }
    ...
        try {
         //調(diào)用handle dispatchMessage
            msg.target.dispatchMessage(msg);
           ...
        } catch (Exception exception) {
          ....
        } finally {
            .....
        }
       ....
       
        //對message進(jìn)行清空處理匣吊,復(fù)用內(nèi)存盗扒,避免內(nèi)存碎片,產(chǎn)生內(nèi)存抖動缀去,造成oom
        msg.recycleUnchecked();

        return true;
    }

//messageQueue 通過next() 取出消息
 Message next() {
    
        final long ptr = mPtr;//是否進(jìn)入睡眠狀態(tài)
        if (ptr == 0) {
            return null;
        }
         ......
        for (;;) {
           .....

            synchronized (this) {
               ....
                Message prevMsg = null;
                Message msg = mMessages;
               ......
                if (msg != null) {
                    ....
                    } else {
                        ......
                        return msg;
                    }
                }
               .....
              
                if (mQuitting) { //Looper 調(diào)用了quit() 清空全部message信息
                    dispose();
                    return null;
                }
               ..........
        }
    }



//handle dispatchMessage,把message 回調(diào)給handleMessage()函數(shù)
 public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
6.Message 類的 obtain(),享元設(shè)計(jì)模式,復(fù)用內(nèi)存
  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();
    }

//message對象甸祭,數(shù)據(jù)清空缕碎,復(fù)用內(nèi)存
 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 = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

面試題解析

1.一個線程有幾個Handler?

可以有很多個Handler池户,在主線程中 new Handler(), 在子線程中 new Handle(Looper) 必須傳入對應(yīng)Looper對象咏雌。

2.一個線程有幾個Looper?怎么保證

一個線程只能有一個Looer校焦,通過ThreadLocal的ThreadLocalMap來保證赊抖,在Looper實(shí)例化的時候,會把Looper實(shí)例化對象存在ThreadLocalMap中寨典,key是當(dāng)前Thread氛雪。下次再調(diào)用Looper實(shí)例化函數(shù)prepare(),會先從ThreadLocalMap 讀取以thread為key的數(shù)據(jù),如果不為空耸成,拋出異常报亩。

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) { //一個線程只有一個Looper
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

3,Handler內(nèi)存泄露的原因井氢?為啥其他內(nèi)部類沒有這個問題弦追?

內(nèi)部類默認(rèn)持有外部類對象,所以handler實(shí)例默認(rèn)持有外部activity花竞。
在handler發(fā)送消息的函數(shù)中劲件,把handler對象賦值給了message,所以message持有了handler。而message的執(zhí)行是不確定的零远,按照順序執(zhí)行的苗分,如此一來即使activity退出頁面,也不會被內(nèi)存回收遍烦,造成內(nèi)存泄露俭嘁。可以使用靜態(tài)內(nèi)部 Handler來解決這個問題服猪。
其他內(nèi)部類 比如RecycleView adapter的viewHolder 為什么沒有造成內(nèi)存泄露供填,因?yàn)樗鴄ctivity的生命周期,當(dāng)activity退出罢猪,也會退出近她。

4.為何主線程可以直接new Handler(),子線程中想要new Handler()需要做哪些準(zhǔn)備?

Handler中必須有一個Looper對象膳帕。
因?yàn)橹骶€程在activityThread 的main()函數(shù)執(zhí)行的時候粘捎,就已經(jīng)創(chuàng)建了Looper的對象實(shí)例,并且調(diào)用了Loope.loop()函數(shù)危彩,所以不需要在對Looper對象操作攒磨。
想要在子線程中 new Handler()實(shí)例對象,必須初始化mLooper = Looper.prepar()構(gòu)建Looper實(shí)例對象汤徽,new Handler(mLooper) 創(chuàng)建handler對象娩缰,然后調(diào)用Looper.loop() 循環(huán)取出消息

5.子線程中維護(hù)的Looper,消息隊(duì)列無消息的時候的處理方案是什么谒府?有什么用拼坎?

消息隊(duì)列無消息的時候就進(jìn)入睡眠狀態(tài),Looper.loop死循環(huán)完疫,取出message 為null泰鸡,就退出本次循環(huán),然后再來一次壳鹤。Looper.quit()調(diào)用MessageQueue的quit()

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

//MessageQueue的quit()
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(); //清除所有的message數(shù)據(jù)
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr); //進(jìn)入睡眠狀態(tài)
        }

 private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked(); //清空數(shù)據(jù)盛龄,復(fù)用內(nèi)存
            p = n;
        }
        mMessages = null;
    }

    }

6.存在多個Handler 往messageQueue 中添加數(shù)據(jù),內(nèi)部是如何確保線程安全的芳誓?

利用鎖來保證線程安全讯嫂,synchronized 同步鎖,也稱之為內(nèi)置鎖兆沙,有jvm實(shí)現(xiàn)上鎖和解鎖操作欧芽。
messageQueue.enqueueMessage(),利用 synchronized (this) 對整個messageQueue對象上鎖,對象里面的所有函數(shù) 代碼塊都會受到限制葛圃。并且一個線程中只有一個Looper千扔,所有也只有一個可以操作messageQueue的地方憎妙,保證一個線程只有一個messagQueue對象。

   boolean enqueueMessage(Message msg, long when) {
      
        synchronized (this) {
           ....
                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;
    }

7.使用Message時應(yīng)該如何創(chuàng)建它曲楚?

為了保證內(nèi)存的復(fù)用厘唾,Message使用了享元設(shè)計(jì)模式,通過提供的obtain()函數(shù)來構(gòu)建Message對象龙誊,優(yōu)先使用已分配內(nèi)存并且清空數(shù)據(jù)的Message對象抚垃,如果不存在這樣的內(nèi)存對象,就new Message(只是個空函數(shù)) 構(gòu)建message 對象趟大,分配內(nèi)存鹤树。
在message對象被分配給handle之后,會調(diào)用 msg.recycleUnchecked();清空數(shù)據(jù)逊朽,復(fù)用內(nèi)存罕伯。

8.Looper死循環(huán)為什么不會導(dǎo)致應(yīng)用卡死。

anr是應(yīng)用無響應(yīng)叽讳,當(dāng)在主線程 執(zhí)行點(diǎn)擊操作 5s之內(nèi)沒有響應(yīng)追他, 或者廣播10s,服務(wù)20s岛蚤,執(zhí)行耗時操作沒有結(jié)束任務(wù)邑狸,就會報出ANR 應(yīng)用無響應(yīng) 彈窗。
無論是點(diǎn)擊事件涤妒,還是Anr事件都是一個message单雾,都需要Looper循環(huán)取出的。
比如 一個點(diǎn)擊事件届腐,生成一個message,在5s之內(nèi)沒有處理該message蜂奸,就會再發(fā)一個Anr message犁苏,因?yàn)樗膬?yōu)先級比較高,所以會先取出ANR的消息扩所,展示彈窗围详。
所以Looper的死循環(huán)和Anr不是一個層級的問題。

9.Handler 是怎么實(shí)現(xiàn)多線程通信的祖屏?

子線程中助赞,通過主線程創(chuàng)建的handler對象實(shí)例發(fā)送消息,調(diào)用messagQueue把消息加入消息隊(duì)列袁勺。messagQueue只是個容器雹食,沒有線程的區(qū)別。
因?yàn)橹骶€程創(chuàng)建的handler對象實(shí)例期丰,所以handler 對應(yīng)的looper就是ActivityThread main() 函數(shù)創(chuàng)建的looper群叶。
主線程中Looper.loop() 調(diào)用messagQueue.next() ,獲取對應(yīng)的message消息吃挑,通過handler.dispatchMessage() ,給回調(diào)函數(shù)handleMessage() 賦值街立。
子線程發(fā)送數(shù)據(jù)舶衬,主線程取出數(shù)據(jù),就這樣完成了線程之間的通信赎离。

Handler的擴(kuò)展使用

HandlerThread

HandlerThread 是Thread的子類,就是一個線程逛犹,只是在內(nèi)部幫助實(shí)現(xiàn)了Looper實(shí)例化。
方便使用梁剔,保證了線程安全虽画。


public class HandlerThread extends Thread {
   .....
    @Override
    public void run() {
      ...
        Looper.prepare();
        synchronized (this) {//保證線程安全
            mLooper = Looper.myLooper();
            notifyAll();//激活所有等待的線程,進(jìn)入就緒狀態(tài)
        }
       ....
        Looper.loop();
      .....
    }
    
  
    public Looper getLooper() {
       .....
        synchronized (this) { //保證線程安全憾朴,避免取出空looper
            while (isAlive() && mLooper == null) {
                try {
                    wait(); //進(jìn)入等待狀態(tài)
                } catch (InterruptedException e) {
                   .....
                }
            }
        }
      ......
        return mLooper;
    }

    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) { //內(nèi)部創(chuàng)建Handler實(shí)例對象
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

   ......
}

IntentService

是Service的子類狸捕,一般用于處理后臺耗時任務(wù)。內(nèi)部封裝 HandlerThread實(shí)現(xiàn)子線程處理耗時任務(wù)众雷。
內(nèi)部創(chuàng)建了Handler 實(shí)例對象灸拍,用來分發(fā)數(shù)據(jù)給回調(diào)函數(shù) onHandleIntent((Intent)msg.obj);
一個任務(wù)分成幾個子任務(wù),子任務(wù)按照順序先后執(zhí)行砾省,子任務(wù)全部執(zhí)行結(jié)束后鸡岗,這項(xiàng)任務(wù)才算成功。intentService就能完成這個任務(wù)编兄,并且很好的管理線程轩性,保證只有一個子線程處理工作,而且是一個一個完成任務(wù)狠鸳,有條不紊的順序執(zhí)行

@Deprecated
public abstract class IntentService extends Service {
....

//子線程 handler
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);//回調(diào)數(shù)據(jù)
            stopSelf(msg.arg1);
        }
    }
....

    @Override
    public void onCreate() {
      
        super.onCreate();
        //構(gòu)建線程
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        //構(gòu)建子線程的handler
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

   ....

    @Override
    public void onDestroy() {
      //退出循環(huán)
        mServiceLooper.quit();
    }

   ....

    @WorkerThread //因?yàn)閔andler是子線程創(chuàng)建的揣苏,所以此函數(shù)也工作在子線程,可以執(zhí)行耗時操作
    protected abstract void onHandleIntent(@Nullable Intent intent);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末件舵,一起剝皮案震驚了整個濱河市卸察,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铅祸,老刑警劉巖坑质,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異临梗,居然都是意外死亡涡扼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門盟庞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吃沪,“玉大人,你說我怎么就攤上這事什猖∠锊ǎ” “怎么了萎津?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抹镊。 經(jīng)常有香客問我锉屈,道長,這世上最難降的妖魔是什么垮耳? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任颈渊,我火速辦了婚禮,結(jié)果婚禮上终佛,老公的妹妹穿的比我還像新娘俊嗽。我一直安慰自己,他們只是感情好铃彰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布绍豁。 她就那樣靜靜地躺著,像睡著了一般牙捉。 火紅的嫁衣襯著肌膚如雪竹揍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天邪铲,我揣著相機(jī)與錄音芬位,去河邊找鬼。 笑死带到,一個胖子當(dāng)著我的面吹牛昧碉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播揽惹,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼被饿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了搪搏?” 一聲冷哼從身側(cè)響起狭握,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎慕嚷,沒想到半個月后哥牍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體毕泌,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡喝检,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了撼泛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挠说。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖愿题,靈堂內(nèi)的尸體忽然破棺而出损俭,到底是詐尸還是另有隱情蛙奖,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布杆兵,位于F島的核電站雁仲,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏琐脏。R本人自食惡果不足惜攒砖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望日裙。 院中可真熱鬧吹艇,春花似錦、人聲如沸昂拂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽格侯。三九已至鼻听,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間养交,已是汗流浹背精算。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碎连,地道東北人灰羽。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像鱼辙,于是被迫代替她去往敵國和親廉嚼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

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