Android-序列化/反序列化

什么是序列化/反序列化?

  • 簡單來說就是將對象轉(zhuǎn)換為可以傳輸?shù)亩M(jìn)制流(二進(jìn)制序列)的過程,這樣我們就可以通過序列化,轉(zhuǎn)化為可以在網(wǎng)絡(luò)傳輸或者保存到本地的流(序列),從而進(jìn)行傳輸數(shù)據(jù) 癣亚。
  • 那反序列化就是從二進(jìn)制流(序列)轉(zhuǎn)化為對象的過程.
  • 由于存在于內(nèi)存中的對象都是暫時(shí)的撩鹿,無法長期駐存棉钧,為了把對象的狀態(tài)保持下來,這時(shí)需要把對象寫入到磁盤或者其他介質(zhì)中,這個(gè)過程就叫做序列化娩践。
  • 反序列化恰恰是序列化的反向操作,也就是說烹骨,把已存在在磁盤或者其他介質(zhì)中的對象翻伺,反序列化(讀取)到內(nèi)存中沮焕,以便后續(xù)操作吨岭,而這個(gè)過程就叫做反序列化。
  • 反序列化:把字節(jié)序列恢復(fù)為原先的Java對象峦树。
  • 序列化:把Java對象轉(zhuǎn)換為字節(jié)序列辣辫。

什么時(shí)候使用序列化

  • 數(shù)據(jù)傳輸?shù)臅r(shí)候
  • 數(shù)據(jù)保存的時(shí)候

序列化是干啥用的?

  • 序列化的原本意圖是希望對一個(gè)Java對象作一下“變換”魁巩,變成字節(jié)序列急灭,這樣一來方便持久化存儲到磁盤,避免程序運(yùn)行結(jié)束后對象就從內(nèi)存里消失谷遂,另外變換成字節(jié)序列也更便于網(wǎng)絡(luò)運(yùn)輸和傳播葬馋,所以概念上很好理解:


怎么序列化

  • Android中實(shí)現(xiàn)序列化有兩個(gè)選擇:一是實(shí)現(xiàn)Serializable接口(是JavaSE本身就支持的),一是實(shí)現(xiàn)Parcelable接口(是Android特有功能肾扰,效率比實(shí)現(xiàn)Serializable接口高效畴嘶,可用于Intent數(shù)據(jù)傳遞,也可以用于進(jìn)程間通信(IPC))集晚。
  • 實(shí)現(xiàn)Serializable接口非常簡單窗悯,聲明一下就可以了,而實(shí)現(xiàn)Parcelable接口稍微復(fù)雜一些甩恼,但效率更高蟀瞧,在一般的時(shí)候推薦用這種方法提高性能沉颂。

對象如何序列化?

  • 舉個(gè)例子悦污,假如我們要對Student類對象序列化到一個(gè)名為student.txt的文本文件中铸屉,然后再通過文本文件反序列化成Student類對象:
1、Student類定義
public class Student implements Serializable {

    private String name;
    private Integer age;
    private Integer score;
    
    @Override
    public String toString() {
        return "Student:" + '\n' +
        "name = " + this.name + '\n' +
        "age = " + this.age + '\n' +
        "score = " + this.score + '\n'
        ;
    }
    // ... 其他省略 ... 這里省略了set()切端、get()方法 
}
2彻坛、序列化
public static void serialize(  ) throws IOException {

    Student student = new Student();
    student.setName("CodeSheep");
    student.setAge( 18 );
    student.setScore( 1000 );

    ObjectOutputStream objectOutputStream = 
        new ObjectOutputStream( new FileOutputStream( new File(context.getCacheDir()+"/student.txt") ) );
    objectOutputStream.writeObject( student );
    objectOutputStream.close();
    
    System.out.println("序列化成功!已經(jīng)生成student.txt文件");
    System.out.println("==============================================");
}
3踏枣、反序列化
public static void deserialize(  ) throws IOException, ClassNotFoundException {
    ObjectInputStream objectInputStream = 
        new ObjectInputStream( new FileInputStream( new File(context.getCacheDir()+"/student.txt") ) );
    Student student = (Student) objectInputStream.readObject();
    objectInputStream.close();
    
    System.out.println("反序列化結(jié)果為:");
    System.out.println( student );
}
4昌屉、運(yùn)行結(jié)果
序列化成功!已經(jīng)生成student.txt文件
==============================================
反序列化結(jié)果為:
Student:
name = CodeSheep
age = 18
score = 1000

