LifeCycle、ViewModel打厘、LiveData 的組合使用

LifeCycle修肠、ViewModel、LiveData 的組合使用

前言

在文章正文開始之前户盯,我們先引入幾個問題:

在開發(fā)中嵌施,我們時常需要和Activity饲化、Fragment 的生命周期打交道,它們本身也提供了各自一套生命周期的方法吗伤,我們只需要在相應的方法里面做正確的事情即可吃靠,但是我們有沒有想過,這樣對于 Activity 和 Fragment 來說足淆,功能過于復雜了巢块,那么如何將這一塊業(yè)務邏輯從我們的視圖之中遷移到業(yè)務層呢?

當我們開發(fā)的APP 需要適配鍵盤狀態(tài)缸浦、屏幕旋轉(zhuǎn)夕冲、語言變化等 configure 改變會導致 Activity銷毀重建氮兵,這個時候 Activity 持有的數(shù)據(jù)就會全部丟失裂逐,在Activity 重建之后我們又要去重新獲取數(shù)據(jù),這期間可能會對用戶體驗造成不良影響泣栈,傳統(tǒng)的解決辦法是在 onSaveInstanceState方法中保存卜高,在 onCreate中恢復,這樣不僅會讓 UI 層代碼臃腫南片,而且存儲的數(shù)據(jù)還必須是可序列化的掺涛。那么有沒有辦法將這部分邏輯從 UI 層中移除呢?

現(xiàn)在比較常用的設計架構(gòu)有 MVP 和 MVVM疼进,以 MVP 為例子薪缆,通用的做法是將業(yè)務邏輯抽成一個 Presenter 接口,將 UI 邏輯抽成一個 View 接口伞广。在 Activity 中特定條件觸發(fā)的時候調(diào)用特定的 Presenter 接口方法拣帽,在 PresenterImpl 實現(xiàn)類中某個特定的條件觸發(fā)的時候調(diào)用 View 接口特定的方法,這樣來完成業(yè)務邏輯層和 UI 層之間的交互嚼锄,這種架構(gòu)設計只適用PresenterImpl 和 View 接口之間每個方法存在一對一或者少量對一的調(diào)用關(guān)系的場景减拭。我們假設一下Activity 中視圖 A 的可見性依賴于 PresenterImpl 中的布爾值 isVisible屬性,而 PresenterImpl中存在多處更改 isVisible 屬性的情況区丑,那么在這種情況下我們就需要在多個地方多次調(diào)用 View 接口中 showA()和 hiddenA()的操作了拧粪,這樣的代碼會讓人不舒服,或者我們可以通過重寫 isVisible 的設置器沧侥,但是這種無法得到保證可霎,因為這要求程序員必須顯式調(diào)用 isVisible 的設置器。那么在這種情況下我們?nèi)绾尾拍軐崿F(xiàn)在一處代碼塊中處理 isVisible 屬性改變的調(diào)用宴杀,并且不依賴于程序員的自覺性呢啥纸?

當我們的 Activity、Fragment 不可見的時候婴氮,我們是不需要也不應該刷新 UI 的斯棒。比如開啟一個倒計時任務盾致,從10倒數(shù)到0,我們期望當我們的 Actvity荣暮、Fragment 不可見的 時候我們的計時器依然是有效的庭惜,但是不會去刷新 UI 的顯示,在倒數(shù)過程中如果我們讓我們的 Activity穗酥、Fragment 變得可見的時候护赊,視圖應該正確顯示現(xiàn)在倒數(shù)到的最新數(shù)字,通過在設置視圖顯示文本的時候手動判斷當前 Activity砾跃、Fragment 的生命周期可以達到我們的期望骏啰,但是手動是不可靠的,有沒有自動的方式實現(xiàn)我們的期望呢抽高?

解決的方法

