Android 中進(jìn)程間通信(IPC)方式總結(jié)

本文參考:Android 之 IPC 進(jìn)程通信全解析

Android IPC簡(jiǎn)介

IPCInter-Process Communication的縮寫,含義就是進(jìn)程間通信或者跨進(jìn)程通信徘熔,是指兩個(gè)進(jìn)程之間進(jìn)行數(shù)據(jù)交換的過程审轮。
在明確其之前族展,需要先搞懂幾個(gè)概念:

線程:CPU可調(diào)度的最小單位届腐,是程序執(zhí)行流的最小單元鸵隧;線程是進(jìn)程中的一個(gè)實(shí)體粉臊,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源而克,只擁有一點(diǎn)兒在運(yùn)行中必不可少的資源靶壮,但它可與同屬一個(gè)進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。
進(jìn)程: 一個(gè)執(zhí)行單元员萍,在PC 和移動(dòng)設(shè)備上一般指一個(gè)程序或者應(yīng)用腾降,一個(gè)進(jìn)程可以包含多個(gè)線程。每一個(gè)進(jìn)程都有它自己的地址空間碎绎,一般情況下螃壤,包括文本區(qū)域(text region)抗果、數(shù)據(jù)區(qū)域(data region)和堆棧(stack region)。

在Android中奸晴,為每一個(gè)應(yīng)用程序都分配了一個(gè)獨(dú)立的虛擬機(jī)冤馏,或者說每個(gè)進(jìn)程都分配一個(gè)獨(dú)立的虛擬機(jī),不同虛擬機(jī)在內(nèi)存分配上都有不同的地址空間寄啼,這就導(dǎo)致在不同的虛擬機(jī)互相訪問數(shù)據(jù)需要借助其他手段逮光。
下面分別介紹一下在Android中實(shí)現(xiàn)IPC的方式:

使用Bundle的方式

我們知道在Android中三大組件(Activity,Service墩划,Receiver)都支持在Intent中傳遞Bundle數(shù)據(jù)涕刚,由于Bundle實(shí)現(xiàn)了Parceable接口,所以它可以很方便的在不同的進(jìn)程之間進(jìn)行傳輸乙帮。當(dāng)我們?cè)谝粋€(gè)進(jìn)程中啟動(dòng)另外一個(gè)進(jìn)程的Activity杜漠,Service,Receiver時(shí)察净,我們就可以在Bundle中附加我們所需要傳輸給遠(yuǎn)程的進(jìn)程的信息驾茴,并且通過Intent發(fā)送出去。這里注意:我們傳輸?shù)臄?shù)據(jù)必須基本數(shù)據(jù)類型或者能夠被序列化氢卡。

1.基本數(shù)據(jù)類型(int, long, char, boolean, double等)
2.String和CharSequence
3.List:只支持ArrayList锈至,并且里面的元素都能被AIDL支持
4.Map:只支持HashMap,里面的每個(gè)元素能被AIDL支持
5.Parcelable:Parcelable的實(shí)現(xiàn)類

下面看一個(gè)Demo例子:利用Bundle進(jìn)行進(jìn)程間通信

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
Bundle bundle = new Bundle();
bundle.putString("data", "測(cè)試數(shù)據(jù)");
intent.putExtras(bundle);
startActivity(intent);

注意:利用Bundle進(jìn)行進(jìn)程間通信是很容易的译秦,大家應(yīng)該注意到裹赴,這種方式進(jìn)行進(jìn)程間通信只能是單方向的簡(jiǎn)單數(shù)據(jù)傳輸,它使用時(shí)有一定的局限性诀浪。

使用文件共享的方式

文件共享: 將對(duì)象序列化之后保存到文件中,在通過反序列延都,將對(duì)象從文件中讀取雷猪。
在A進(jìn)程中寫入對(duì)象(序列化):

new Thread(new Runnable() {
     @Override
     public void run() {
          User user = new User(1, "user", false);
          File cachedFile = new File(CACHE_FILE_PATH);
          ObjectOutputStream objectOutputStream = null;
          try{
              objectOutputStream = new ObjectOutputStream
                             (new FileOutputStream(cachedFile));
              objectOutputStream.writeObject(user);
          }catch(IOException e){
              e.printStackTrace();
          }finally{
              objectOutputStream.close();
           }
      }
  }).start();

