感知生命周期的數(shù)據(jù) -- LiveData

零. 前言

上篇文章《萬物基于Lifecycle》 介紹了整個Lifecycle體系的基石,今天這篇文章咱們來看看Jetpack給我們帶來的活著的數(shù)據(jù)——LiveData饿这。

大綱

  • LiveData 是什么浊伙?
  • 為什么要用LiveData?
  • How to use LiveData?
  • LiveData的生命感知能力從何而來撞秋,是如何與Lifecycle結(jié)合的?

一. LiveData 是什么吧黄?

? LiveData 簡單來說部服,就是普通數(shù)據(jù)對象的一個包裝類,這個包裝類中幫助你主動管理了數(shù)據(jù)的版本號拗慨,基于觀察者模式廓八,讓普通的數(shù)據(jù)對象能夠感知所屬宿主(Activity、Fragment)的生命周期赵抢。這種感知能力就能夠保證只有宿主活躍(Resumed剧蹂、Started)時,數(shù)據(jù)的觀察者才能受到數(shù)據(jù)變化的消息烦却。

上面這短短的一段話宠叼,卻有著重大的意義,舉一個case:

有一個頁面需要加載一個列表其爵,我們需要在后臺線程去服務(wù)器請求對應(yīng)的數(shù)據(jù)冒冬,請求數(shù)據(jù)成功并經(jīng)過解析后post消息通知UI,UI再渲染請求來的數(shù)據(jù)摩渺。等等简烤!假若此時的網(wǎng)絡(luò)很慢,而剛好用戶此時按home鍵退出了應(yīng)用摇幻,那么在此時UI是不應(yīng)該被渲染的横侦,而是應(yīng)該等用戶重新進入應(yīng)用時才開始渲染。

從下面的演示代碼就詮釋了上面的說所的case

class MainActivity extends AppcompactActivity{
    public void onCreate(Bundle bundle){
  
     Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
             //無論頁面可見不可見绰姻,都會去執(zhí)行頁面刷新,IO枉侧。更有甚者彈出對話框
        }
      };
     //1.無論當(dāng)前頁面是否可見,這條消息都會被分發(fā)。----消耗資源
     //2.無論當(dāng)前宿主是否還存活,這條消息都會被分發(fā)狂芋。---內(nèi)存泄漏
    handler.sendMessage(msg)

    
    liveData.observer(this,new Observer<User>){
         void onChanged(User user){
           
         }
     }
    //1.減少資源占用---          頁面不可見時不會派發(fā)消息
    //2.確保頁面始終保持最新狀態(tài)---頁面可見時,會立刻派發(fā)最新的一條消息給所有觀察者--保證頁面最新狀態(tài)
    //3.不再需要手動處理生命周期---避免NPE
    //4.可以打造一款不用反注冊,不會內(nèi)存泄漏的消息總線---取代eventbus
    liveData.postValue(data);
  }
}

有人說榨馁,我可以在處理消息時,根據(jù)當(dāng)前頁面時是否可見來具體處理對應(yīng)邏輯帜矾。是的翼虫,沒錯,確實可以這樣,就像下面這樣黍特。

 Handler handler = new Handler(){
    @Override
    public void handleMessage(@NonNull Message msg) {
         if (isActivityValid()) {
            // updateUI...
         } else {
            // dosomething...
         }
    }
  };

我再拿上面的例子說一下這種問題:

  1. 需要自行判斷宿主活躍狀態(tài)蛙讥,防止生命周期越界。
  2. 如果Activity不可見灭衷,此時不更新UI次慢,那么就需要復(fù)寫onResume方法去更新UI,手工管理生命周期,增加了代碼的復(fù)雜性迫像。

二. 為什么要使用LiveData?