解決問題一的方案是使用實現(xiàn)了 LifecycleOwner 的 Fragment判耕、Activity,比如 V4包下的 Fragment翘骂、FragmentActivity壁熄、AppCompatActivity 之類的,lifecycleOwner 與傳統(tǒng)的 Activity碳竟、fragment 不同之處是lifecycleOwner是一個接口草丧,而不是一個實現(xiàn)類,這意味著任何類都可以實現(xiàn)這個接口莹桅,從而監(jiān)聽 Activity昌执、Fragment 的生命周期變化

解決問題二的方案是使用 ViewModel 這個組件

解決問題三的方案是使用屬性觀察者,這里是使用 LiveData诈泼,其內(nèi)部實現(xiàn)了有新值則回調(diào)通知外界的邏輯

解決問題四的方案依然是使用 LiveData懂拾,其實也就是注冊監(jiān)聽屬性變化的時候傳入一個 LifecycleOwner,然后在回調(diào)數(shù)據(jù)改變方法之前判斷當前的 Activity厂汗、Fragment 是否可見委粉,如果不可見則不進行回調(diào)。在 Activity娶桦、Fragment 重新變得可見的時候會回調(diào) LifecycleOwner 相應的方法贾节,在這些方法里面讓 LiveData 去檢查視圖最后接收到的值是否為最后一個改變的值,如果不是衷畦,則同步值到視圖觀察者即可栗涂。

三個組件的概要介紹

  • LifecycleOwner 是一個接口,它擁有 Activity祈争、Fragment 所有生命周期的方法斤程,在實現(xiàn)了這個接口的 Activity、Fragment 對象中,每個生命周期對應的方法都會被回調(diào)忿墅,LifecycleOwner 之所以設計成接口扁藕,是為了其它對象可以使用到,這樣其它對象就無需要求 Activity疚脐、Fragment 在特定的生命周期中調(diào)用特定的方法亿柑,比如終結(jié)方法、暫停方法棍弄,而這些要求往往可能被程序員所忽略望薄,也使 Activity、Fragment 變得臃腫復雜呼畸。

  • ViewModel ViewModel通常在Activity痕支、Fragment 的生命周期內(nèi)被創(chuàng)建并且與之關(guān)聯(lián)在一起直到Activity 被 finish 掉。當 Activity蛮原、Fragment 被重新創(chuàng)建的時候卧须,通過 ViewModelProvides 可以重新獲得與之關(guān)聯(lián)的 ViewModel,從而確保數(shù)據(jù)不會丟失瞬痘。ViewModel 被存儲在 ViewModelStore 類中故慈,當與之關(guān)聯(lián)的 Activity板熊、Fragment 被 finish 之后最終會調(diào)用 ViewModel 的 onCleared 方法框全,到這里之后 ViewModel 才會被釋放掉

  • LiveData 提供一個Observer接口給客戶端,客戶端實現(xiàn)這個接口干签,并將引用傳遞給它津辩,當我們調(diào)用 LiveData 的 setValue 或 postValue 的時候,LiveData 就會去回調(diào)這個接口的方法容劳,其實也就是LiveData 自己實現(xiàn)了觀察者模式喘沿。不過 LiveData 搞定了一個讓我們長期以來頭疼的事情,那就是當 Activity竭贩、Fragment 不可見的時候 UI 不要更新的問題蚜印,其內(nèi)部其實也是監(jiān)聽了 LifecycleOwner 的可見和不可見的方法,當 Activity留量、Fragment 不可見的時候就不回調(diào) Observer 的方法窄赋,當 Activity、Fragment 變成可見的時候楼熄,就對比最新的值和最后發(fā)出去的值是否是同一個忆绰,不同的話就發(fā)送最新的值,確保 Activity可岂、Fragment 可見的時候用戶看到的一定是最新的數(shù)據(jù)错敢。

上面的介紹就是這三個組件最核心的功能,雖然看上去沒有什么特別難的地方缕粹,但是確實讓我們的 Activity稚茅、Fragment 變得更加純粹纸淮,也讓我們的業(yè)務邏輯變得更加內(nèi)聚、不依賴客戶端程序員亚享。更少的依賴萎馅、更集中的代碼會讓我們的程序變得更加穩(wěn)固、易讀虹蒋,也讓我們的程序維護起來更加便利糜芳。

