第2章 IPC機(jī)制

2.1 Android IPC簡(jiǎn)介

  1. Linux的IPC通信機(jī)制:
    命名通道、共享內(nèi)存凛篙、信號(hào)量等來(lái)進(jìn)行IPC。
  2. Android系統(tǒng)使用Binder機(jī)制來(lái)實(shí)現(xiàn)IPC,也可以使用Socket實(shí)現(xiàn)任意兩個(gè)終端之間的通信润讥。

2.2 Android中的多進(jìn)程模式

  1. 通過(guò)給四大組件指定android:process屬性就可以開(kāi)啟多進(jìn)程模式。

    1. 默認(rèn)進(jìn)程的進(jìn)程名是包名packageName
    2. 進(jìn)程名以:開(kāi)頭的進(jìn)程屬于當(dāng)前應(yīng)用的私有進(jìn)程盘寡,其他應(yīng)用的組件不可以和它跑在同一個(gè)進(jìn)程中楚殿,
    3. 進(jìn)程名不以:開(kāi)頭的進(jìn)程屬于全局進(jìn)程,其他應(yīng)用通過(guò)ShareUID方法可以和它跑在同一個(gè)進(jìn)程中竿痰。
    4. 兩個(gè)應(yīng)用必須有相同的ShareUID而且簽名相同脆粥,才可以泡在同一個(gè)進(jìn)程。
  2. android系統(tǒng)會(huì)為每個(gè)進(jìn)程分配一個(gè)獨(dú)立的虛擬機(jī)影涉,不同的虛擬機(jī)在內(nèi)存分配上有不同的地址空間变隔,所以不同的虛擬機(jī)中訪問(wèn)同一個(gè)類(lèi)的對(duì)象會(huì)產(chǎn)生多個(gè)副本。

  3. 多進(jìn)程會(huì)造成幾個(gè)問(wèn)題:

    1. 靜態(tài)成員和單例模式失效
    2. 線程同步機(jī)制失效
    3. sharedPreferences失效(sharedPreferences不支持兩個(gè)進(jìn)程同時(shí)去執(zhí)行寫(xiě)操作)
    4. Application會(huì)多次創(chuàng)建蟹倾。(系統(tǒng)要在創(chuàng)建新的進(jìn)程的同時(shí)分配獨(dú)立的虛擬機(jī)匣缘,應(yīng)用會(huì)重新啟動(dòng)一次,也就會(huì)創(chuàng)建新的Application鲜棠。)

2.3 IPC基礎(chǔ)概念

2.3.1 Serializable接口

  1. serialVersionUId是一串long型數(shù)字肌厨,主要是用來(lái)輔助序列化和反序列化的,原則上序列化后的數(shù)據(jù)中的serialVersionUId只有和當(dāng)前類(lèi)的serialVersionUId相同才能夠正常地被反序列化豁陆。
  2. 靜態(tài)成員變量屬于類(lèi)不屬于對(duì)象柑爸,因此不會(huì)參與序列化過(guò)程
  3. transient關(guān)鍵字標(biāo)記的成員變量不參與序列化過(guò)程

2.3.2 Parcelable接口

Parcelable接口內(nèi)部包裝了可序列化的數(shù)據(jù),可以在Binder中自由傳輸盒音,Parcelable主要用在內(nèi)存序列化上表鳍,可以直接序列化的有Intent馅而、Bundle、Bitmap以及List和Map等等

public class Book implements Parcelable {
    public int bookId;
    public String bookName;
    public Book() {
    }

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    //“內(nèi)容描述”进胯,如果含有文件描述符返回1用爪,否則返回0,幾乎所有情況下都是返回0
    public int describeContents() {
        return 0;
    }

    //實(shí)現(xiàn)序列化操作胁镐,flags標(biāo)識(shí)只有0和1偎血,1表示標(biāo)識(shí)當(dāng)前對(duì)象需要作為返回值返回,不能立即釋放資源盯漂,幾乎所有情況都為0
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(bookId);
        out.writeString(bookName);
    }

    //實(shí)現(xiàn)反序列化操作
    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
        //從序列化后的對(duì)象中創(chuàng)建原始對(duì)象
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }
        public Book[] newArray(int size) {//創(chuàng)建指定長(zhǎng)度的原始對(duì)象數(shù)組
            return new Book[size];
        }
    };

    private Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

}

