LiveDataBus替代RxBus凶朗、EventBus(轉)

對于Android系統(tǒng)來說,消息傳遞是最基本的組件显拳,每一個App內的不同頁面,不同組件都在進行消息傳遞搓萧。消息傳遞既可以用于Android四大組件之間的通信杂数,也可用于異步線程和主線程之間的通信。對于Android開發(fā)者來說瘸洛,經常使用的消息傳遞方式有很多種揍移,從最早使用的Handler、BroadcastReceiver反肋、接口回調那伐,到近幾年流行的通信總線類框架EventBus、RxBus石蔗。Android消息傳遞框架罕邀,總在不斷的演進之中

從EventBus說起

EventBus是一個Android事件發(fā)布/訂閱框架,通過解耦發(fā)布者和訂閱者簡化Android事件傳遞养距。EventBus可以代替Android傳統(tǒng)的Intent诉探、Handler、Broadcast或接口回調棍厌,在Fragment肾胯、Activity、Service線程之間傳遞數據耘纱,執(zhí)行方法敬肚。

EventBus最大的特點就是簡潔、解耦束析。在沒有EventBus之前我們通常用廣播來實現監(jiān)聽艳馒,或者自定義接口函數回調,有的場景我們也可以直接用Intent攜帶簡單數據畸陡,或者在線程之間通過Handler處理消息傳遞鹰溜。但無論是廣播還是Handler機制遠遠不能滿足我們高效的開發(fā)虽填。EventBus簡化了應用程序內各組件間、組件與后臺線程間的通信曹动。EventBus一經推出斋日,便受到廣大開發(fā)者的推崇。

現在看來墓陈,EventBus給Android開發(fā)者世界帶來了一種新的框架和思想恶守,就是消息的發(fā)布和訂閱。這種思想在其后很多框架中都得到了應用贡必。

圖片摘自EventBus GitHub主頁

發(fā)布/訂閱模式

訂閱發(fā)布模式定義了一種“一對多”的依賴關系兔港,讓多個訂閱者對象同時監(jiān)聽某一個主題對象。這個主題對象在自身狀態(tài)變化時仔拟,會通知所有訂閱者對象衫樊,使它們能夠自動更新自己的狀態(tài)。

RxBus的出現

RxBus不是一個庫利花,而是一個文件科侈,實現只有短短30行代碼。RxBus本身不需要過多分析炒事,它的強大完全來自于它基于的RxJava技術臀栈。響應式編程(Reactive Programming)技術這幾年特別火,RxJava是它在Java上的實作挠乳。RxJava天生就是發(fā)布/訂閱模式权薯,而且很容易處理線程切換。所以睡扬,RxBus憑借區(qū)區(qū)30行代碼盟蚣,就敢挑戰(zhàn)EventBus“江湖老大”的地位。

RxBus原理

在RxJava中有個Subject類威蕉,它繼承Observable類刁俭,同時實現了Observer接口,因此Subject可以同時擔當訂閱者和被訂閱者的角色韧涨,我們使用Subject的子類PublishSubject來創(chuàng)建一個Subject對象(PublishSubject只有被訂閱后才會把接收到的事件立刻發(fā)送給訂閱者)牍戚,在需要接收事件的地方,訂閱該Subject對象虑粥,之后如果Subject對象接收到事件如孝,則會發(fā)射給該訂閱者,此時Subject對象充當被訂閱者的角色娩贷。

完成了訂閱第晰,在需要發(fā)送事件的地方將事件發(fā)送給之前被訂閱的Subject對象,則此時Subject對象作為訂閱者接收事件,然后會立刻將事件轉發(fā)給訂閱該Subject對象的訂閱者茁瘦,以便訂閱者處理相應事件品抽,到這里就完成了事件的發(fā)送與處理。