在詳細介紹這三個組件之前,我們先上源碼魄衅,這份源碼來自google 的 codelabs峭竣,github 地址

LifecycleOwner 簡單使用

其實這個LifecycleOwner真的沒什么好說的,因為實在太簡單了晃虫,我覺得通過上面的說明之后皆撩,我們應該是知道這個是什么東西,實現(xiàn)原理是什么哲银。
需要注意的就是只有支持包V4下面的 Fragment扛吞、FragmentActivity、AppCompatActivity等等幾個類是實現(xiàn)了 LifecycleOwner 接口的荆责。目前我們使用的 AS 只要是3.0.0以上的滥比,新建的項目中 MainActivity 應該都是繼承自AppCompatActivity的。
舉個例子做院,我們以定位為例子盲泛,可以參考BoundLocationManager。以往我們需要在 onResume 和 onPause 中分別啟動和暫停定位器键耕,現(xiàn)在我們可以將其從 Activity寺滚、Fragment 中移除,代碼如下

static class BoundLocationListener implements LifecycleObserver {
    private final Context mContext;
    private LocationManager mLocationManager;
    private final LocationListener mListener;

    public BoundLocationListener(LifecycleOwner lifecycleOwner,
                                 LocationListener listener, Context context) {
        mContext = context;
        mListener = listener;
        lifecycleOwner.getLifecycle().addObserver(this);
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    void addLocationListener() {

        Location lastLocation = //獲取用戶定位
        if (lastLocation != null) {
            mListener.onLocationChanged(lastLocation);
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    void removeLocationListener() {
        if (mLocationManager == null) {
            return;
        }
        mLocationManager.removeUpdates(mListener);
        mLocationManager = null;
    }
}

通過@OnLifecycleEvent標注我們可以標注我們的方法所對應的生命周期階段屈雄,當然我們也可以直接實現(xiàn)GenericLifecycleObserver接口村视,在void onStateChanged(LifecycleOwner source, Lifecycle.Event event);中判斷 Event 的值,不過通過標注會讓代碼更加純粹易讀酒奶。

ViewModel 簡單剖析

如上面的簡介蚁孔,ViewModel 中保存的數(shù)據(jù)可以躲避因 configure 發(fā)生改變而丟失的問題,我們先看下如何使用 ViewModel
如工程中的ChronometerViewModel類所示讥蟆,只需要繼承自 ViewModel 類即可勒虾,代碼如下

public class ChronometerViewModel extends ViewModel {

    @Nullable
    private Long mStartTime;

    @Nullable
    public Long getStartTime() {
        return mStartTime;
    }

    public void setStartTime(final long startTime) {
        this.mStartTime = startTime;
    }
}

當然沒有這么簡單,重要的是如何獲取這個 ViewModel 的對象瘸彤,我們在ChronoActivity2onCreate中通過以下的代碼獲取 ViewModel 對象

ChronometerViewModel chronometerViewModel = ViewModelProviders.of(this).get(ChronometerViewModel.class);

至于是創(chuàng)建還是重新連接已經(jīng)存在的 ViewModel修然,就靠 ViewModelProviders 與其相關(guān)的系列工廠方法去判斷,我們無需關(guān)心
當 Activity 被銷毀重建之后,系統(tǒng)會補償我們一個savedInstanceState對象愕宋,在 Activity 中通過這個對象系統(tǒng)可以恢復FragmentManager玻靡,這個 FragmentManager主要就是用來尋找HolderFragment這個對象的,通過這個對象我們就可以找到之前Activity中贝、Fragment 關(guān)聯(lián)的 ViewModelStore囤捻,進而找到對應的 ViewModel,具體分析如下:

  1. ViewModelProviders.of(this)通過靜態(tài)工廠方法創(chuàng)建一個全新的對象ViewModelProvider對象邻寿,但是這個對象里面的mViewModelStore屬性卻不一定是全新的蝎土,通過跟蹤代碼,我們可以發(fā)現(xiàn)在HolderFragment類的內(nèi)部類HolderFragmentManager中有如下屬性
private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
private Map<Fragment, HolderFragment> mNotCommittedFragmentHolders = new HashMap<>();

即對于一個 Activity绣否、Fragment 的實例或者是銷毀重建后的新實例誊涯,都只會返回相同的一個HolderFragment實例,可以通過下面的方法證明

HolderFragment holderFragmentFor(FragmentActivity activity) {
    //這行代碼獲取的的 fm 就是我們上文所說的系統(tǒng)補償給意外重建的 Activity 的參數(shù)中獲取的信息恢復出來的 fm
    FragmentManager fm = activity.getSupportFragmentManager();
    //找到了 holder蒜撮,我們就可以找到 ViewModelStore暴构,進而找到 ViewModel
    HolderFragment holder = findHolderFragment(fm); 

    //獲取 map 中 activity key 對應的值
    holder = mNotCommittedActivityHolders.get(activity);
    if (holder != null) {
        return holder;
    }

    holder = createHolderFragment(fm);
    //將新創(chuàng)建的對象作為value,activity 作為 key 保存到 map 中
    mNotCommittedActivityHolders.put(activity, holder);
    return holder;
}
  1. 我們接下去看ChronometerViewModel chronometerViewModel = provider.get(ChronometerViewModel.class);這個方法的實現(xiàn)段磨,通過跟進代碼取逾,我們確實發(fā)現(xiàn)我們的 ViewModel 對象是在mViewModelStore單例屬性中獲取的,代碼如下
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    //獲取 ViewModel 對象
    ViewModel viewModel = mViewModelStore.get(key);
    if (modelClass.isInstance(viewModel)) {
        return (T) viewModel;
    }
    //如果不存在則創(chuàng)建一個新的 ViewModel苹支,并且緩存起來
    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

通過上面的代碼砾隅,我們也就可以知道ViewModel 相關(guān)工廠類的內(nèi)部邏輯了,簡單說起來就是 viewModel 被緩存起來了沐序,只要被關(guān)聯(lián)的 key 沒有變琉用,那么就可以取到與之關(guān)聯(lián)的 ViewModel 對象堕绩,也就是我們前言所說的如何將這一塊判斷邏輯從 Activity策幼、Fragment 移除出去,通過 ViewModel 相關(guān)工廠方法就可以實現(xiàn)了奴紧。
通過上面的分析我們知道這一切的關(guān)鍵在于ViewModelProvider對象的一個單例字段 mViewModelStore特姐,嚴格意義上來說,這個不是單例黍氮,他會隨著 Activity唐含、Fragment 的銷毀而銷毀,因此也不會造成內(nèi)存泄露沫浆。當然客戶端程序員不需要顯式地調(diào)用其終結(jié)方法捷枯,因為HolderFragmentManager內(nèi)部會監(jiān)聽 LifecycleOwner 的銷毀方法,在其中移除對 ViewModel 對象的引用专执。

需要注意的是淮捆,正如上段代碼所示ViewModel viewModel = mViewModelStore.get(key);,mViewModelStore是通過一個字符串去獲取 ViewModel的,而這個 key 的值相對具體的 ViewModel 類是不會變化的攀痊,所以就算我們在 Activity 中多次調(diào)用

ChronometerViewModel chronometerViewModel = ViewModelProviders.of(this).get(ChronometerViewModel.class);

獲取到的對象都是同一個對象桐腌,所以假設我們的 Activity 中擁有多個 Fragment,每個 Fragment 都要獲取到相同的 ViewModel對象苟径,那么這個時候可以這樣寫

//傳遞 getActivity()案站,而不是 this
ChronometerViewModel chronometerViewModel = ViewModelProviders.of(getActivity()).get(ChronometerViewModel.class);

因為對于不同的 Fragment,它們唯一的關(guān)聯(lián)就是屬于同一個 Activity棘街,如果傳遞的是 this蟆盐,那么獲取到的 ViewModel 就不是同一個對象了,這一點在源碼 Fragment_step5中有所體現(xiàn)遭殉。

注意舱禽,由于 ViewModel 的生命周期會比 Activity 長,所以一定不要在 ViewModel 中持有 Activity 的引用恩沽,以免造成內(nèi)存泄露

LiveData 簡單剖析

LiveData感覺也沒什么好說的誊稚,我們先看一下用法,在 Activity罗心、Fragment 中我們觀察 LiveData 的變化里伯,可以看ChronoActivity3代碼如下

private void subscribe() {
    final Observer<Long> elapsedTimeObserver = new Observer<Long>() {
        @Override
        public void onChanged(@Nullable final Long aLong) {
            String newText = ChronoActivity3.this.getResources().getString(
                R.string.seconds, aLong);
            ((TextView) findViewById(R.id.timer_textview)).setText(newText);
            Log.d("ChronoActivity3", "Updating timer");
        }
    };

    mLiveDataTimerViewModel.getElapsedTime().observe(this, elapsedTimeObserver);
}

其實就是把 lifecycleOwner 和一個callBack 類傳遞進去,我們知道 lifecyclOwner 是為了讓 LiveData 內(nèi)部判斷是否要通知 Activity渤闷、Fragment 有新值變化了疾瓮。
接下去我們看看 LiveData 怎么創(chuàng)建和傳遞值的,可以LiveDataTimerViewModel飒箭,代碼如下

public class LiveDataTimerViewModel extends ViewModel {

    private static final int ONE_SECOND = 1000;

    private MutableLiveData<Long> mElapsedTime = new MutableLiveData<>();

    private long mInitialTime;

    public LiveDataTimerViewModel() {
        mInitialTime = SystemClock.elapsedRealtime();
        Timer timer = new Timer();

        // Update the elapsed time every second.
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000;
                // setValue() cannot be called from a background thread so post to main thread.
                mElapsedTime.postValue(newValue);
            }
        }, ONE_SECOND, ONE_SECOND);

    }