上面的例子已經(jīng)很好地詮釋了LiveData的強大劈愚,當(dāng)然不僅限于此,它的優(yōu)勢如下:

  1. 確保界面符合數(shù)據(jù)狀態(tài)

    LiveData 遵循觀察者模式闻妓。當(dāng)生命周期狀態(tài)發(fā)生變化時菌羽,LiveData 會通知 Observer 對象。觀察者可以在onChanged事件時更新界面由缆,而不是在每次數(shù)據(jù)發(fā)生更改時立即更新界面注祖。

  2. 不會發(fā)生內(nèi)存泄漏

    觀察者會綁定到 Lifecycle 對象,并在其關(guān)聯(lián)的生命周期遭到銷毀后進行自我清理均唉。

  3. 不會因 Activity 停止而導(dǎo)致崩潰

    如果觀察者的生命周期處于非活躍狀態(tài)(如返回棧中的 Activity)是晨,則它不會接收任何 LiveData 事件。

  4. 不再需要手動處理生命周期

    界面組件只是觀察相關(guān)數(shù)據(jù)舔箭,不會停止或恢復(fù)觀察罩缴。LiveData 將自動管理所有這些操作,因為它在觀察時可以感知相關(guān)的生命周期狀態(tài)變化层扶。

  5. 數(shù)據(jù)始終保持最新狀態(tài)

    如果生命周期變?yōu)榉腔钴S狀態(tài)箫章,它會在再次變?yōu)榛钴S狀態(tài)時接收最新的數(shù)據(jù)。例如镜会,曾經(jīng)在后臺的 Activity 會在返回前臺后立即接收最新的數(shù)據(jù)檬寂。

  6. 適當(dāng)?shù)呐渲酶?/strong>

    如果由于配置更改(如設(shè)備旋轉(zhuǎn))而重新創(chuàng)建了 Activity 或 Fragment,它會立即接收最新的可用數(shù)據(jù)稚叹。

  7. 共享資源

    可以使用單一實例模式擴展 LiveData 對象以封裝系統(tǒng)服務(wù)焰薄,以便在應(yīng)用中共享它們拿诸。LiveData 對象連接到系統(tǒng)服務(wù)一次扒袖,然后需要相應(yīng)資源的任何觀察者只需觀察 LiveData 對象

  8. 支持黏性事件的分發(fā)
    即先發(fā)送一條數(shù)據(jù),后注冊一個觀察者亩码,默認是能夠收到之前發(fā)送的那條數(shù)據(jù)的

三. How to use LiveData ?

step1: 添加依賴:

step2: 在ViewModel中創(chuàng)建 LiveData 實例 (ViewModel組件會在下期講到季率,將LiveData存儲至viewModel中,是為了符合MVVM架構(gòu)思想描沟,V層僅負責(zé)展示飒泻,而VM層負責(zé)數(shù)據(jù)邏輯)

public class ViewModelTest extends AndroidViewModel {
    public final  MutableLiveData<String> name = new MutableLiveData<>();
    ...
}

step3: 在Activity或Fragment中對LiveData進行添加觀察者進行監(jiān)聽

public class ActivityTest extends AppCompatActivity {
    ...
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        viewModel.name.observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String name) {
                // dosomething...
            }
        });
    }
}

我們可以根據(jù)LiveData值的變化來做對應(yīng)的事情,且不用擔(dān)心生命周期越界的問題吏廉。

LiveData核心方法

方法名 作用
observe(LifecycleOwner owner,Observer observer) 注冊和宿主生命周期關(guān)聯(lián)的觀察者
observeForever(Observer observer) 注冊觀察者,不會反注冊,需自行維護
setValue(T data) 發(fā)送數(shù)據(jù),沒有活躍的觀察者時不分發(fā)泞遗。只能在主線程。
postValue(T data) 和setValue一樣席覆。不受線程環(huán)境限制,
onActive 當(dāng)且僅當(dāng)有一個活躍的觀察者時會觸發(fā)
inActive 不存在活躍的觀察者時會觸發(fā)