Serializable接口

  • 上面在定義Student類時(shí)茵瀑,實(shí)現(xiàn)了一個(gè)Serializable接口间驮,然而當(dāng)我們點(diǎn)進(jìn)Serializable接口內(nèi)部查看,發(fā)現(xiàn)它竟然是一個(gè)空接口马昨,并沒有包含任何方法竞帽!
public interface Serializable {
}
試想,如果上面在定義Student類時(shí)忘了加implements Serializable時(shí)會發(fā)生什么呢鸿捧?
  • 實(shí)驗(yàn)結(jié)果是:此時(shí)的程序運(yùn)行會報(bào)錯(cuò)屹篓,并拋出NotSerializableException異常:


  • 我們按照錯(cuò)誤提示,由源碼一直跟到ObjectOutputStream的writeObject0()方法底層一看匙奴,才恍然大悟:


  • 如果一個(gè)對象既不是字符串堆巧、數(shù)組、枚舉泼菌,而且也沒有實(shí)現(xiàn)Serializable接口的話谍肤,在序列化時(shí)就會拋出NotSerializableException異常!

  • 原來Serializable接口也僅僅只是做一個(gè)標(biāo)記用T詈洹Rシ小刷钢!

  • 它告訴代碼只要是實(shí)現(xiàn)了Serializable接口的類都是可以被序列化的笋颤!然而真正的序列化動作不需要靠它完成。

serialVersionUID號有何用内地?
  • 相信你一定經(jīng)嘲槌危看到有些類中定義了如下代碼行,即定義了一個(gè)名為serialVersionUID的字段:
private static final long serialVersionUID = -4392658638228508589L;
  • 繼續(xù)來做一個(gè)簡單實(shí)驗(yàn)阱缓,還拿上面的Student類為例非凌,我們并沒有人為在里面顯式地聲明一個(gè)serialVersionUID字段。
  • 我們首先還是調(diào)用上面的serialize()方法荆针,將一個(gè)Student對象序列化到本地磁盤上的student.txt文件:
  • 接下來我們在Student類里面動點(diǎn)手腳敞嗡,比如在里面再增加一個(gè)名為studentID的字段颁糟,表示學(xué)生學(xué)號:新增一個(gè)字段 private Long studentId; // 表示學(xué)號
  • 這時(shí)候,我們拿剛才已經(jīng)序列化到本地的student.txt文件喉悴,還用如下代碼進(jìn)行反序列化棱貌,試圖還原出剛才那個(gè)Student對象:
  • 運(yùn)行發(fā)現(xiàn)報(bào)錯(cuò)了,并且拋出了InvalidClassException異常:


  • 這地方提示的信息非常明確了:序列化前后的serialVersionUID號碼不兼容箕肃!
從這地方最起碼可以得出兩個(gè)重要信息:
  • 1婚脱、serialVersionUID是序列化前后的唯一標(biāo)識符
  • 2、默認(rèn)如果沒有人為顯式定義過serialVersionUID勺像,那編譯器會為它自動聲明一個(gè)障贸!
  • serialVersionUID序列化ID,可以看成是序列化和反序列化過程中的“暗號”吟宦,在反序列化時(shí)篮洁,JVM會把字節(jié)流中的序列號ID和被序列化類中的序列號ID做比對,只有兩者一致殃姓,才能重新反序列化嘀粱,否則就會報(bào)異常來終止反序列化的過程。
  • 如果在定義一個(gè)可序列化的類時(shí)辰狡,沒有人為顯式地給它定義一個(gè)serialVersionUID的話锋叨,則Java運(yùn)行時(shí)環(huán)境會根據(jù)該類的各方面信息自動地為它生成一個(gè)默認(rèn)的serialVersionUID,一旦像上面一樣更改了類的結(jié)構(gòu)或者信息宛篇,則類的serialVersionUID也會跟著變化娃磺!
  • 所以,為了serialVersionUID的確定性叫倍,寫代碼時(shí)還是建議偷卧,凡是implements Serializable的類,都最好人為顯式地為它聲明一個(gè)serialVersionUID明確值吆倦!
  • Android Studio 自動添加serialVersionUID http://www.reibang.com/p/9252e1e4e82e

Parcelable

AndroidStudio中的快捷生成方式

  • 插件 android Parcelable code generator

Parcel的簡介 [?pɑ?rsl]

  • 在介紹之前我們需要先了解Parcel是什么?Parcel翻譯過來是打包的意思,其實(shí)就是包裝了我們需要傳輸?shù)臄?shù)據(jù),然后在Binder中傳輸,也就是用于跨進(jìn)程傳輸數(shù)據(jù)听诸。

