輕松理解 Android Binder,只需要讀這一篇

在 Android 系統(tǒng)中阔拳,Binder 起著非常重要的作用崭孤,它是整個系統(tǒng) IPC 的基石类嗤。網(wǎng)上已經(jīng)有很多文章講述 Binder 的原理糊肠,有的講的比較淺顯,沒有觸及到關(guān)鍵遗锣,有的講的太過于深入底層货裹,難以理解,本文會比較全面精偿,以一個比較輕松的方式弧圆,從面到點,大處著眼笔咽,小處著手的形式去講述 Binder 在 Android 中是如何使用的搔预。理解 Binder 的基本原理,對學習 Android 也有很大的幫助叶组,很多問題也能夠得到解釋拯田,例如 ContentProvider 中的 CRUD 是否是線程安全的?又例如在使用 AIDL 的時候甩十,在 Service 中實現(xiàn)的接口是否是線程安全的船庇?

本文分為以下幾個部分去介紹

  • Android 整體架構(gòu)
  • Binder IPC 的架構(gòu)
  • 手動實現(xiàn) Binder IPC
  • 使用 AIDL 實現(xiàn) Binder IPC

如果覺得文章太長吭产,可以先只看「小結(jié)」部分,小結(jié)會把每個部分的重點總結(jié)出來鸭轮,有些部分可以跳過臣淤。

Android 整體架構(gòu)

不識廬山真面目,只緣身在此山中窃爷,所以我們先來大概看下 Android 這座大山的整體輪廓邑蒋。我們先從 Android 的整體架構(gòu)來看看 Binder 是處于什么地位,這張圖引自 Android 項目開源網(wǎng)站:https://source.android.com

從下往上依次為

  • 內(nèi)核層:Linux 內(nèi)核和各類硬件設備的驅(qū)動按厘,這里需要注意的是寺董,Binder IPC 驅(qū)動也是在這一層實現(xiàn),比較特殊
  • 硬件抽象層:封裝「內(nèi)核層」硬件驅(qū)動刻剥,提供可供「系統(tǒng)服務層」調(diào)用的統(tǒng)一硬件接口
  • 系統(tǒng)服務層:提供核心服務遮咖,并且提供可供「應用程序框架層」調(diào)用的接口
  • Binder IPC 層:作為「系統(tǒng)服務層」與「應用程序框架層」的 IPC 橋梁,互相傳遞接口調(diào)用的數(shù)據(jù)造虏,實現(xiàn)跨進層的通訊
  • 應用程序框架層:這一層可以理解為 Android SDK御吞,提供四大組件,View 繪制體系等平時開發(fā)中用到的基礎(chǔ)部件

在一個大的項目里面漓藕,分層是非常重要的陶珠,處于最底層的接口最具有「通用性」,接口粒度最細享钞,越往上層通用性降低揍诽。理論上來說上面的每一層都可以「開放」給開發(fā)者調(diào)用,例如開發(fā)者可以直接調(diào)用硬件抽象層的接口去操作硬件栗竖,或者直接調(diào)用系統(tǒng)服務層中的接口去直接操作系統(tǒng)服務暑脆,甚至是像 Windows 開發(fā)一樣,開發(fā)者可以在內(nèi)核層寫程序狐肢,運行在內(nèi)核中添吗。不過開放帶來的問題就是開發(fā)者權(quán)利太大,對于系統(tǒng)的穩(wěn)定性是沒有任何好處的份名,一個病毒制作者寫了一個內(nèi)核層的病毒碟联,系統(tǒng)也許永遠也起不來了。所以谷歌的做法是將開發(fā)者的權(quán)利收攏到了「應用程序框架層」僵腺,開發(fā)者只能調(diào)用這一層提供的接口鲤孵。