最后就是取消訂閱的操作了甜熔,RxJava中圆恤,訂閱操作會返回一個Subscription對象,以便在合適的時機取消訂閱腔稀,防止內存泄漏盆昙,如果一個類產生多個Subscription對象,我們可以用一個CompositeSubscription存儲起來焊虏,以進行批量的取消訂閱淡喜。

RxBus有很多實現,如:

AndroidKnife/RxBus(https://github.com/AndroidKnife/RxBus

Blankj/RxBus(https://github.com/Blankj/RxBus

其實正如前面所說的诵闭,RxBus的原理是如此簡單炼团,我們自己都可以寫出一個RxBus的實現:

基于RxJava1的RxBus實現:

publicfinalclassRxBus{

privatefinalSubject?bus;

privateRxBus(){

bus?=newSerializedSubject<>(PublishSubject.create());

}

privatestaticclassSingletonHolder{

privatestaticfinalRxBus?defaultRxBus?=newRxBus();

}

publicstaticRxBusgetInstance(){

returnSingletonHolder.defaultRxBus;

}

/*

*?發(fā)送

*/

publicvoidpost(Object?o){

bus.onNext(o);

}

/*

*?是否有Observable訂閱

*/

publicbooleanhasObservable(){

returnbus.hasObservers();

}

/*

*?轉換為特定類型的Obserbale

*/

publicObservabletoObservable(Class?type){

returnbus.ofType(type);

}

}

基于RxJava2的RxBus實現:

publicfinalclassRxBus2{

privatefinalSubject?bus;

privateRxBus2(){

//?toSerialized?method?made?bus?thread?safe

bus?=?PublishSubject.create().toSerialized();

}

publicstaticRxBus2getInstance(){

returnHolder.BUS;

}

privatestaticclassHolder{

privatestaticfinalRxBus2?BUS?=newRxBus2();

}

publicvoidpost(Object?obj){

bus.onNext(obj);

}

publicObservabletoObservable(Class?tClass){

returnbus.ofType(tClass);

}

publicObservabletoObservable(){

returnbus;

}

publicbooleanhasObservers(){

returnbus.hasObservers();

}

}

引入LiveDataBus的想法

從LiveData談起

LiveData是Android Architecture Components提出的框架。LiveData是一個可以被觀察的數據持有類涂圆,它可以感知并遵循Activity们镜、Fragment或Service等組件的生命周期。正是由于LiveData對組件生命周期可感知特點润歉,因此可以做到僅在組件處于生命周期的激活狀態(tài)時才更新UI數據。

LiveData需要一個觀察者對象颈抚,一般是Observer類的具體實現踩衩。當觀察者的生命周期處于STARTED或RESUMED狀態(tài)時,LiveData會通知觀察者數據變化贩汉;在觀察者處于其他狀態(tài)時驱富,即使LiveData的數據變化了,也不會通知匹舞。

LiveData的優(yōu)點

UI和實時數據保持一致褐鸥,因為LiveData采用的是觀察者模式,這樣一來就可以在數據發(fā)生改變時獲得通知赐稽,更新UI叫榕。

避免內存泄漏,觀察者被綁定到組件的生命周期上姊舵,當被綁定的組件銷毀(destroy)時晰绎,觀察者會立刻自動清理自身的數據。

不會再產生由于Activity處于stop狀態(tài)而引起的崩潰括丁,例如:當Activity處于后臺狀態(tài)時荞下,是不會收到LiveData的任何事件的。

不需要再解決生命周期帶來的問題,LiveData可以感知被綁定的組件的生命周期尖昏,只有在活躍狀態(tài)才會通知數據變化仰税。

實時數據刷新,當組件處于活躍狀態(tài)或者從不活躍狀態(tài)到活躍狀態(tài)時總是能收到最新的數據抽诉。

解決Configuration Change問題陨簇,在屏幕發(fā)生旋轉或者被回收再次啟動,立刻就能收到最新的數據掸鹅。

談一談Android Architecture Components

Android Architecture Components的核心是Lifecycle塞帐、LiveData、ViewModel 以及 Room巍沙,通過它可以非常優(yōu)雅的讓數據與界面進行交互葵姥,并做一些持久化的操作,高度解耦句携,自動管理生命周期榔幸,而且不用擔心內存泄漏的問題。

Room?

一個強大的SQLite對象映射庫矮嫉。

ViewModel

一類對象削咆,它用于為UI組件提供數據,在設備配置發(fā)生變更時依舊可以存活蠢笋。

LiveData?一個可感知生命周期拨齐、可被觀察的數據容器,它可以存儲數據昨寞,還會在數據發(fā)生改變時進行提醒瞻惋。

Lifecycle

包含LifeCycleOwer和LifecycleObserver,分別是生命周期所有者和生命周期感知者援岩。

Android Architecture Components的特點

數據驅動型編程

變化的永遠是數據歼狼,界面無需更改。

感知生命周期享怀,防止內存泄漏

高度解耦

數據羽峰,界面高度分離。

數據持久化

數據添瓷、ViewModel不與 UI的生命周期掛鉤梅屉,不會因為界面的重建而銷毀。

重點:為什么使用LiveData構建數據通信總線LiveDataBus

使用LiveData的理由

LiveData具有的這種可觀察性和生命周期感知的能力仰坦,使其非常適合作為Android通信總線的基礎構件履植。

使用者不用顯示調用反注冊方法。

由于LiveData具有生命周期感知能力悄晃,所以LiveDataBus只需要調用注冊回調方法玫霎,而不需要顯示的調用反注冊方法凿滤。這樣帶來的好處不僅可以編寫更少的代碼,而且可以完全杜絕其他通信總線類框架(

如EventBus庶近、RxBus)忘記調用反注冊所帶來的內存泄漏的風險翁脆。

為什么要用LiveDataBus替代EventBus和RxBus

LiveDataBus的實現極其簡單,相對EventBus復雜的實現鼻种,LiveDataBus只需要一個類就可以實現反番。

LiveDataBus可以減小APK包的大小,由于LiveDataBus只依賴Android官方Android Architecture Components組件的LiveData叉钥,沒有其他依賴罢缸,本身實現只有一個類。作為比較投队,EventBus JAR包大小為57kb枫疆,RxBus依賴RxJava和RxAndroid,其中RxJava2包大小2.2MB敷鸦,RxJava1包大小1.1MB息楔,RxAndroid包大小9kb。使用LiveDataBus可以大大減小APK包的大小扒披。

LiveDataBus依賴方支持更好值依,LiveDataBus只依賴Android官方Android Architecture Components組件的LiveData,相比RxBus依賴的RxJava和RxAndroid碟案,依賴方支持更好愿险。

LiveDataBus具有生命周期感知,LiveDataBus具有生命周期感知价说,在Android系統(tǒng)中使用調用者不需要調用反注冊拯啦,相比EventBus和RxBus使用更為方便,并且沒有內存泄漏風險熔任。

LiveDataBus的設計和架構

LiveDataBus的組成

消息

消息可以是任何的Object,可以定義不同類型的消息唁情,如Boolean疑苔、String。也可以定義自定義類型的消息甸鸟。

消息通道

LiveData扮演了消息通道的角色惦费,不同的消息通道用不同的名字區(qū)分,名字是String類型的抢韭,可以通過名字獲取到一個LiveData消息通道薪贫。

消息總線

消息總線通過單例實現,不同的消息通道存放在一個HashMap中刻恭。

訂閱

訂閱者通過getChannel獲取消息通道瞧省,然后調用observe訂閱這個通道的消息扯夭。

發(fā)布

發(fā)布者通過getChannel獲取消息通道,然后調用setValue或者postValue發(fā)布消息鞍匾。

LiveDataBus原理圖

LiveDataBus的實現

第一個實現:

publicfinalclassLiveDataBus{

privatefinalMap>?bus;

privateLiveDataBus(){

bus?=newHashMap<>();

}

privatestaticclassSingletonHolder{

privatestaticfinalLiveDataBus?DATA_BUS?=newLiveDataBus();

}

publicstaticLiveDataBusget(){

returnSingletonHolder.DATA_BUS;

}

publicMutableLiveDatagetChannel(String?target,?Class?type){

if(!bus.containsKey(target))?{

bus.put(target,newMutableLiveData<>());

}

return(MutableLiveData)?bus.get(target);

}

publicMutableLiveDatagetChannel(String?target){

returngetChannel(target,?Object.class);

}

}

短短二十行代碼交洗,就實現了一個通信總線的全部功能,并且還具有生命周期感知功能橡淑,并且使用起來也及其簡單:

注冊訂閱:

LiveDataBus.get().getChannel("key_test",?Boolean.class)

.observe(this,newObserver()?{

@Override

publicvoidonChanged(@Nullable?Boolean?aBoolean){

}

});

發(fā)送消息:

LiveDataBus.get().getChannel("key_test").setValue(true);

我們發(fā)送了一個名為"key_test"构拳,值為true的事件。

這個時候訂閱者就會收到消息梁棠,并作相應的處理置森,非常簡單。

問題出現

對于LiveDataBus的第一版實現符糊,我們發(fā)現凫海,在使用這個LiveDataBus的過程中,訂閱者會收到訂閱之前發(fā)布的消息濒蒋。對于一個消息總線來說盐碱,這是不可接受的。無論EventBus或者RxBus沪伙,訂閱方都不會收到訂閱之前發(fā)出的消息瓮顽。對于一個消息總線,LiveDataBus必須要解決這個問題围橡。

問題分析

怎么解決這個問題呢暖混?先分析下原因:

當LifeCircleOwner的狀態(tài)發(fā)生變化的時候,會調用LiveData.ObserverWrapper的activeStateChanged函數翁授,如果這個時候ObserverWrapper的狀態(tài)是active拣播,就會調用LiveData的dispatchingValue。

在LiveData的dispatchingValue中收擦,又會調用LiveData的considerNotify方法贮配。

在LiveData的considerNotify方法中,紅框中的邏輯是關鍵塞赂,如果ObserverWrapper的mLastVersion小于LiveData的mVersion泪勒,就會去回調mObserver的onChanged方法。而每個新的訂閱者宴猾,其version都是-1圆存,LiveData一旦設置過其version是大于-1的(每次LiveData設置值都會使其version加1),這樣就會導致LiveDataBus每注冊一個新的訂閱者仇哆,這個訂閱者立刻會收到一個回調沦辙,即使這個設置的動作發(fā)生在訂閱之前。

問題原因總結

對于這個問題讹剔,總結一下發(fā)生的核心原因油讯。對于LiveData详民,其初始的version是-1,當我們調用了其setValue或者postValue撞羽,其vesion會+1阐斜;對于每一個觀察者的封裝ObserverWrapper,其初始version也為-1诀紊,也就是說谒出,每一個新注冊的觀察者,其version為-1邻奠;當LiveData設置這個ObserverWrapper的時候笤喳,如果LiveData的version大于ObserverWrapper的version,LiveData就會強制把當前value推送給Observer碌宴。

如何解決這個問題

明白了問題產生的原因之后杀狡,我們來看看怎么才能解決這個問題。很顯然贰镣,根據之前的分析呜象,只需要在注冊一個新的訂閱者的時候把Wrapper的version設置成跟LiveData的version一致即可。

那么怎么實現呢碑隆,看看LiveData的observe方法恭陡,他會在步驟1創(chuàng)建一個LifecycleBoundObserver,LifecycleBoundObserver是ObserverWrapper的派生類上煤。然后會在步驟2把這個LifecycleBoundObserver放入一個私有Map容器mObservers中休玩。無論ObserverWrapper還是LifecycleBoundObserver都是私有的或者包可見的,所以無法通過繼承的方式更改LifecycleBoundObserver的version劫狠。

那么能不能從Map容器mObservers中取到LifecycleBoundObserver拴疤,然后再更改version呢?答案是肯定的独泞,通過查看SafeIterableMap的源碼我們發(fā)現有一個protected的get方法呐矾。因此,在調用observe的時候懦砂,我們可以通過反射拿到LifecycleBoundObserver凫佛,再把LifecycleBoundObserver的version設置成和LiveData一致即可。

對于非生命周期感知的observeForever方法來說孕惜,實現的思路是一致的,但是具體的實現略有不同晨炕。observeForever的時候衫画,生成的wrapper不是LifecycleBoundObserver,而是AlwaysActiveObserver(步驟1)瓮栗,而且我們也沒有機會在observeForever調用完成之后再去更改AlwaysActiveObserver的version削罩,因為在observeForever方法體內瞄勾,步驟3的語句,回調就發(fā)生了弥激。

那么對于observeForever进陡,如何解決這個問題呢?既然是在調用內回調的微服,那么我們可以寫一個ObserverWrapper趾疚,把真正的回調給包裝起來。把ObserverWrapper傳給observeForever以蕴,那么在回調的時候我們去檢查調用棧糙麦,如果回調是observeForever方法引起的,那么就不回調真正的訂閱者丛肮。

LiveDataBus最終實現

publicfinalclassLiveDataBus{

privatefinalMap>?bus;

privateLiveDataBus(){

bus?=newHashMap<>();

}

privatestaticclassSingletonHolder{

privatestaticfinalLiveDataBus?DEFAULT_BUS?=newLiveDataBus();

}

publicstaticLiveDataBusget(){

returnSingletonHolder.DEFAULT_BUS;

}

publicMutableLiveDatawith(String?key,?Class?type){

if(!bus.containsKey(key))?{

bus.put(key,newBusMutableLiveData<>());

}

return(MutableLiveData)?bus.get(key);

}

publicMutableLiveDatawith(String?key){

returnwith(key,?Object.class);

}

privatestaticclassObserverWrapperimplementsObserver{

privateObserver?observer;

publicObserverWrapper(Observer?observer){

this.observer?=?observer;

}

@Override

publicvoidonChanged(@Nullable?T?t){

if(observer?!=null)?{

if(isCallOnObserve())?{

return;

}

observer.onChanged(t);

}

}

privatebooleanisCallOnObserve(){

StackTraceElement[]?stackTrace?=?Thread.currentThread().getStackTrace();

if(stackTrace?!=null&&?stackTrace.length?>0)?{

for(StackTraceElement?element?:?stackTrace)?{

if("android.arch.lifecycle.LiveData".equals(element.getClassName())?&&

"observeForever".equals(element.getMethodName()))?{

returntrue;

}

}

}

returnfalse;

}

}

privatestaticclassBusMutableLiveDataextendsMutableLiveData{

privateMap?observerMap?=newHashMap<>();

@Override

publicvoidobserve(@NonNull?LifecycleOwner?owner,?@NonNull?Observer?observer){

super.observe(owner,?observer);

try{

hook(observer);

}catch(Exception?e)?{

e.printStackTrace();

}

}

@Override

publicvoidobserveForever(@NonNull?Observer?observer){

if(!observerMap.containsKey(observer))?{

observerMap.put(observer,newObserverWrapper(observer));

}

super.observeForever(observerMap.get(observer));

}

@Override

publicvoidremoveObserver(@NonNull?Observer?observer){

Observer?realObserver?=null;

if(observerMap.containsKey(observer))?{

realObserver?=?observerMap.remove(observer);

}else{

realObserver?=?observer;

}

super.removeObserver(realObserver);

}

privatevoidhook(@NonNull?Observer?observer)throwsException{

//get?wrapper's?version

Class?classLiveData?=?LiveData.class;

Field?fieldObservers?=?classLiveData.getDeclaredField("mObservers");

fieldObservers.setAccessible(true);

Object?objectObservers?=?fieldObservers.get(this);

Class?classObservers?=?objectObservers.getClass();

Method?methodGet?=?classObservers.getDeclaredMethod("get",?Object.class);

methodGet.setAccessible(true);

Object?objectWrapperEntry?=?methodGet.invoke(objectObservers,?observer);

Object?objectWrapper?=null;

if(objectWrapperEntryinstanceofMap.Entry)?{

objectWrapper?=?((Map.Entry)?objectWrapperEntry).getValue();

}

if(objectWrapper?==null)?{

thrownewNullPointerException("Wrapper?can?not?be?bull!");

}

Class?classObserverWrapper?=?objectWrapper.getClass().getSuperclass();

Field?fieldLastVersion?=?classObserverWrapper.getDeclaredField("mLastVersion");

fieldLastVersion.setAccessible(true);

//get?livedata's?version

Field?fieldVersion?=?classLiveData.getDeclaredField("mVersion");

fieldVersion.setAccessible(true);

Object?objectVersion?=?fieldVersion.get(this);

//set?wrapper's?version

fieldLastVersion.set(objectWrapper,?objectVersion);

}

}

}

注冊訂閱:

LiveDataBus.get()

.with("key_test",?String.class)

.observe(this,newObserver()?{

@Override

publicvoidonChanged(@Nullable?String?s){

}

});

發(fā)送消息:

LiveDataBus.get().with("key_test").setValue(s);

源碼說明

LiveDataBus的源碼可以直接拷貝使用赡磅,也可以前往作者的GitHub倉庫查看下載:

https://github.com/JeremyLiao/LiveDataBus

總結

本文提供了一個新的消息總線框架——LiveDataBus。訂閱者可以訂閱某個消息通道的消息宝与,發(fā)布者可以把消息發(fā)布到消息通道上焚廊。利用LiveDataBus,不僅可以實現消息總線功能习劫,而且對于訂閱者咆瘟,他們不需要關心何時取消訂閱,極大減少了因為忘記取消訂閱造成的內存泄漏風險榜聂。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末搞疗,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子须肆,更是在濱河造成了極大的恐慌匿乃,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件豌汇,死亡現場離奇詭異幢炸,居然都是意外死亡,警方通過查閱死者的電腦和手機拒贱,發(fā)現死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門宛徊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人逻澳,你說我怎么就攤上這事闸天。” “怎么了斜做?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵苞氮,是天一觀的道長。 經常有香客問我瓤逼,道長笼吟,這世上最難降的妖魔是什么库物? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮贷帮,結果婚禮上戚揭,老公的妹妹穿的比我還像新娘。我一直安慰自己撵枢,他們只是感情好民晒,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著诲侮,像睡著了一般镀虐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沟绪,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天刮便,我揣著相機與錄音镰官,去河邊找鬼碍侦。 笑死,一個胖子當著我的面吹牛仑氛,可吹牛的內容都是我干的坝疼。 我是一名探鬼主播搜贤,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钝凶!你這毒婦竟也來了仪芒?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤耕陷,失蹤者是張志新(化名)和其女友劉穎掂名,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體哟沫,經...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡饺蔑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了嗜诀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猾警。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖隆敢,靈堂內的尸體忽然破棺而出发皿,到底是詐尸還是另有隱情,我是刑警寧澤拂蝎,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布雳窟,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏封救。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一捣作、第九天 我趴在偏房一處隱蔽的房頂上張望誉结。 院中可真熱鬧,春花似錦券躁、人聲如沸惩坑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽以舒。三九已至,卻和暖如春慢哈,著一層夾襖步出監(jiān)牢的瞬間蔓钟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工卵贱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留滥沫,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓键俱,卻偏偏與公主長得像兰绣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子编振,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容