在B進(jìn)程中讀取文件(反序列化):

new Thread(new Runnable() {
     @Override
     public void run() {
          User user = null;
          File cachedFile = new File(CACHE_FILE_PATH);
          if(cachedFile.exists()){
              ObjectInputStream objectInputStream = null;
              try{
                   objectInputStream = new ObjectInputStream
                              (new FileInputStream(cachedFile));
                   user = objectInputStream.readObject(user);
              }catch(IOException e){
                   e.printStackTrace();
              }finally{
                   objectInputStream.close();
              }
           }

           try{
              objectOutputStream = new ObjectOutputStream
                   (new FileOutputStream(cachedFile));
              objectOutputStream.writeObject(user);
           }catch(IOException e){
              e.printStackTrace();
           }finally{
              objectOutputStream.close();
           }
      }
}).start();

這樣,分屬不同的進(jìn)程成功的獲取到了共享的數(shù)據(jù)晰房。
通過共享文件這種方式來共享數(shù)據(jù)對(duì)文件的格式是沒有具體的要求的求摇。比如可以是文件,也可以是Xml殊者、JSON 等与境。只要讀寫雙方約定一定的格式即可。
注意:同文件共享方式也存在著很大的局限性猖吴。即并發(fā)讀/ 寫的問題摔刁。讀/寫會(huì)造成數(shù)據(jù)不是最新。讀寫很明顯會(huì)出現(xiàn)錯(cuò)誤海蔽。文件共享適合在對(duì)數(shù)據(jù)同步要求不高的進(jìn)程之間進(jìn)行通信共屈。并且要妥善處理并發(fā)讀寫的問題绑谣。

使用Messenger的方式

Messenger 可以翻譯為信使,通過該對(duì)象拗引,可以在不同的進(jìn)程中傳遞Message對(duì)象借宵。注意,兩個(gè)單詞不同矾削。
下面就通過服務(wù)端(Service)和客戶端(Activity)的方式進(jìn)行演示壤玫。
客戶端向服務(wù)端發(fā)送消息,可分為以下幾步哼凯。
服務(wù)端
1.創(chuàng)建Service
2.構(gòu)造Handler對(duì)象欲间,實(shí)現(xiàn)handlerMessage方法。
3.通過Handler對(duì)象構(gòu)造Messenger信使對(duì)象挡逼。
4.通過Service的onBind()返回信使中的Binder對(duì)象括改。
客戶端
1.創(chuàng)建Actvity
2.綁定服務(wù)
3.創(chuàng)建ServiceConnection,監(jiān)聽綁定服務(wù)的回調(diào)。
4.通過onServiceConnected()方法的參數(shù)家坎,構(gòu)造客戶端Messenger對(duì)象
5.通過Messenger向服務(wù)端發(fā)送消息嘱能。
實(shí)現(xiàn)服務(wù)端

public class MessengerService extends Service{  
  private Handler MessengerHandler = new Handler(){  
      @Override  
      public void handleMessage(Message msg) {  
         //消息處理.......    
         Log.i("messenger","接收到客戶端的消息--"+msgClient);        
  };  
  //創(chuàng)建服務(wù)端Messenger  
  private final Messenger mMessenger = new Messenger(MessengerHandler);  
  @Override  
  public IBinder onBind(Intent intent) {   
      //向客戶端返回Ibinder對(duì)象,客戶端利用該對(duì)象訪問服務(wù)端  
      return mMessenger.getBinder();  
  }  
  @Override  
  public void onCreate() {  
      super.onCreate();  
  }  
}

注意:MessengerService需要在AndroidManifest.xml中注冊(cè)虱疏。
實(shí)現(xiàn)客戶端

public class MessengerActivity extends Activity{  
  private ServiceConnection conn = new ServiceConnection(){  
      @Override  
      public void onServiceConnected(ComponentName name, IBinder service) {  
          //根據(jù)得到的IBinder對(duì)象創(chuàng)建Messenger  
          mService = new Messenger(service);  
          //通過得到的mService 可以進(jìn)行通信
      }  
  };  