上面的層次中,內(nèi)核層與硬件抽象層均用 C/C++ 實現(xiàn)辰如,系統(tǒng)服務層是以 Java 實現(xiàn)普监,硬件抽象層編譯為 so 文件,以 JNI 的形式供系統(tǒng)服務層使用。系統(tǒng)服務層中的服務隨系統(tǒng)的啟動而啟動鹰椒,只要不關(guān)機锡移,就會一直運行。這些服務干什么事情呢漆际?其實很簡單淆珊,就是完成一個手機該有的核心功能如短信的收發(fā)管理、電話的接聽奸汇、掛斷以及應用程序的包管理施符、Activity 的管理等等。每一個服務均運行在一個獨立進程中擂找,因為是以 Java 實現(xiàn)戳吝,所以本質(zhì)上來說就是運行在一個獨立進程的 Dalvik 虛擬機中。問題就來了贯涎,開發(fā)者的 APP 運行在一個新的進程空間听哭,如何調(diào)用到系統(tǒng)服務層中的接口呢?答案是 IPC(Inter-Process Communication)塘雳,進程間通訊陆盘,縮寫與 RPC(Remote Procedure Call)是不一樣的,實現(xiàn)原理也是不一樣的败明。每一個系統(tǒng)服務在應用層序框架層都有一個 Manager 與之對應隘马,方便開發(fā)者調(diào)用其相關(guān)的功能,具體關(guān)系大致如下

IPC 的方式有很多種妻顶,例如 socket酸员、共享內(nèi)存、管道讳嘱、消息隊列等等幔嗦,我們就不去深究為何要使用 Binder 而不使用其他方式去做,到目前為止呢燥,這座大山的面目算是有個大概的輪廓了崭添。

小結(jié)

  • Android 從下而上分了內(nèi)核層寓娩、硬件抽象層叛氨、系統(tǒng)服務層、Binder IPC 層棘伴、應用程序框架層
  • Android 中「應用程序框架層」以 SDK 的形式開放給開發(fā)者使用寞埠,「系統(tǒng)服務層」中的核心服務隨系統(tǒng)啟動而運行,通過應用層序框架層提供的 Manager 實時為應用程序提供服務調(diào)用焊夸。系統(tǒng)服務層中每一個服務運行在自己獨立的進程空間中仁连,應用程序框架層中的 Manager 通過 Binder IPC 的方式調(diào)用系統(tǒng)服務層中的服務。

這一小節(jié)完了,看個美女放松下饭冬,繼續(xù)下一小節(jié)

Binder IPC 的架構(gòu)

下面我們就來看看 Binder IPC 的架構(gòu)是怎樣的

Binder IPC 屬于 C/S 結(jié)構(gòu)使鹅,Client 部分是用戶代碼,用戶代碼最終會調(diào)用 Binder Driver 的 transact 接口昌抠,Binder Driver 會調(diào)用 Server患朱,這里的 Server 與 service 不同,可以理解為 Service 中 onBind 返回的 Binder 對象炊苫,請注意區(qū)分下裁厅。

  • Client:用戶需要實現(xiàn)的代碼,如 AIDL 自動生成的接口類
  • Binder Driver:在內(nèi)核層實現(xiàn)的 Driver
  • Server:這個 Server 就是 Service 中 onBind 返回的 IBinder 對象

需要注意的是侨艾,上面綠色的色塊部分都是屬于用戶需要實現(xiàn)的部分执虹,而藍色部分是系統(tǒng)去實現(xiàn)了。也就是說 Binder Driver 這塊并不需要知道唠梨,Server 中會開啟一個線程池去處理客戶端調(diào)用袋励。為什么要用線程池而不是一個單線程隊列呢?試想一下当叭,如果用單線程隊列插龄,則會有任務積壓,多個客戶端同時調(diào)用一個服務的時候就會有來不及響應的情況發(fā)生科展,這是絕對不允許的均牢。

對于調(diào)用 Binder Driver 中的 transact 接口,客戶端可以手動調(diào)用才睹,也可以通過 AIDL 的方式生成的代理類來調(diào)用徘跪,服務端可以繼承 Binder 對象,也可以繼承 AIDL 生成的接口類的 Stub 對象琅攘。這些細節(jié)下面繼續(xù)接著說垮庐,這里暫時不展開。

切記坞琴,這里 Server 的實現(xiàn)是線程池的方式哨查,而不是單線程隊列的方式,區(qū)別在于剧辐,單線程隊列的話寒亥,Server 的代碼是線程安全的,線程池的話荧关,Server 的代碼則不是線程安全的溉奕,需要開發(fā)者自己做好多線程同步。