LiveData的衍生類及功能

  • MutableLiveData

    該類十分簡單史辙,主要開放了LiveData的發(fā)消息接口

    public class MutableLiveData<T> extends LiveData<T> {
          @Override
          public void postValue(T value) {
            super.postValue(value);
        }
    
        @Override
        public void setValue(T value) {
            super.setValue(value);
        }
    }
    

    設(shè)計初衷:考慮單一開閉原則,LiveData只能接受消息,避免拿到LiveData對象既能發(fā)消息也能收消息的混亂使用聊倔。

  • MediatorLiveData

    合并多個LiveData, 即一對多統(tǒng)一觀察晦毙,一個經(jīng)典的場景是:在向服務(wù)器請求數(shù)據(jù)時,優(yōu)先展示本地數(shù)據(jù)庫的數(shù)據(jù)耙蔑,然后由請求的響應(yīng)決定是否要更新數(shù)據(jù)庫见妒,如下圖所示:

    // ResultType: Type for the Resource data.
    // RequestType: Type for the API response.
    public abstract class NetworkBoundResource<ResultType, RequestType> {
        // MediatorLiveData 數(shù)據(jù)組合者
        private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();
        private Executor executor;
        @MainThread
        protected NetworkBoundResource(Executor mExecutor) {
            this.executor = mExecutor;
            // 首先初始化一個Loading的status 空result
            result.setValue(Resource.loading(null));
            // 然后從數(shù)據(jù)庫中獲取持久化數(shù)據(jù)
            LiveData<ResultType> dbSource = loadFromDb();
            // 數(shù)據(jù)組合者監(jiān)聽數(shù)據(jù)庫中的數(shù)據(jù)
            result.addSource(dbSource, data -> {
                // dbSource第一次回調(diào),用來判斷數(shù)據(jù)有效期甸陌,此時取消監(jiān)聽
                result.removeSource(dbSource);
                // 業(yè)務(wù)自行定義是否需要fetch最新的數(shù)據(jù)
                if (shouldFetch(data)) {
                    fetchFromNetwork(dbSource);
                } else {
                    // 數(shù)據(jù)有效须揣,重新觀察一次,觀察者會立馬收到一次回調(diào)(LiveData粘性事件機制)
                    result.addSource(dbSource, newData -> result.setValue(Resource.success(newData)));
                }
            });
        }
        
        private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
            LiveData<ApiResponse<RequestType>> apiResponse = createCall();
            // 這里數(shù)據(jù)雖無效钱豁,但是可以先給UI展示
            result.addSource(dbSource, newData -> setValue(Resource.loading(newData)));
            result.addSource(apiResponse, response -> {
                result.removeSource(apiResponse);
                result.removeSource(dbSource);
    
                if (response != null) {
                    if (response.isSuccessful()) {
                        executor.execute(() -> {
                            saveCallResult(processResponse(response));
                            executor.execute(() ->
                                 // 這里我們拿到的最新的數(shù)據(jù)需要主動通知監(jiān)聽者量蕊,以拿到從服務(wù)端拿到的最新數(shù)據(jù)
                                    result.addSource(loadFromDb(),
                                            newData -> setValue(Resource.success(newData)))
                            );
                        });
                    } else {
                        onFetchFailed();
                        result.addSource(dbSource,
                                newData -> setValue(Resource.error(response.errorMessage, newData)));
                    }
                } else {
                    result.addSource(dbSource,
                            newData -> setValue(Resource.error("Request failed, server didn't         response", newData)));
                }
            });
        }
    }
    

    上面的例子MediatorLiveData同時監(jiān)聽了數(shù)據(jù)庫中的LiveData和服務(wù)端的LiveData

  • Transformations

    這是一個數(shù)據(jù)轉(zhuǎn)化工具類灵寺,共兩個主要方法:

    1. 靜態(tài)轉(zhuǎn)化 -- map(@NonNull LiveData<X> source, @NonNull final Function<X, Y> mapFunction)

      MutableLiveData<Integer> data = new MutableLiveData<>();
      
      //數(shù)據(jù)轉(zhuǎn)換
      LiveData<String> transformData = Transformations.map(data, input ->   String.valueOf(input));
      //使用轉(zhuǎn)換后生成的transformData去觀察數(shù)據(jù)
      transformData.observe( this, output -> {
      
      });
      
      //使用原始的livedata發(fā)送數(shù)據(jù)
      data.setValue(10);
      
  1. 動態(tài)轉(zhuǎn)化 -- LiveData<Y> switchMap(
    @NonNull LiveData<X> source,
    @NonNull final Function<X, LiveData<Y>> switchMapFunction)
 MutableLiveData userIdLiveData = ...;
 // 用戶數(shù)據(jù)和用戶id緊密相關(guān),當(dāng)我們改變userId的liveData的同時還會主動通知userLiveData更新
LiveData userLiveData = Transformations.switchMap(userIdLiveData, id ->
     repository.getUserById(id));

 void setUserId(String userId) {
      this.userIdLiveData.setValue(userId);
 }