    public LiveData<Long> getElapsedTime() {
        return mElapsedTime;
    }
}

其實就是創(chuàng)建一個 MutableLiveData 對象狼电,然后在需要傳遞值的時候調(diào)用 postValue 就可以了。具體的 LiveData 實現(xiàn)并不復雜弦蹂,感興趣的朋友可以自行查看肩碟。
這里我們就只說幾點使用 LiveData 需要注意的事情:

  • LiveData 是一個抽象類,我們無法直接創(chuàng)建凸椿,只能使用它的實現(xiàn)類 MutableLiveData來創(chuàng)建對象削祈,但是我們對外聲明還是要用 LiveData,因為它的 postValue脑漫、setValue等是 protected 訪問權(quán)限的髓抑,這樣做之后外界就無法修改我們的值,不要給外部過大的權(quán)限优幸。
  • setValue 需要在主線程中調(diào)用吨拍,如果在子線程中,那么需要使用 postValue网杆。
  • liveData 可以在任意線程被訂閱羹饰,但觀察者接收到變化數(shù)據(jù)的 onChanged 方法一定是在主線程中被執(zhí)行握爷,因為這是由setValue 和 postValue 內(nèi)部調(diào)用 onChanged 方法所在的線程決定的,而這兩個方法的實現(xiàn)都是在主線程严里。