小結(jié)

  • Binder IPC 屬于 C/S 架構(gòu)忍啤,包括 Client加勤、Driver、Server 三個部分
  • Client 可以手動調(diào)用 Driver 的 transact 接口,也可以通過 AIDL 生成的 Proxy 調(diào)用
  • Server 中會啟動一個「線程池」來處理 Client 的調(diào)用請求鳄梅,處理完成后將結(jié)果返回給 Driver叠国,Driver 再返回給 Client

這里就回答了開篇提問的兩個問題:Service 中通過 AIDL 提供的接口并不是線程安全的,同理 ContentProvider 底層也是使用 Binder戴尸,同樣不是線程安全的煎饼,至于是否需要做多線程保護,看業(yè)務而定校赤,最好是做好多線程同步吆玖,以防萬一。

手動實現(xiàn) Binder IPC

通過上面的講解马篮,大家應該對整體的流程已經(jīng)有了清楚的認識沾乘,下面我們先來看看如何手動實現(xiàn) Binder IPC,即不使用 AIDL 的方式浑测。對應上面的 Client翅阵、Driver、Server迁央,在 Activity掷匠、Service 中分別是什么呢?

上文說的 Server 其實就是 Service 中 onBind 返回的 IBinder 對象岖圈。

Server

假如我們要做一個上報數(shù)據(jù)的功能讹语,運行在 Service 中,在后臺上報數(shù)據(jù)蜂科,接口定義如下

public interface IReporter {

    int report(String values, int type);
}

那如何拿到它的 Server 對象呢顽决?答案是通過 Service 的 onBind 方法返回,實現(xiàn)如下

BindService.java

public class BinderService extends Service {

    public static final int REPORT_CODE = 0;

    public interface IReporter {
        int report(String values, int type);
    }

    public final class Reporter extends Binder implements IReporter {
        @Override
        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            return super.onTransact(code, data, reply, flags);
        }

        @Override
        public int report(String values, int type) {
            return type;
        }
    }

    private Reporter mReporter;

    public BinderService() {
        mReporter = new Reporter();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mReporter;
    }
}

這里我暫時不寫 onTransact 的實現(xiàn)部分导匣,最主要的是繼承 Binder 對象才菠,這個是 Android SDK 提供的基類,它實現(xiàn)了 IBinder 接口贡定,并且封裝了底層的 Binder Driver赋访,看看它是如何初始化的

Binder.java

public Binder() {
    init();

    ...
}

...

private native final void init();

這里調(diào)用 native 的一個 init 方法,我們就不去深究了缓待,知道它是對底層的 Binder Driver 的封裝即可蚓耽。當客戶端發(fā)起請求的時候,Binder Driver 會調(diào)用它的 execTransact 方法命斧,并在內(nèi)部調(diào)用到 onTransact 方法田晚,用戶端代碼可以重載該方法去實現(xiàn)自己的業(yè)務邏輯代碼。我們的實現(xiàn)方式如下

BindService.java

protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    switch (code) {
        case REPORT_CODE:
            data.enforceInterface("reporter");
            String values = data.readString();
            Log.i("IReporter", "data is '" + values + "'");
            int type = data.readInt();
            int result = report(values, type);
            reply.writeInterfaceToken("reporter");
            reply.writeInt(result);
            return true;
    }
    return super.onTransact(code, data, reply, flags);
}

這里的主要過程就是:獲取到 data 中傳遞過來的參數(shù) values 和 type国葬,調(diào)用自己實現(xiàn)的 report 函數(shù),將返回值寫到 reply 中。

注意汇四,這里實現(xiàn)的兩個關(guān)鍵點就是

  • Reporter 類繼承 Binder 類接奈,重載 onTransact 函數(shù),實現(xiàn)自己的業(yè)務邏輯
  • 在 Service 的 onBind 中返回 Reporter 類的實例

這里看不到半點線程池的影子對吧通孽,其實是在 Binder 內(nèi)部的 native 方法中去實現(xiàn)了的序宦,記住你寫的代碼要保持線程安全就對了。

Driver