// 不要像下面這么做
private LiveData getUserLiveData(String userId) {
    // DON'T DO THIS
    return repository.getUserById(userId);
}

四. LiveData的實現(xiàn)機制

LiveData注冊觀察者觸發(fā)消息分發(fā)流程:

  1. observe 注冊時,可以主動跟宿主生命周期綁定先蒋,不用反注冊:
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        //1. 首先來個斷言,這個方法只能在主線程調(diào)用愧捕,observeForever也是量九。
        assertMainThread("observe");
        //2.其次把注冊進來的observer包裝成 一個具有生命周邊邊界的觀察者
        //它能監(jiān)聽宿主被銷毀的事件,從而主動的把自己反注冊塑煎,避免內(nèi)存泄漏
        //此時觀察者是否處于活躍狀態(tài)就等于宿主是否可見
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        //3.接著會判斷該觀察是否已經(jīng)注冊過了,如果是則拋異常,所以要注意垮兑,不允許重復(fù)注冊
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        //4.這一步才是關(guān)鍵
        //利用Lifecycle冷尉,把觀察者注冊到進去,才能監(jiān)聽到宿主生命周期狀態(tài)的變化系枪,對不對雀哨?
        //根據(jù)Lifecycle文章中的分析,一旦一個新的觀察者被添加私爷,Lifecycle也會同步它的狀態(tài)和宿主一致對不對雾棺?此時會觸發(fā)觀察者的onStateChanged方法
        owner.getLifecycle().addObserver(wrapper);
}
  1. LifecycleBoundObserver 監(jiān)聽宿主的生命周期(這里我們還記得之前在Lifecycle解析中提到addObserver時也會對observer進行包裝捌浩,這時一樣的),并且宿主不可見時不分發(fā)任何數(shù)據(jù):
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
            super(observer);
        }

        @Override
        boolean shouldBeActive() {
        //使用observer方法注冊的觀察者都會被包裝成LifecycleBoundObserver
        //觀察者是否活躍就等于宿主 的狀態(tài)是否大于等于STARTED工秩,
        //如果頁面當(dāng)前不可見尸饺,你發(fā)送了一條消息麻昼,此時是不會被分發(fā)的,可以避免后臺任務(wù)搶占資源,當(dāng)頁面恢復(fù)可見才會分發(fā)馋辈。
        //注意:如果使用observerForever注冊的觀察者抚芦,
        //會被包裝成AlwaysActiveObserver,它的shouldBeActive一致返回true.即便在頁面不可見也能收到數(shù)據(jù)
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
                //在這里如果監(jiān)聽到宿主被銷毀了,則主動地把自己從livedata的觀察者中移除掉
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            //否則說明宿主的狀態(tài)發(fā)生了變化迈螟,此時會判斷宿主是否處于活躍狀態(tài)
            activeStateChanged(shouldBeActive());
        }
    }
  1. ObserverWrapper 狀態(tài)變更后叉抡,如果觀察者處于活躍狀態(tài)會觸發(fā)數(shù)據(jù)的分發(fā)流程:
abstract class ObserverWrapper{
        final Observer<? super T> mObserver;
        boolean mActive;
        int mLastVersion = START_VERSION//這里就等于-1,沒有主動和LiveData的mVersion對齊,為黏性事件埋下了伏筆
        
        void activeStateChanged(boolean newActive) {
            if (newActive == mActive) {
                return;
            }
            //更改觀察者的狀態(tài)
            mActive = newActive;
            boolean wasInactive = LiveData.this.mActiveCount == 0;
            //如果此時有且只有一個活躍的觀察者則觸發(fā)onActive
            LiveData.this.mActiveCount += mActive ? 1 : -1;
            if (wasInactive && mActive) {
                onActive();
            }
            //沒有任何一個活躍的觀察者則觸發(fā)onInactive
            //利用這個方法被觸發(fā)的時機答毫,可以做很多事褥民,比如懶加載,資源釋放等
            if (LiveData.this.mActiveCount == 0 && !mActive) {
                onInactive();
            }
            //如果此時觀察者處于活躍狀態(tài),下面就開始分發(fā)數(shù)據(jù)了
            //請注意洗搂,這里傳遞了this = observer
            if (mActive) {
                dispatchingValue(this);
            }
        }
} 
  1. dispatchingValue 數(shù)據(jù)分發(fā)流程控制:
void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
            //如果傳遞的觀察者不為空消返,則把數(shù)據(jù)分發(fā)給他自己。這個流程是新注冊觀察者的時候會被觸發(fā)
                considerNotify(initiator);
                initiator = null;
            } else {
                //否則遍歷集合中所有已注冊的的觀察者耘拇,逐個調(diào)用considerNotify撵颊,分發(fā)數(shù)據(jù)
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>>                             iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }
  1. considerNotify 數(shù)據(jù)真正分發(fā)的地方,需要滿足三個套件:
private void considerNotify(ObserverWrapper observer) {
        //觀察者當(dāng)前狀態(tài)不活躍不分發(fā)
        if (!observer.mActive) {
            return;
        }
        //觀察者所在宿主是否處于活躍狀態(tài),否則不分發(fā)惫叛,并且更改觀察者的狀態(tài)為false
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        //此處判斷觀察者接收消息的次數(shù)是否大于等于 發(fā)送消息的次數(shù)
        //但是observer被創(chuàng)建之初verison=-1 
        //如果此時LiveData已經(jīng)發(fā)送過數(shù)據(jù)了倡勇。這里就不滿足了,就出現(xiàn)黏性事件了嘉涌,后注冊的觀察者收到了前面發(fā)送的消息妻熊。
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        //每分發(fā)一次消息,則把觀察者和LiveData的version對齊仑最,防止重復(fù)發(fā)送
        observer.mLastVersion = mVersion;
        //最后的數(shù)據(jù)傳遞
        observer.mObserver.onChanged((T) mData);
    }

普通消息分發(fā)流程扔役。即調(diào)用 postValue,setValue 才會觸發(fā)消息的分發(fā):

五. 總結(jié)

Android開發(fā)大部分主要的工作就是從服務(wù)器獲取數(shù)據(jù)词身,將其轉(zhuǎn)為渲染為UI展示給用戶厅目。本質(zhì)上我們所做的邏輯都是“數(shù)據(jù)驅(qū)動”番枚,所有的View都是數(shù)據(jù)的一種狀態(tài)法严,數(shù)據(jù)映射的過程中我們需要去考慮生命周期的限制--即數(shù)據(jù)的活躍性。LiveData結(jié)合Lifecycle幫我們規(guī)范了這個流程葫笼,完美詮釋了observer深啤、lifecycle-aware、data holder 這個鐵三角路星,開發(fā)者在遵循這個開發(fā)流程的過程中溯街,便完成了UI -> ViewModel -> Data的單項依賴诱桂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市呈昔,隨后出現(xiàn)的幾起案子挥等,更是在濱河造成了極大的恐慌堤尾,老刑警劉巖肝劲,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異郭宝,居然都是意外死亡辞槐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門粘室,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榄檬,“玉大人,你說我怎么就攤上這事衔统÷拱瘢” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵锦爵,是天一觀的道長犬缨。 經(jīng)常有香客問我,道長棉浸,這世上最難降的妖魔是什么怀薛? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮迷郑,結(jié)果婚禮上枝恋,老公的妹妹穿的比我還像新娘。我一直安慰自己嗡害,他們只是感情好焚碌,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著霸妹,像睡著了一般十电。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叹螟,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天鹃骂,我揣著相機與錄音,去河邊找鬼罢绽。 笑死畏线,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的良价。 我是一名探鬼主播蒿叠,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蚣常!你這毒婦竟也來了市咽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抵蚊,失蹤者是張志新(化名)和其女友劉穎魂务,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泌射,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡粘姜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了熔酷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孤紧。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拒秘,靈堂內(nèi)的尸體忽然破棺而出号显,到底是詐尸還是另有隱情,我是刑警寧澤躺酒,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布押蚤,位于F島的核電站,受9級特大地震影響羹应,放射性物質(zhì)發(fā)生泄漏揽碘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一园匹、第九天 我趴在偏房一處隱蔽的房頂上張望雳刺。 院中可真熱鬧,春花似錦裸违、人聲如沸掖桦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枪汪。三九已至,卻和暖如春怔昨,著一層夾襖步出監(jiān)牢的瞬間雀久,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工朱监, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留岸啡,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓赫编,卻偏偏與公主長得像巡蘸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子擂送,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

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