三個組件組合使用

通過上面小結(jié)的內(nèi)容之后新啼,我們知道 LifecyeleOwner 和 ViewModel、LiveData 之間都是有緊密聯(lián)系的刹碾,這一點無需我們通過代碼去組合燥撞,我們唯一能夠組合的就是 LiveData 和 ViewModel,兩者結(jié)合在一起之后其實也沒有什么生成什么化學反應迷帜,也就是同時擁有 ViewModel 和 LiveData 的特性物舒,所以也沒有什么好舉例的,只要了解了這三個組件的原理之后戏锹,想怎么組合使用都不是什么問題冠胯。

通過組合這三個組件之后我們現(xiàn)在的程序就變得職責分明了,具體有以下幾點體現(xiàn)

  • Activity锦针、Fragment 更加純粹荠察,只需要用來管理UI 邏輯,不需要了解業(yè)務層與生命周期之間的關(guān)系
  • 業(yè)務層邏輯更加內(nèi)聚奈搜,無需依賴 UI 去做生命周期相關(guān)階段的處理悉盆,避免出錯
  • 無需在 onCreate 中判斷是否要創(chuàng)建新業(yè)務數(shù)據(jù)還是從緩存中獲取,讓 UI 層代碼更加簡介
  • 當一個信號量可能在多個地方改變的時候馋吗,使用 LiveData 可以只在一處代碼塊中做邏輯處理焕盟,而不需要分散代碼或者重寫 setter 方法并強制程序員調(diào)用 setter 方法,當然當某個 UI的狀態(tài)依賴的是多個信號量或者依賴的信號量是由多個信號量組成的宏粤,那么這個時候用 RxJava2會讓代碼更加純粹脚翘、集中、易讀绍哎。