該部分已經(jīng)被 Binder 類給封裝了背苦,暴露給開發(fā)者的已經(jīng)是很簡單的使用方式了互捌,即繼承 Binder,實現(xiàn) onTransact 即可行剂。

Client

那 Client 是什么呢秕噪?也就是我們想使用 IReport 接口來做數(shù)據(jù)上報的地方,一般都在 Activity 里面厚宰,主要實現(xiàn)如下

MainActivity.java

private IBinder mReporterBind;

private class BindConnection implements ServiceConnection {

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mReporterBind = service;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mReporterBind = null;
    }
}

...

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...
    
    intent = new Intent(this, BinderService.class);
    bindService(intent, new BindConnection(), BIND_AUTO_CREATE);
}
    

這樣就拿到了后臺 Service 中的 onBind 返回的 IBinder 對象腌巾,也就是上面 Binder IPC 架構(gòu)中的 mRemote 對象。至于 bindService 中做了什么铲觉,有興趣的讀者可以再去研究澈蝙,這里知道它返回的就是 mRemote 對象即可。

MainActivity.java

Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("reporter");
data.writeString("this is a test string.");
data.writeInt(type);

mReporterBind.transact(BinderService.REPORT_CODE, data, reply, 0);
reply.enforceInterface("reporter");
int result = reply.readInt();

data.recycle();
reply.recycle();

通過 Parcel.obtain() 獲取發(fā)送包對象撵幽、應答包對象灯荧,寫入數(shù)據(jù),調(diào)用 IBinder 的 transact 接口盐杂,即 mRemote.transact() 的調(diào)用漏麦。

小結(jié)

  • 一切復雜的邏輯均已經(jīng)被封裝在實現(xiàn)了 IBinder 接口的 Binder 類中
  • Activity 中通過 bindService 拿到 Binder Driver 中的 mRemote 對象(IBinder 的實例),然后「組包」况褪,然后「調(diào)用 transact 接口」按序發(fā)送數(shù)據(jù)包
  • Service 中繼承 Binder 類撕贞,「重載 onTransact 函數(shù)」,實現(xiàn)參數(shù)的「解包」测垛,發(fā)送返回包等捏膨,在 onBind 中返回具體的實現(xiàn)類 如上文中的 Reporter

總的說來就是 Client 組包,調(diào)用 transact 發(fā)送數(shù)據(jù)食侮,Server 接到調(diào)用号涯,解包,返回锯七,下面使用 AIDL 的流程本質(zhì)也是一樣链快。

使用 AIDL 實現(xiàn) Binder IPC

上面的例子我們看到,定義了 IReporter 接口眉尸,但是其實 Client 中并沒有用到域蜗,因為數(shù)據(jù)的組包和解包其實是手動編碼的巨双,并不能直接調(diào)用接口,所以其實定義接口的意義等于 0霉祸,基于此筑累,Android 給了我們更好用的方式那就是 AIDL,定義如下

IReporter.aidl

package com.android.binder;

interface IReporter {

    int report(String values, int type);
}

Server

AidlService.java

public class AidlService extends Service {

    public static final class Reporter extends IReporter.Stub {

        @Override
        public int report(String values, int type) throws RemoteException {
            return type;
        }
    }

    private Reporter mReporter;

    public AidlService() {
        mReporter = new Reporter();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mReporter;
    }
}

這里與手動實現(xiàn)的方式不同的是丝蹭,一個繼承了 Binder慢宗,一個繼承了 AIDL 自動生成的 Stub 對象,它是什么呢奔穿?我們可以看下它的定義

IReporter.java

public interface IReporter extends android.os.IInterface
{