  @Override  
  protected void onCreate(Bundle savedInstanceState) {  
      super.onCreate(savedInstanceState);  
      setContentView(R.layout.activity_messenger);  
      init();  
  }  
  private void init() {  
     intent = new Intent(MessengerActivity.this, MessengerService.class);  
     bindService(intent, conn, Context.BIND_AUTO_CREATE); 
  }  

  /**
   *  布局文件中添加了一個(gè)按鈕惹骂,點(diǎn)擊該按鈕的處理方法
   * @param view
   */
   public void send(View view) {
        try {
            // 向服務(wù)端發(fā)送消息
            Message message = Message.obtain();
            Bundle data = new Bundle();
            data.putString("msg", "lalala");
            message.setData(data);
            // 發(fā)送消息
            mService.send(message);
            Log.i("messenger","向服務(wù)端發(fā)送了消息");
        } catch (Exception e) {
            e.printStackTrace();
        }
   }

  @Override  
  protected void onDestroy(){  
      unbindService(conn);  
      super.onDestroy();  
  }  
}

其中有一點(diǎn)需要注意:
我們是通過Message作為媒介去攜帶數(shù)據(jù)的。但是做瞪,Message的obj 并沒有實(shí)現(xiàn)序列化(實(shí)現(xiàn)Serializable或Parcelable),也就是其不能保存數(shù)據(jù)对粪。必須使用message.setData()方法去傳入一個(gè)Bundle對(duì)象,Bundle中保存需要傳入的數(shù)據(jù)装蓬。

使用AIDL的方式

AIDL是一種IDL語言著拭,用于生成可以在Android設(shè)備上兩個(gè)進(jìn)程之間進(jìn)行進(jìn)程間通信(IPC)的代碼儡遮。如果在一個(gè)進(jìn)程中(例如Activity)要調(diào)用另一個(gè)進(jìn)程中(例如Service)對(duì)象的操作鄙币,就可以使用AIDL生成可序列化的參數(shù)蹂随。
AIDL是IPC的一個(gè)輕量級(jí)實(shí)現(xiàn),用了對(duì)于Java開發(fā)者來說很熟悉的語法绩衷。Android也提供了一個(gè)工具唇聘,可以自動(dòng)創(chuàng)建Stub(類架構(gòu)迟郎,類骨架)。當(dāng)我們需要在應(yīng)用間通信時(shí)表制,我們需要按以下幾步走:
1.定義一個(gè)AIDL接口么介。
2.為遠(yuǎn)程服務(wù)(Service)實(shí)現(xiàn)對(duì)應(yīng)Stub壤短。
3.將服務(wù)“暴露”給客戶程序使用久脯。
下面簡(jiǎn)單介紹一下使用AIDL的使用方法:
因?yàn)樾枰?wù)端和客戶端共用aidl文件镰吆,所以最好單獨(dú)建一個(gè)包万皿,適合拷貝到客戶端牢硅。
服務(wù)端:
添加如下包名:com.example.ipc.aidl
創(chuàng)建BookAidl.java,該對(duì)象需要作為傳輸减余。所以需要實(shí)現(xiàn)Parcelable。

public class BookAidl implements Parcelable {

    public int bookId;

    public String bookName;

    public BookAidl() {
        super();

    }

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

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    public static final Parcelable.Creator<BookAidl> CREATOR = new Creator<BookAidl>() {

        @Override
        public BookAidl[] newArray(int size) {
            return new BookAidl[size];
        }

        @Override
        public BookAidl createFromParcel(Parcel source) {

            BookAidl book = new BookAidl();
            book.bookId = source.readInt();
            book.bookName = source.readString();
            return book;
        }
    };

    @Override
    public String toString() {
        return "BookAidl [bookId=" + bookId + ", bookName=" + bookName + "]";
    }
}

創(chuàng)建BookAidl.aidl文件蛆挫,并手動(dòng)添加悴侵。

package com.example.ipc.aidl;
Parcelable BookAidl;

創(chuàng)建IBookManager.aidl文件,接口文件做粤,面向客戶端調(diào)用:

package com.example.ipc.aidl;
import com.example.ipc.aidl.BookAidl;

interface IBookManager{
    List<BookAidl> getBookList();
    void addBook(in BookAidl book);
}