注意:
如果有一個(gè)實(shí)現(xiàn)了Parcelable接口的User類(lèi)颇玷,Book對(duì)象是User對(duì)象的成員。那么在User的反序列化時(shí)就缆,需要傳遞當(dāng)前線程的上下類(lèi)加載器

    private User(Parcel in){
        book = in.readParcelable(Thread.currentThread().getContextClassloader());
    }

2.3.3 Binder接口

  1. 概念:
    1. Binder是Android中的一個(gè)類(lèi)帖渠,它實(shí)現(xiàn)了IBinder接口。
    2. 從IPC角度看竭宰,Binder是Android中一種跨進(jìn)程通信的方式空郊;
    3. Binder還可以理解為虛擬的物理設(shè)備,它的設(shè)備驅(qū)動(dòng)是/dev/binder切揭;
    4. 從Framework層角度看狞甚,Binder是ServiceManager連接各種Manager和相應(yīng)的ManagerService的橋梁;
    5. 從Android應(yīng)用層來(lái)說(shuō)廓旬,Binder是客戶(hù)端和服務(wù)端進(jìn)行通信的媒介哼审,當(dāng)bindService的時(shí)候,服務(wù)端會(huì)返回一個(gè)包含了服務(wù)端業(yè)務(wù)調(diào)用的Binder對(duì)象孕豹,通過(guò)這個(gè)Binder對(duì)象涩盾,客戶(hù)端就可以獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù)。
  2. aidl生成的java接口解析
    1. 聲明了幾個(gè)接口方法
    2. 聲明一個(gè)內(nèi)部類(lèi)Stub励背。Stub是一個(gè)Binder類(lèi)春霍。當(dāng)客戶(hù)端和服務(wù)端在一個(gè)進(jìn)程內(nèi)時(shí),方法調(diào)用不會(huì)走跨進(jìn)程的transact叶眉;當(dāng)客戶(hù)端和服務(wù)端處于不同進(jìn)程终畅,方法調(diào)用要走跨進(jìn)程的transact,這個(gè)邏輯由Stub的內(nèi)部代理類(lèi)Proxy來(lái)完成竟闪。
  3. Stub類(lèi)的方法定義:
    1. asInterface(android.os.IBinder obj):將服務(wù)端的Binder對(duì)象轉(zhuǎn)換成客戶(hù)端所需的AIDL接口類(lèi)型的對(duì)象离福,這種轉(zhuǎn)換過(guò)程是區(qū)分進(jìn)程的,如果客戶(hù)端和服務(wù)端是在同一個(gè)進(jìn)程中炼蛤,那么這個(gè)方法返回的是服務(wù)端的Stub對(duì)象本身妖爷,否則返回的是系統(tǒng)封裝的Stub.Proxy對(duì)象。
    2. asBinder:返回當(dāng)前Binder對(duì)象。
    3. onTransact:這個(gè)方法運(yùn)行在服務(wù)端中的Binder線程池中絮识,當(dāng)客戶(hù)端發(fā)起跨進(jìn)程請(qǐng)求(mRemote.transact)時(shí)绿聘,遠(yuǎn)程請(qǐng)求會(huì)通過(guò)系統(tǒng)底層封裝后交由此方法來(lái)處理。
    4. 聲明了幾個(gè)標(biāo)識(shí)接口方法的整型id(static)次舌。這幾個(gè)id用于標(biāo)識(shí)在transact過(guò)程中客戶(hù)端所請(qǐng)求的到底是哪個(gè)方法熄攘。
    5. 聲明DESCRIPTIOR: Binder唯一標(biāo)識(shí),一般用類(lèi)名表示
  4. Proxy的方法定義:
    1. asBinder:返回服務(wù)端的Binder對(duì)象
    2. getInterfaceDescriptor: 返回DESCRIPTIOR
    3. Proxy#XXX接口方法:所有的接口方法都是調(diào)用mRemote.transact(id, data_parcel, reply_parcel, 0)彼念。流程為:客戶(hù)端發(fā)送RPC調(diào)用請(qǐng)求挪圾,同時(shí)客戶(hù)端當(dāng)前線程掛起;然后服務(wù)端onTransact方法調(diào)用逐沙,直到RPC過(guò)程返回后哲思,當(dāng)前客戶(hù)端線程繼續(xù)執(zhí)行,從reply_parcel中取出RPC過(guò)程中的返回結(jié)果吩案。
  5. linkToDeath/unlinkToDeath
    Binder運(yùn)行在服務(wù)端棚赔,如果由于某種原因服務(wù)端異常終止了的話會(huì)導(dǎo)致客戶(hù)端的遠(yuǎn)程調(diào)用失敗,所以Binder提供了兩個(gè)配對(duì)的方法linkToDeath和unlinkToDeath徘郭。