給我感覺最強大的是 LifecycleOwner来农,因為現(xiàn)在我們可以直接在我們的業(yè)務類中監(jiān)聽 Activity、Fragment 的變化蛇摸,而不需要向外接再三強調(diào)一定要在 onResume 中調(diào)用開始备图,在 onPause中調(diào)用暫停,在 onDestory 中終結(jié)赶袄,這樣的代碼只需要在業(yè)務類中一次實現(xiàn),就可以處處使用抠藕。

至于說什么時候使用這三個組件饿肺,這里我簡單說一下我的看法,首先 LifecycleOwner 和 ViewModel 是一定要處處用到的盾似,具體原因上面也說了敬辣,雖然人家叫做 ViewModel雪标,但是對于我們選擇用 MVP 或者是 MVVM 并沒有什么沖突,因為ViewModel 的概念和這兩種架構(gòu)沒有沖突溉跃,所以剩下的就是 LiveData 了村刨,LiveData 的使用場景適合在一個信號量在多處改變的時候使用,當一個信號量只在一個地方改變撰茎,那么用不用 LiveData 其實差不不大嵌牺。所以我的定位是一個信號量在多處地方改變的時候

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市龄糊,隨后出現(xiàn)的幾起案子逆粹,更是在濱河造成了極大的恐慌,老刑警劉巖炫惩,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件僻弹,死亡現(xiàn)場離奇詭異,居然都是意外死亡他嚷,警方通過查閱死者的電腦和手機蹋绽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筋蓖,“玉大人蟋字,你說我怎么就攤上這事∨っ悖” “怎么了鹊奖?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長涂炎。 經(jīng)常有香客問我忠聚,道長,這世上最難降的妖魔是什么唱捣? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任两蟀,我火速辦了婚禮,結(jié)果婚禮上震缭,老公的妹妹穿的比我還像新娘赂毯。我一直安慰自己,他們只是感情好拣宰,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布党涕。 她就那樣靜靜地躺著,像睡著了一般巡社。 火紅的嫁衣襯著肌膚如雪膛堤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天晌该,我揣著相機與錄音肥荔,去河邊找鬼绿渣。 笑死,一個胖子當著我的面吹牛燕耿,可吹牛的內(nèi)容都是我干的中符。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼誉帅,長吁一口氣:“原來是場噩夢啊……” “哼淀散!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起堵第,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤吧凉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后踏志,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阀捅,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年针余,在試婚紗的時候發(fā)現(xiàn)自己被綠了饲鄙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡圆雁,死狀恐怖忍级,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情伪朽,我是刑警寧澤轴咱,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站烈涮,受9級特大地震影響朴肺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坚洽,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一戈稿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧讶舰,春花似錦鞍盗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至庐舟,卻和暖如春欣除,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挪略。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工历帚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杠娱。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓挽牢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親摊求。 傳聞我的和親對象是個殘疾皇子禽拔,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

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