    public static abstract class Stub extends android.os.Binder implements com.android.binder.IReporter {
        ...
        
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
        {
            switch (code)
            {
                case INTERFACE_TRANSACTION:
                {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_report:
                {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.report(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
    }

...

}

其實和我們上文的寫法是一樣的镜沽,自動生成的 IReporter 類自動給我們處理了一些參數(shù)的組包和解包而已,在 case 語句中調(diào)用了 this.report 即可調(diào)用到自己的業(yè)務邏輯部分了贱田。

Driver

與上文一致缅茉,還是 Binder 的內(nèi)部封裝

Client

MainActivity.java

private IReporter mReporterAidl;

private class AidlConnection implements ServiceConnection {

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mReporterAidl = IReporter.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mReporterAidl = null;
    }
}

...

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...
    
    Intent intent = new Intent(this, AidlService.class);
    bindService(intent, new AidlConnection(), BIND_AUTO_CREATE);
}

這里與手動實現(xiàn)方式也有區(qū)別,即調(diào)用了 Stub 對象的 asInterface湘换,具體做了什么呢宾舅?

public static com.android.binder.IReporter asInterface(android.os.IBinder obj)
{
    if ((obj==null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin!=null)&&(iin instanceof com.android.binder.IReporter))) {
        return ((com.android.binder.IReporter)iin);
    }
    return new com.android.binder.IReporter.Stub.Proxy(obj);
}

先查找本地接口是否存在,判斷是否是本地調(diào)用彩倚,如果是則直接返回 IReporter 的對象筹我,否則返回 Stub.Proxy 對象,這個 Proxy 對象是做什么的呢帆离?

private static class Proxy implements com.android.binder.IReporter
{
    private android.os.IBinder mRemote;
    Proxy(android.os.IBinder remote)
    {
        mRemote = remote;
    }
    @Override public android.os.IBinder asBinder()
    {
        return mRemote;
    }
    public java.lang.String getInterfaceDescriptor()
    {
        return DESCRIPTOR;
    }
    @Override public int report(java.lang.String values, int type) throws android.os.RemoteException
    {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            _data.writeString(values);
            _data.writeInt(type);
            mRemote.transact(Stub.TRANSACTION_report, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readInt();
        }
        finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}

基本上已經(jīng)很明了了蔬蕊,就是一個代理對象,對調(diào)用接口參數(shù)做組包而已哥谷,然后調(diào)用了 mRemote.transact 接口岸夯,和上文手動實現(xiàn)的方式是一致的。

小結(jié)

  • AIDL 自動生成了 Stub 類
  • 在 Service 端繼承 Stub 類们妥,Stub 類中實現(xiàn)了 onTransact 方法實現(xiàn)了「解包」的功能
  • 在 Client 端使用 Stub 類的 Proxy 對象猜扮,該對象實現(xiàn)了「組包」并且調(diào)用 transact 的功能

有了 AIDL 之后,IReporter 接口就變得有意義了监婶,Client 調(diào)用接口旅赢,Server 端實現(xiàn)接口,一切「組包」惑惶、「解包」的邏輯封裝在了 Stub 類中煮盼,一切就是那么完美。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末带污,一起剝皮案震驚了整個濱河市僵控,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鱼冀,老刑警劉巖报破,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悠就,死亡現(xiàn)場離奇詭異,居然都是意外死亡泛烙,警方通過查閱死者的電腦和手機理卑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門翘紊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔽氨,“玉大人,你說我怎么就攤上這事帆疟○木浚” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵踪宠,是天一觀的道長自赔。 經(jīng)常有香客問我,道長柳琢,這世上最難降的妖魔是什么绍妨? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮柬脸,結(jié)果婚禮上他去,老公的妹妹穿的比我還像新娘。我一直安慰自己倒堕,他們只是感情好灾测,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著垦巴,像睡著了一般媳搪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骤宣,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天秦爆,我揣著相機與錄音,去河邊找鬼憔披。 笑死等限,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的活逆。 我是一名探鬼主播精刷,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蔗候!你這毒婦竟也來了怒允?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤锈遥,失蹤者是張志新(化名)和其女友劉穎纫事,沒想到半個月后勘畔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡丽惶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年炫七,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钾唬。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡万哪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抡秆,到底是詐尸還是另有隱情奕巍,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布儒士,位于F島的核電站的止,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏着撩。R本人自食惡果不足惜诅福,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拖叙。 院中可真熱鬧氓润,春花似錦、人聲如沸憋沿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辐啄。三九已至采章,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間壶辜,已是汗流浹背悯舟。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留砸民,地道東北人抵怎。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像岭参,于是被迫代替她去往敵國和親反惕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

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