Parcel模型

  • 簡單來說,Parcel提供了一套機(jī)制蚕泽,可以將序列化之后的數(shù)據(jù)寫入到一個(gè)共享內(nèi)存中晌梨,其他進(jìn)程通過Parcel可以從這塊共享內(nèi)存中讀出字節(jié)流,并反序列化成對象,下圖是這個(gè)過程的模型须妻。
  • Parcel可以包含原始數(shù)據(jù)類型(用各種對應(yīng)的方法寫入仔蝌,比如writeInt(),writeFloat()等),可以包含Parcelable對象荒吏,它還包含了一個(gè)活動的IBinder對象的引用敛惊,這個(gè)引用導(dǎo)致另一端接收到一個(gè)指向這個(gè)IBinder的代理IBinder。


Parcelable中的三大過程介紹(序列化,反序列化,描述)

/**
 * ================================================
 * 作    者:SharkZ
 * 郵    箱:229153959@qq.com
 * 創(chuàng)建日期:2020/8/26  22:51
 * 描    述
 * 修訂歷史:
 * ================================================
 */
public class MyParcelable implements Parcelable {

    private String paramsA;
    private int paramsB;
    private boolean paramsC;

    public MyParcelable() {

    }

    public MyParcelable(String paramsA, int paramsB, boolean paramsC) {
        this.paramsA = paramsA;
        this.paramsB = paramsB;
        this.paramsC = paramsC;
    }

    public String getParamsA() {
        return paramsA;
    }

    public void setParamsA(String paramsA) {
        this.paramsA = paramsA;
    }

    public int getParamsB() {
        return paramsB;
    }

    public void setParamsB(int paramsB) {
        this.paramsB = paramsB;
    }

    public boolean isParamsC() {
        return paramsC;
    }

    public void setParamsC(boolean paramsC) {
        this.paramsC = paramsC;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.paramsA);
        dest.writeInt(this.paramsB);
        dest.writeByte(this.paramsC ? (byte) 1 : (byte) 0);
    }

    protected MyParcelable(Parcel in) {
        this.paramsA = in.readString();
        this.paramsB = in.readInt();
        this.paramsC = in.readByte() != 0;
    }

    public static final Creator<MyParcelable> CREATOR = new Creator<MyParcelable>() {
        @Override
        public MyParcelable createFromParcel(Parcel source) {
            return new MyParcelable(source);
        }

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

  • command+n 選擇parcelable插件自動生成
  • 如果實(shí)現(xiàn)Parcelable接口的對象中包含對象或者集合,那么其中的對象也要實(shí)現(xiàn)Parcelable接口

兩種特殊情況

1绰更、凡是被static修飾的字段是不會被序列化的

對于第一點(diǎn)瞧挤,因?yàn)樾蛄谢4娴氖菍ο蟮臓顟B(tài)而非類的狀態(tài)锡宋,所以會忽略static靜態(tài)域也是理所應(yīng)當(dāng)?shù)摹?/p>

2、凡是被transient修飾符修飾的字段也是不會被序列化的
  • 對于第二點(diǎn)特恬,就需要了解一下transient修飾符的作用了员辩。

  • 如果在序列化某個(gè)類的對象時(shí),就是不希望某個(gè)字段被序列化(比如這個(gè)字段存放的是隱私值鸵鸥,如:密碼等)奠滑,那這時(shí)就可以用transient修飾符來修飾該字段。

  • 比如在之前定義的Student類中妒穴,加入一個(gè)密碼字段宋税,但是不希望序列化到txt文本,則可以:


  • 這樣在序列化Student類對象時(shí)讼油,password字段會設(shè)置為默認(rèn)值null杰赛,這一點(diǎn)可以從反序列化所得到的結(jié)果來看出:


Parcelable 與 Serializable 區(qū)別

(1)兩者的實(shí)現(xiàn)差異

  • Serializable的實(shí)現(xiàn),只需要實(shí)現(xiàn)Serializable接口即可矮台。這只是給對象打了一個(gè)標(biāo)記(UID)乏屯,系統(tǒng)會自動將其序列化。而Parcelabel的實(shí)現(xiàn)瘦赫,不僅需要實(shí)現(xiàn)Parcelabel接口辰晕,還需要在類中添加一個(gè)靜態(tài)成員變量CREATOR,這個(gè)變量需要實(shí)現(xiàn) Parcelable.Creator 接口确虱,并實(shí)現(xiàn)讀寫的抽象方法含友。