如何給Binder設(shè)置死亡代理:

1. 聲明一個(gè)DeathRecipient對(duì)象靠益,DeathRecipient是一個(gè)接口,其內(nèi)部只有一個(gè)方法bindeDied残揉,實(shí)現(xiàn)這個(gè)方法在Binder死亡的時(shí)候收到捆毫。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if (mRemoteBookManager == null) return;
        mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
        mRemoteBookManager = null;
        // TODO:這里重新綁定遠(yuǎn)程Service
    }
};
2. 在客戶(hù)端綁定遠(yuǎn)程服務(wù)成功之后,給binder設(shè)置死亡代理
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);

2.4 Android中的IPC方式

2.4.1. 使用Bundle:

Bundle實(shí)現(xiàn)了Parcelable接口冲甘,Activity、Service和Receiver都支持在Intent中傳遞Bundle數(shù)據(jù)途样。

2.4.2. 使用文件共享:

這種方式簡(jiǎn)單江醇,適合在對(duì)數(shù)據(jù)同步要求不高的進(jìn)程之間進(jìn)行通信,并且要妥善處理并發(fā)讀寫(xiě)的問(wèn)題何暇。 SharedPreferences是一個(gè)特例陶夜,雖然它也是文件的一種,但是由于系統(tǒng)對(duì)它的讀寫(xiě)有一定的緩存策略裆站,即在內(nèi)存中會(huì)有一份SharedPreferences文件的緩存条辟,因此在多進(jìn)程模式下,系統(tǒng)對(duì)它的讀寫(xiě)就變得不可靠宏胯,當(dāng)面對(duì)高并發(fā)讀寫(xiě)訪問(wèn)的時(shí)候羽嫡,有很大幾率會(huì)丟失數(shù)據(jù),因此肩袍,不建議在進(jìn)程間通信中使用SharedPreferences杭棵。

2.4.3. 使用Messenger:Messenger是一種輕量級(jí)的IPC方案,它的底層實(shí)現(xiàn)就是AIDL氛赐。Messenger是以串行的方式處理請(qǐng)求的魂爪,即服務(wù)端只能一個(gè)個(gè)處理先舷,不存在并發(fā)執(zhí)行的情形。

Messenger中進(jìn)行數(shù)據(jù)傳輸必須將數(shù)據(jù)放入Message中滓侍,一般情況下

  1. 服務(wù)端會(huì)建一個(gè)Hander和Messenger對(duì)應(yīng)蒋川,并通過(guò)mMessenger.getBinder()暴露Binder接口給客戶(hù)端
  2. 客戶(hù)端通過(guò)new Messenger(binder)得到Messenger,然后通過(guò)mMessenger.send(mMessage)來(lái)實(shí)現(xiàn)數(shù)據(jù)傳輸
  3. 服務(wù)端得到數(shù)據(jù)后撩笆,Handler做處理
  4. 如客戶(hù)端需要反饋結(jié)果捺球,那需要客戶(hù)端也建立Messenger對(duì)應(yīng)的Hander。而服務(wù)端在Handler處理時(shí)浇衬,通過(guò)mMessage.replyTo得到客戶(hù)端的Messenger懒构,然后通過(guò)mMessenger.send(mReplyMessage)將result發(fā)給客戶(hù)端。最后客戶(hù)端在Handler中得到result耘擂。

2.4.4 使用AIDL