寫完之后clean一下工程怕品,之后會(huì)在gen目錄下生成對(duì)應(yīng)的java文件肉康。
繼續(xù)編寫服務(wù)端吼和,創(chuàng)建Service類炫乓。

public class BookService extends Service {

    /**
     * 支持線程同步献丑,因?yàn)槠浯嬖诙鄠€(gè)客戶端同時(shí)連接的情況
     */
    private CopyOnWriteArrayList<BookAidl> list = new CopyOnWriteArrayList<>();


    /**
     * 構(gòu)造 aidl中聲明的接口的Stub對(duì)象阳距,并實(shí)現(xiàn)所聲明的方法
     */
    private Binder mBinder = new IBookManager.Stub() {

        @Override
        public List<BookAidl> getBookList() throws RemoteException {
            return list;
        }

        @Override
        public void addBook(BookAidl book) throws RemoteException {
            list.add(book);
            Log.i("aidl", "服務(wù)端添加了一本書"+book.toString());
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        //加點(diǎn)書
        list.add(new BookAidl(1, "java"));
        list.add(new BookAidl(2, "android"));

    }

    @Override
    public IBinder onBind(Intent intent) {
        // 返回給客戶端的Binder對(duì)象
        return mBinder;
    }
}

在Service中筐摘,主要干了兩件事情:
1.實(shí)現(xiàn)aidl文件中的接口的Stub對(duì)象。并實(shí)現(xiàn)方法圃酵。
2.將Binder對(duì)象通過onBinder返回給客戶端郭赐。
為了省事捌锭,在這里不在另起一個(gè)工程了观谦,直接將Service在另一個(gè)進(jìn)程中運(yùn)行豁状。

<service
        android:name="com.example.ipc.BookService"
        android:process=":remote" />

客戶端
因?yàn)樵谕粋€(gè)工程中,不需要拷貝aidl包中的文件夭禽。如果不在同一個(gè)工程讹躯,需要拷貝蜀撑。

public class BookActivity extends AppCompatActivity{

    /**
     * 接口對(duì)象
     */
    private IBookManager mService;