(2)兩者的設(shè)計(jì)初衷

  • Serializable的設(shè)計(jì)初衷是為了序列化對象到本地文件、數(shù)據(jù)庫校辩、網(wǎng)絡(luò)流窘问、RMI以便數(shù)據(jù)傳輸,當(dāng)然這種傳輸可以是程序內(nèi)的也可以是兩個(gè)程序間的宜咒。而Android的Parcelable的設(shè)計(jì)初衷是由于Serializable效率過低惠赫,消耗大,而android中數(shù)據(jù)傳遞主要是在內(nèi)存環(huán)境中(內(nèi)存屬于android中的稀有資源)故黑,因此Parcelable的出現(xiàn)為了滿足數(shù)據(jù)在內(nèi)存中低開銷而且高效地傳遞問題儿咱。

(3)兩者效率選擇

  • Serializable使用IO讀寫存儲在硬盤上。序列化過程使用了反射技術(shù)倍阐,并且期間產(chǎn)生臨時(shí)對象概疆,優(yōu)點(diǎn)代碼少逗威,在將對象序列化到存儲設(shè)置中或?qū)ο笮蛄谢笸ㄟ^網(wǎng)絡(luò)傳輸時(shí)建議選擇Serializable峰搪。
  • Parcelable是直接在內(nèi)存中讀寫,我們知道內(nèi)存的讀寫速度肯定優(yōu)于硬盤讀寫速度凯旭,所以Parcelable序列化方式性能上要優(yōu)于Serializable方式很多概耻。所以Android應(yīng)用程序在內(nèi)存間數(shù)據(jù)傳輸時(shí)推薦使用Parcelable使套,如activity間傳輸數(shù)據(jù)和AIDL數(shù)據(jù)傳遞。大多數(shù)情況下使用Serializable也是沒什么問題的鞠柄,但是針對Android應(yīng)用程序在內(nèi)存間數(shù)據(jù)傳輸還是建議大家使用Parcelable方式實(shí)現(xiàn)序列化侦高,畢竟性能好很多,其實(shí)也沒多麻煩厌杜。
  • Parcelable也不是不可以在網(wǎng)絡(luò)中傳輸奉呛,只不過實(shí)現(xiàn)和操作過程過于麻煩并且為了防止android版本不同而導(dǎo)致Parcelable可能不同的情況,因此在序列化到存儲設(shè)備或者網(wǎng)絡(luò)傳輸方面還是盡量選擇Serializable接口夯尽。

序列化的受控和加強(qiáng)

約束性加持

  • 從上面的過程可以看出,序列化和反序列化的過程其實(shí)是有漏洞的,因?yàn)閺男蛄谢椒葱蛄谢怯兄虚g過程的义矛,如果被別人拿到了中間字節(jié)流派诬,然后加以偽造或者篡改,那反序列化出來的對象就會有一定風(fēng)險(xiǎn)了圈纺。
  • 畢竟反序列化也相當(dāng)于一種 “隱式的”對象構(gòu)造 秦忿,因此我們希望在反序列化時(shí),進(jìn)行受控的對象反序列化動作蛾娶。

那怎么個(gè)受控法呢灯谣?

  • 答案就是: 自行編寫readObject()函數(shù),用于對象的反序列化構(gòu)造蛔琅,從而提供約束性酬屉。
  • 既然自行編寫readObject()函數(shù),那就可以做很多可控的事情:比如各種判斷工作揍愁。
  • 還以上面的Student類為例呐萨,一般來說學(xué)生的成績應(yīng)該在0 ~ 100之間,我們?yōu)榱朔乐箤W(xué)生的考試成績在反序列化時(shí)被別人篡改成一個(gè)奇葩值莽囤,我們可以自行編寫readObject()函數(shù)用于反序列化的控制:
private void readObject( ObjectInputStream objectInputStream ) throws IOException, ClassNotFoundException {

    // 調(diào)用默認(rèn)的反序列化函數(shù)
    objectInputStream.defaultReadObject();

    // 手工檢查反序列化后學(xué)生成績的有效性谬擦,若發(fā)現(xiàn)有問題,即終止操作朽缎!
    if( 0 > score || 100 < score ) {
        throw new IllegalArgumentException("學(xué)生分?jǐn)?shù)只能在0到100之間惨远!");
    }
}
  • 比如我故意將學(xué)生的分?jǐn)?shù)改為101,此時(shí)反序列化立馬終止并且報(bào)錯(cuò):


  • 對于上面的代碼话肖,有些小伙伴可能會好奇北秽,為什么自定義的private的readObject()方法可以被自動調(diào)用,這就需要你跟一下底層源碼來一探究竟了最筒,我?guī)湍愀搅薕bjectStreamClass類的最底層贺氓,看到這里我相信你一定恍然大悟:


  • 又是反射機(jī)制在起作用!是的床蜘,在Java里辙培,果然萬物皆可“反射”(滑稽)蔑水,即使是類中定義的private私有方法,也能被摳出來執(zhí)行了扬蕊,簡直引起舒適了搀别。

單例模式增強(qiáng)

  • 一個(gè)容易被忽略的問題是:可序列化的單例類有可能并不單例!
  • 比如這里我們先用java寫一個(gè)常見的「靜態(tài)內(nèi)部類」方式的單例模式實(shí)現(xiàn):
public class Singleton implements Serializable {

    private static final long serialVersionUID = -1576643344804979563L;

    private Singleton() {
    }

    private static class SingletonHolder {
        private static final Singleton singleton = new Singleton();
    }

    public static synchronized Singleton getSingleton() {
        return SingletonHolder.singleton;
    }
}

  • 然后寫一個(gè)驗(yàn)證主函數(shù):
public class Test2 {

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        ObjectOutputStream objectOutputStream =
                new ObjectOutputStream(
                    new FileOutputStream( new File("singleton.txt") )
                );
        // 將單例對象先序列化到文本文件singleton.txt中
        objectOutputStream.writeObject( Singleton.getSingleton() );
        objectOutputStream.close();

        ObjectInputStream objectInputStream =
                new ObjectInputStream(
                    new FileInputStream( new File("singleton.txt") )
                );
        // 將文本文件singleton.txt中的對象反序列化為singleton1
        Singleton singleton1 = (Singleton) objectInputStream.readObject();
        objectInputStream.close();

        Singleton singleton2 = Singleton.getSingleton();

        // 運(yùn)行結(jié)果竟打印 false 尾抑!
        System.out.println( singleton1 == singleton2 );
    }

}

  • 運(yùn)行后我們發(fā)現(xiàn):反序列化后的單例對象和原單例對象并不相等了歇父,這無疑沒有達(dá)到我們的目標(biāo)。
解決辦法是:在單例類中手寫readResolve()函數(shù)再愈,直接返回單例對象庶骄,來規(guī)避之:
private Object readResolve() {
    return SingletonHolder.singleton;
}
  • 這樣一來,當(dāng)反序列化從流中讀取對象時(shí)践磅,readResolve()會被調(diào)用单刁,用其中返回的對象替代反序列化新建的對象。

App 性能優(yōu)化

  • 界面?zhèn)髦档炔僮魇褂肞racelable來序列化,畢竟谷歌官方推薦的么府适。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末羔飞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子檐春,更是在濱河造成了極大的恐慌逻淌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疟暖,死亡現(xiàn)場離奇詭異卡儒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)俐巴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進(jìn)店門骨望,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人欣舵,你說我怎么就攤上這事擎鸠。” “怎么了缘圈?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵劣光,是天一觀的道長。 經(jīng)常有香客問我糟把,道長绢涡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任遣疯,我火速辦了婚禮雄可,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己滞项,他們只是感情好狭归,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布夭坪。 她就那樣靜靜地躺著文判,像睡著了一般。 火紅的嫁衣襯著肌膚如雪室梅。 梳的紋絲不亂的頭發(fā)上戏仓,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天,我揣著相機(jī)與錄音亡鼠,去河邊找鬼赏殃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛间涵,可吹牛的內(nèi)容都是我干的仁热。 我是一名探鬼主播,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼勾哩,長吁一口氣:“原來是場噩夢啊……” “哼抗蠢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起思劳,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎潜叛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體威兜,經(jīng)...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年椒舵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逮栅。...
    茶點(diǎn)故事閱讀 40,435評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖措伐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情侥加,我是刑警寧澤,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站昔穴,受9級特大地震影響镰官,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吗货,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一泳唠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宙搬,春花似錦笨腥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至闲孤,卻和暖如春谆级,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背讼积。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工肥照, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人币砂。 一個(gè)月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓建峭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親决摧。 傳聞我的和親對象是個(gè)殘疾皇子亿蒸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,442評論 2 359