大致流程:首先建一個(gè)Service和一個(gè)AIDL接口胆剧,接著創(chuàng)建一個(gè)類(lèi)繼承自AIDL接口中的Stub類(lèi)并實(shí)現(xiàn)Stub類(lèi)中的抽象方法,在Service的onBind方法中返回這個(gè)類(lèi)的對(duì)象醉冤,然后客戶(hù)端就可以綁定服務(wù)端Service秩霍,建立連接后就可以訪問(wèn)遠(yuǎn)程服務(wù)端的方法了。

注意:

  1. AIDL的包結(jié)構(gòu)在服務(wù)端和客戶(hù)端要保持一致蚁阳,否則會(huì)運(yùn)行出錯(cuò)
  2. AIDL方法是在服務(wù)端的Binder線程池中執(zhí)行的铃绒,因此當(dāng)多個(gè)客戶(hù)端同時(shí)運(yùn)行時(shí),會(huì)出現(xiàn)多個(gè)線程同時(shí)訪問(wèn)的情形螺捐。因此AIDL方法要處理好線程同步颠悬。比如List,建議使用CopyOnWriteArrayList來(lái)進(jìn)行自動(dòng)的線程同步定血,類(lèi)似的還有ConcurrentHashMap赔癌。當(dāng)AIDL傳遞時(shí),Binder會(huì)按照List和Map的規(guī)范最形成ArrayList和Hashmap傳遞給客戶(hù)端澜沟。
  3. 觀察者模式:客戶(hù)端注冊(cè)一個(gè)listener到服務(wù)端灾票,用于觀察服務(wù)端的狀態(tài)。
    1. 需要加一個(gè)listener的aidl文件茫虽,并且要在原本的aidl文件中加registerlistener/unRegisterListener的接口
    2. 客戶(hù)端registerListener
    3. 服務(wù)端的listener保存要使用RemoteCallbackList
    4. 在需要的時(shí)機(jī)刊苍,服務(wù)端運(yùn)行
    final int N = mRemoteCallbackList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IxxxListener l = mRemoteCallbackList.getBroadcastItem(i);
            if (l != null) {
                try {
                    l.onXxxed();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        mRemoteCallbackList.finishBroadcast();
    

RemoteCallbackList是系統(tǒng)專(zhuān)門(mén)提供的用于刪除跨進(jìn)程Listener的接口。RemoteCallbackList是一個(gè)泛型濒析,支持管理任意的AIDL接口正什,因?yàn)樗械腁IDL接口都繼承自IInterface接口。

  1. 遠(yuǎn)程調(diào)用的方法是運(yùn)行在Binder線程池号杏,同時(shí)客戶(hù)端線會(huì)掛起埠忘,如果遠(yuǎn)程調(diào)用耗時(shí),則會(huì)導(dǎo)致客戶(hù)端線程阻塞,此時(shí)要避免UI線程進(jìn)遠(yuǎn)程調(diào)用莹妒,尤其是onServiceConnected/onServiceDisConnected名船。
  2. 服務(wù)端方法是運(yùn)行在Binder線程池中的,所以原本就可以執(zhí)行大量耗時(shí)操作旨怠,因此不需要開(kāi)新線程進(jìn)行異步任務(wù)渠驼。
  3. 當(dāng)服務(wù)端調(diào)用客戶(hù)端listener中的方法時(shí),調(diào)用的方法是運(yùn)行在客戶(hù)端的Binder線程池鉴腻,因此如果此調(diào)用耗時(shí)迷扇,會(huì)導(dǎo)致服務(wù)端線程阻塞無(wú)法響應(yīng)。
  4. Binder是可能意外死亡的爽哎,往往是因?yàn)榉?wù)端進(jìn)程意外終止了蜓席。重練遠(yuǎn)程服務(wù)的方法:
    1. 死亡代理:DeathRecipient監(jiān)聽(tīng),在binderDied中重連
    2. onServiceDisconnected中重連课锌。