    /**
     * 綁定服務(wù)的回調(diào)
     */
    private ServiceConnection conn = new ServiceConnection(){

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            // 獲取到書籍管理的對(duì)象
            mService = IBookManager.Stub.asInterface(service);

            Log.i("aidl", "連接到服務(wù)端酷麦,獲取IBookManager的對(duì)象");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }

    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_book);

        // 啟動(dòng)服務(wù)
        Intent intent = new Intent(this,BookService.class);
        bindService(intent, conn, BIND_AUTO_CREATE);

    }
    /**
     * 獲取服務(wù)端書籍列表
     * @param view
     */
    public void getBookList(View view){

        try {
            Log.i("aidl","客戶端查詢書籍"+mService.getBookList().toString());
        } catch (RemoteException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    /**
     * 添加書籍
     */
    public void add(View view){

        try {
            // 調(diào)用服務(wù)端添加書籍
            mService.addBook(new BookAidl(3,"iOS"));
        } catch (RemoteException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

客戶端的代碼和之前的Messenger很類似:
1.綁定服務(wù),監(jiān)聽回調(diào)糊肤。
2.將回調(diào)中的IBinder service通過IBookManager.Stub.asInterface()轉(zhuǎn)化為借口對(duì)象馆揉。
3.調(diào)用借口對(duì)象的方法升酣。

使用ContentProvider的方式

ContentProvider是Android中的四大組件之一噩茄,為了在應(yīng)用程序之間進(jìn)行數(shù)據(jù)交換绩聘,Android提供了ContentProvider凿菩,ContentProvider是不同應(yīng)用之間進(jìn)行數(shù)據(jù)交換的API帜讲,一旦某個(gè)應(yīng)用程序通過ContentProvider暴露了自己的數(shù)據(jù)操作的接口舒帮,那么不管該應(yīng)用程序是否啟動(dòng)玩郊,其他的應(yīng)用程序都可以通過接口來操作接口內(nèi)的數(shù)據(jù)预茄,包括數(shù)據(jù)的增侦厚、刪诗宣、改想诅、查等操作篮灼。
開發(fā)一個(gè)ContentProvider的步驟很簡(jiǎn)單:
1.定義自己的ContentProvider類徘禁,該類集成ContentProvider基類娘荡;
2.在AndroidMainfest.xml中注冊(cè)這個(gè)ContentProvider它改,類似于Activity注冊(cè)央拖,注冊(cè)時(shí)要給ContentProvider綁定一個(gè)域名;
3.當(dāng)我們注冊(cè)好這個(gè)ContentProvider后鹉戚,其他應(yīng)用就可以訪問ContentProvider暴露出來的數(shù)據(jù)了鲜戒。
ContentProvider只是暴露出來可供其他應(yīng)用操作的數(shù)據(jù),其他應(yīng)用則需要通過ContentProvider來操作
使用ContentResolver操作數(shù)據(jù)的步驟也很簡(jiǎn)單:
1.調(diào)用Activity的getContentResolver()獲取ContentResolver對(duì)象抹凳;
2.根據(jù)調(diào)用的ContentResolver的insert()遏餐、delete()、update()和query()方法操作數(shù)據(jù)庫即可赢底。

使用Socket的方式

Socaket也是實(shí)現(xiàn)進(jìn)程間通信的一種方式失都,Socaket也稱為“套接字”柏蘑,網(wǎng)絡(luò)通信中的概念,通過Socket我們可以很方便的進(jìn)行網(wǎng)絡(luò)通信粹庞,都可以實(shí)現(xiàn)網(wǎng)絡(luò)通信錄,那么實(shí)現(xiàn)跨進(jìn)程通信不是也是相同的嘛,但是Socaket主要還是應(yīng)用在網(wǎng)絡(luò)通信中。

Android 進(jìn)程間通信不同方式的比較

  • Bundle:四大組件間的進(jìn)程間通信方式,簡(jiǎn)單易用,但傳輸?shù)臄?shù)據(jù)類型受限典勇。
  • 文件共享: 不適合高并發(fā)場(chǎng)景伤溉,并且無法做到進(jìn)程間的及時(shí)通信宫静。
  • Messenger: 數(shù)據(jù)通過Message傳輸伏伯,只能傳輸Bundle支持的類型
  • ContentProvider:android 系統(tǒng)提供的。簡(jiǎn)單易用。但使用受限迂猴,只能根據(jù)特定規(guī)則訪問數(shù)據(jù)携兵。
  • AIDL:功能強(qiáng)大眠副,支持實(shí)時(shí)通信,但使用稍微復(fù)雜。
  • Socket:網(wǎng)絡(luò)數(shù)據(jù)交換的常用方式。不推薦使用。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市涨颜,隨后出現(xiàn)的幾起案子弹灭,更是在濱河造成了極大的恐慌,老刑警劉巖驾诈,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異戏自,居然都是意外死亡念脯,警方通過查閱死者的電腦和手機(jī)假勿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來删窒,“玉大人缤沦,你說我怎么就攤上這事包蓝」枨疲” “怎么了颂暇?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵祝高,是天一觀的道長(zhǎng)工闺。 經(jīng)常有香客問我乍赫,道長(zhǎng),這世上最難降的妖魔是什么陆蟆? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任雷厂,我火速辦了婚禮,結(jié)果婚禮上叠殷,老公的妹妹穿的比我還像新娘改鲫。我一直安慰自己,他們只是感情好林束,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布像棘。 她就那樣靜靜地躺著,像睡著了一般壶冒。 火紅的嫁衣襯著肌膚如雪缕题。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天胖腾,我揣著相機(jī)與錄音烟零,去河邊找鬼。 笑死咸作,一個(gè)胖子當(dāng)著我的面吹牛锨阿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播记罚,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼群井,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了毫胜?” 一聲冷哼從身側(cè)響起书斜,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎酵使,沒想到半個(gè)月后荐吉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡口渔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年样屠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡痪欲,死狀恐怖悦穿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情业踢,我是刑警寧澤栗柒,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站知举,受9級(jí)特大地震影響瞬沦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜雇锡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一逛钻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锰提,春花似錦曙痘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赛不,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間罢洲,已是汗流浹背踢故。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惹苗,地道東北人殿较。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像桩蓉,于是被迫代替她去往敵國和親淋纲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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