2.4.5 使用ContentProvider

  1. ContentProvider主要以表格的形式來(lái)組織數(shù)據(jù)厨内,并且可以包含多個(gè)表;
  2. ContentProvider還支持文件數(shù)據(jù)渺贤,比如圖片雏胃、視頻等,系統(tǒng)提供的MediaStore就是文件類(lèi)型的ContentProvider志鞍;
  3. ContentProvider對(duì)底層的數(shù)據(jù)存儲(chǔ)方式?jīng)]有任何要求瞭亮,可以是SQLite、文件固棚,甚至是內(nèi)存中的一個(gè)對(duì)象都行统翩;
  4. 要觀察ContentProvider中的數(shù)據(jù)變化情況,可以通過(guò)ContentResolver的registerContentObserver方法來(lái)注冊(cè)觀察者此洲;
  5. 跨進(jìn)程訪問(wèn)ContentProvider的數(shù)據(jù)厂汗,只需要通過(guò)ContentResolver的query、update黍翎、insert、delete方法艳丛。
  6. sqliteDatabase內(nèi)部對(duì)數(shù)據(jù)庫(kù)的操作是有同步處理的匣掸。

2.4.6使用Socket

2.5 Binder連接池

將所有的AIDL放在同一個(gè)Service中去管理是一種比較好的方式。

2.5.1 Binder連接池工作機(jī)制:

  1. 每個(gè)業(yè)務(wù)模塊創(chuàng)建自己的AIDL接口并實(shí)現(xiàn)此接口氮双,這個(gè)時(shí)候不同業(yè)務(wù)模塊之間是不能有耦合的碰酝,所有實(shí)現(xiàn)細(xì)節(jié)我們要單獨(dú)開(kāi)來(lái),然后向服務(wù)端提供自己的唯一標(biāo)識(shí)和其對(duì)應(yīng)的Binder對(duì)象戴差;
  2. 對(duì)于服務(wù)端來(lái)說(shuō)送爸,只需要一個(gè)Service,服務(wù)端提供一個(gè)queryBinder接口,這個(gè)接口能夠根據(jù)業(yè)務(wù)模塊的特征來(lái)返回相應(yīng)的Binder對(duì)象給它們袭厂,不同的業(yè)務(wù)模塊拿到所需的Binder對(duì)象后就可以進(jìn)行遠(yuǎn)程方法調(diào)用了墨吓。

2.5.2 Binder連接池的主要作用

  1. 將每個(gè)業(yè)務(wù)模塊的Binder請(qǐng)求統(tǒng)一轉(zhuǎn)發(fā)到遠(yuǎn)程Service去執(zhí)行,從而避免了重復(fù)創(chuàng)建Service的過(guò)程纹磺。
  2. 建議在AIDL開(kāi)發(fā)工作中引入BinderPool機(jī)制
    作者的BinderPool源碼
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帖烘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子橄杨,更是在濱河造成了極大的恐慌秘症,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件式矫,死亡現(xiàn)場(chǎng)離奇詭異乡摹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)采转,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)聪廉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人氏义,你說(shuō)我怎么就攤上這事锄列。” “怎么了惯悠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵邻邮,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我克婶,道長(zhǎng)筒严,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任情萤,我火速辦了婚禮鸭蛙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘筋岛。我一直安慰自己娶视,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布睁宰。 她就那樣靜靜地躺著肪获,像睡著了一般。 火紅的嫁衣襯著肌膚如雪柒傻。 梳的紋絲不亂的頭發(fā)上孝赫,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音红符,去河邊找鬼青柄。 笑死伐债,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的致开。 我是一名探鬼主播峰锁,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼喇喉!你這毒婦竟也來(lái)了祖今?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拣技,失蹤者是張志新(化名)和其女友劉穎千诬,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體膏斤,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡徐绑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了莫辨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片傲茄。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖沮榜,靈堂內(nèi)的尸體忽然破棺而出盘榨,到底是詐尸還是另有隱情,我是刑警寧澤蟆融,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布草巡,位于F島的核電站,受9級(jí)特大地震影響型酥,放射性物質(zhì)發(fā)生泄漏山憨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一弥喉、第九天 我趴在偏房一處隱蔽的房頂上張望郁竟。 院中可真熱鬧,春花似錦由境、人聲如沸棚亩。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)讥蟆。三九已至,卻和暖如春嘹屯,著一層夾襖步出監(jiān)牢的瞬間攻询,已是汗流浹背从撼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工州弟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钧栖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓婆翔,卻偏偏與公主長(zhǎng)得像拯杠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子啃奴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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