談一談我對(duì)mvp框架的理解

我是最近才開始寫Android文章玫霎,暫時(shí)不知道該寫些什么東西鸽斟。外加上一位朋友好像對(duì)mvp有點(diǎn)疑問酗捌。我本不想一開始就寫這個(gè)呢诬,但是我又不耐煩的去給他講什么mvp,mvp該怎么寫胖缤。我想了一下尚镰,與其一點(diǎn)一點(diǎn)告訴他什么是mvp,還不如寫下一篇文章來分享我關(guān)于MVP的一些理解哪廓。

說在前面

首先狗唉,在我的觀點(diǎn)里面,閱讀該源碼是需要有一點(diǎn)Android的開發(fā)經(jīng)驗(yàn)的涡真。如果你只是一個(gè)初學(xué)者或者是沒有基礎(chǔ)的小伙子分俯,我奉勸你別花費(fèi)時(shí)間來閱讀我這篇文章,可能對(duì)你的發(fā)展并沒有多大的作用哆料。

然后談到框架缸剪,其實(shí)首先映入眼簾的應(yīng)該是mvc框架,這是最早在學(xué)習(xí)java的時(shí)候常見的东亦。m是model層杏节,v是view層、c是control層。這篇文章呢奋渔?我希望由mvc的概念講起镊逝、延伸至mvp的概念,然后再簡單的寫一個(gè)mvp的demo卒稳、到最后實(shí)際來封裝出一個(gè)在我掌控之內(nèi)的mvp框架蹋半。結(jié)尾希望能結(jié)合我的一些開發(fā)經(jīng)驗(yàn)談一談mvp的優(yōu)劣勢(shì)他巨。

一充坑、 mvc

首先mvc框架共分為三層。m是實(shí)體層用來組裝數(shù)據(jù)的染突;v是視圖層用來顯示數(shù)據(jù)的捻爷;c是控制層用來分發(fā)用戶的操作給視圖層》萜螅總的來說也榄,基本的流程應(yīng)該是下圖:

image.png

簡單的來說mvc的運(yùn)行流程就是:用戶通過控制層去分發(fā)操作到實(shí)體層去組裝數(shù)據(jù),最后將數(shù)據(jù)展示到視圖層的過程司志。

如果按照Android如今的分法的話甜紫,原本的實(shí)體層里面就應(yīng)該還是實(shí)體層,然后fragment/activity里面就會(huì)富含生命周期骂远、業(yè)務(wù)邏輯囚霸、視圖的操作等等。這樣做的好處呢激才?是代碼量比較統(tǒng)一拓型,易于查找。

但是當(dāng)業(yè)務(wù)邏輯比較復(fù)雜的時(shí)候呢瘸恼?就會(huì)出現(xiàn)代碼量比較龐大劣挫,我甚至在之前的一個(gè)項(xiàng)目內(nèi)看到了將近2000行的一個(gè)activity。當(dāng)時(shí)我驚了個(gè)呆东帅。由于剛接觸那個(gè)項(xiàng)目压固,我調(diào)試、log等等一系列操作都用上了靠闭,硬是用了三天才搞清楚代碼的流程帐我。

作為一個(gè)有追求的程序員,也為了成為一個(gè)有責(zé)任心的程序員阎毅。我建議你看一看mvp焚刚。

二、 mvp

前面談到在mvc里面扇调,業(yè)務(wù)邏輯層和視圖都會(huì)放在activity/fragment里面進(jìn)行操作矿咕,并且本身activity就需要維護(hù)自己的生命周期。這會(huì)導(dǎo)致activity/fragment里面代碼的臃腫,減少代碼的可讀性和代碼的可維護(hù)性碳柱。

在我看來mvp框架其實(shí)是mvc框架變種產(chǎn)品捡絮。講原本的activity/fragment的層次劃分成present層和view層。m還是原來的實(shí)體層用來組裝數(shù)據(jù)莲镣,p層則用來隔離view層福稳,被稱為中介層,v層還是view層主要用來展示數(shù)據(jù)的層瑞侮。如下圖所示:

有了present層之后呢的圆?view層就專心在activity/fragment里面主要去處理視圖層和維護(hù)自己的生命周期,將業(yè)務(wù)邏輯委托給present層半火,present層作為實(shí)體層和視圖層的中介越妈。實(shí)體層和視圖層不直接進(jìn)行交互,而是通過委托給persent層進(jìn)行交互钮糖,這樣做的好處是:

  • 分離了視圖邏輯和業(yè)務(wù)邏輯梅掠,降低了耦合
  • Activity只處理生命周期的任務(wù),代碼變得更加簡潔
  • 視圖邏輯和業(yè)務(wù)邏輯分別抽象到了View和Presenter的接口中去店归,提高代碼的可閱讀性
  • Presenter被抽象成接口阎抒,可以有多種具體的實(shí)現(xiàn),所以方便進(jìn)行單元測(cè)試
  • 把業(yè)務(wù)邏輯抽到Presenter中去消痛,避免后臺(tái)線程引用著Activity導(dǎo)致Activity的資源無法被系統(tǒng)回收從而引起內(nèi)存泄露和OOM
  • 方便代碼的維護(hù)和單元測(cè)試且叁。

其實(shí)說了這么多,都是瞎說

Talk is cheap, let me show you the code!

三肄满、 用mvp簡單實(shí)現(xiàn)一個(gè)實(shí)例

我看了很多mvp都在模擬寫一個(gè)登陸的界面谴古,我也就來簡單的模擬一個(gè)登陸的界面吧。

activity_main的代碼:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white">

    <android.support.constraint.Group
        android:id="@+id/login_group"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="visible"
        app:constraint_referenced_ids="edit_username,edit_password,guide_view,login_btn,clear_btn" />

    <EditText
        android:id="@+id/edit_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="請(qǐng)輸入賬號(hào)"
        android:inputType="text"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/edit_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:hint="請(qǐng)輸入密碼"
        android:inputType="textPassword"
        app:layout_constraintStart_toStartOf="@id/edit_username"
        app:layout_constraintTop_toBottomOf="@id/edit_username" />


    <android.support.constraint.Guideline
        android:id="@+id/guide_view"
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />

    <Button
        android:id="@+id/login_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="10dp"
        android:layout_marginTop="20dp"
        android:text="登陸"
        app:layout_constraintEnd_toStartOf="@id/guide_view"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintTop_toBottomOf="@id/edit_password" />

    <Button
        android:id="@+id/clear_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:text="重置"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toEndOf="@id/guide_view"
        app:layout_constraintTop_toTopOf="@id/login_btn" />

    <android.support.v4.widget.ContentLoadingProgressBar
        android:id="@+id/progress_bar"
        style="?android:attr/progressBarStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        app:layout_constraintBottom_toTopOf="parent"
        app:layout_constraintEnd_toStartOf="parent"
        app:layout_constraintStart_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="parent" />

    <Button
        android:id="@+id/retry_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="重試"
        android:visibility="gone"
        app:layout_constraintBottom_toTopOf="parent"
        app:layout_constraintEnd_toStartOf="parent"
        app:layout_constraintStart_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="parent" />

    <TextView
        android:id="@+id/login_success_tips"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="登陸成功3砬浮掰担!"
        android:visibility="gone"
        app:layout_constraintBottom_toTopOf="parent"
        app:layout_constraintEnd_toStartOf="parent"
        app:layout_constraintStart_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="parent" />


</android.support.constraint.ConstraintLayout>

說明一下:我里面用到了很多ConstraintLayout的新屬性,如果你對(duì)這個(gè)有疑問怒炸,請(qǐng)翻閱我之前的文章ConstraintLayout用法詳解.

MainActivity的代碼(視圖層):

class MainActivity : AppCompatActivity(), IView, View.OnClickListener {


    private var persent: IPresent? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        persent = MainPresent(this)

        login_btn.setOnClickListener(this)

        clear_btn.setOnClickListener(this)

        retry_btn.setOnClickListener(this)

    }

    override fun onClick(view: View?) {
        when (view?.id) {
            R.id.login_btn -> {
                persent?.checkFrom(edit_username.text.toString(),
                        edit_password.text.toString())

            }
            R.id.clear_btn -> {
                edit_username.setText("")
                edit_password.setText("")
            }
            R.id.retry_btn -> {
                retry_btn.visibility = View.GONE
                persent?.checkFrom(edit_username.text.toString(),
                        edit_password.text.toString())
            }
        }
    }

    override fun errorShowTips(tips: String) {
        toast(tips)
    }

    override fun onSubmit() {
        login_group.visibility = View.INVISIBLE
        progress_bar.visibility = View.VISIBLE
    }

    override fun showResult(loginSuccess: Boolean) {
        progress_bar.visibility = View.GONE

        if (loginSuccess) {
            login_success_tips.visibility = View.VISIBLE
        } else {
            retry_btn.visibility = View.VISIBLE
        }
    }
}

MainModel的代碼(實(shí)體層):

class MainModel : IModel{

    // 模擬請(qǐng)求數(shù)據(jù)
    override fun login(username: String, password: String): Observable<Boolean> {
        return Observable.just(true)
    }
}

MainPresent的代碼(中介層):

class MainPresent(view: IView) : IPresent {
    private var view: IView? = null
    private var model: IModel? = null

    init {
        model = MainModel()
        this.view = view
    }

    override fun checkFrom(username: String, password: String) {
        if (username.isEmpty()) {
            view?.errorShowTips("請(qǐng)輸入用戶名")
            return
        }
        if (password.isBlank()) {
            view?.errorShowTips("請(qǐng)輸入密碼")
            return
        }
        view?.onSubmit()

        // 模擬一下網(wǎng)絡(luò)加載的延時(shí)
        model?.run {
            login(username = username, password = password)
                    .delay(2, TimeUnit.SECONDS)
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribeBy(
                            onNext = {
                                view?.showResult(it)
                            },
                            onError = {
                                view?.showResult(false)
                            }
                    )
        }
    }
}

IFeature的代碼(封裝接口):

interface IView {
    
    fun errorShowTips(tips:String)

    fun onSubmit()

    fun showResult(loginSuccess: Boolean)

}

interface IPresent {
    
    fun checkFrom(username:String,password:String)

}

interface IModel {

    fun login(username:String,password: String): Observable<Boolean>

}

四带饱、 重新封裝一下mvp

如果你是一個(gè)對(duì)自己的要求非常高的程序員,你會(huì)盡量去優(yōu)化重復(fù)的代碼阅羹。如果你對(duì)上面的代碼已經(jīng)純熟了之后勺疼,你會(huì)發(fā)現(xiàn):我們每次都會(huì)寫想同的代碼。好處就是增加了你對(duì)代碼層面的熟悉程度捏鱼,但是壞處就會(huì)造成大量的代碼冗余执庐。

所以此時(shí)我們就需要一個(gè)抽取想同的代碼進(jìn)行封裝操作。當(dāng)然我的經(jīng)歷也不算太過豐富导梆,可能代碼考慮面沒有那么全轨淌。如果存在疑慮的呢迂烁?可以進(jìn)行討論、完善递鹉。

基類:IFeature.kt

interface IModel {

    fun onAttach()

    fun onDetach()
}

interface IView

interface IPresenter<in V : IView, in M : IModel> {

    fun attach(view: V?, model: M?)

    fun onResume()

    fun onPause()

    fun detach()

    fun isAttached(): Boolean
}

Presenter:

abstract class Presenter : PresenterLifecycle, PresenterLifecycleOwner {

    protected open var mContext: Context? = null
    /**
     * mHandler is main thread handler
     */
    protected val mHandler: Handler = Handler(Looper.getMainLooper())
    /**
     * currentState is current present lifecycle state
     */
    override var currentState: Event = Event.DETACH
    /**
     * mOnAttachStateChangedListeners contains listeners object who would be notified when this presenter's lifecycle changed
     */
    private val mOnAttachStateChangedListeners: FastSafeIterableMap<OnAttachStateChangedListener, Unit> = FastSafeIterableMap()
    /**
     * isAttached is true after presenter has been invoked [onAttach]
     */
    protected var mIsAttached: Boolean = false
    /**
     * isPaused is true when presenter's lifecycle is ON_PAUSE
     */
    protected var mIsPaused: Boolean = false


    open fun onAttach(context: Context) {
        mContext = context
        mIsAttached = true
        currentState = Event.ATTACH
        synchronized(this) {
            mOnAttachStateChangedListeners.forEach { (listener, _) ->
                listener.onStateChanged(this, Event.ATTACH)
            }
        }
    }

    open fun onResume() {
        mIsPaused = false
        currentState = PresenterLifecycle.Event.ON_RESUME
        synchronized(this) {
            mOnAttachStateChangedListeners.forEach { (listener, _) ->
                listener.onStateChanged(this, Event.ON_RESUME)
            }
        }
    }

    open fun onPause() {
        mIsPaused = true
        currentState = PresenterLifecycle.Event.ON_PAUSE
        synchronized(this) {
            mOnAttachStateChangedListeners.forEach { (listener, _) ->
                listener.onStateChanged(this, Event.ON_PAUSE)
            }
        }
    }

    open fun onDetach() {
        mIsAttached = false
        currentState = PresenterLifecycle.Event.DETACH
        synchronized(this) {
            mOnAttachStateChangedListeners.forEach { (listener, _) ->
                listener.onStateChanged(this, Event.DETACH)
                mOnAttachStateChangedListeners.remove(listener)
            }
        }
    }

    override fun addOnAttachStateChangedListener(listener: PresenterLifecycle.OnAttachStateChangedListener) {
        synchronized(this) {
            mOnAttachStateChangedListeners.putIfAbsent(listener, Unit)
        }
    }

    override fun removeOnAttachStateChangesListener(listener: PresenterLifecycle.OnAttachStateChangedListener) {
        synchronized(this) {
            mOnAttachStateChangedListeners.remove(listener)
        }
    }

    override fun getLifecycle(): PresenterLifecycle {
        return this
    }

}

PresenterLifecycle

interface PresenterLifecycle {

    var currentState: Event

    fun addOnAttachStateChangedListener(listener: OnAttachStateChangedListener)

    fun removeOnAttachStateChangesListener(listener: OnAttachStateChangedListener)

    interface OnAttachStateChangedListener {
        fun onStateChanged(presenter: Presenter, event: Event)
    }

    enum class Event {
        ATTACH, ON_RESUME, ON_PAUSE, DETACH
    }
}

VMpresent:

abstract class VMPresenter<V : IView, M : IModel>(val context: Context) : Presenter(), IPresenter<V, M> {

    /**
     * viewRef is weak reference of view object
     */
    private var viewRef: WeakReference<V>? = null
    /**
     * modelRef is weak reference of model object
     */
    private var modelRef: WeakReference<M>? = null
    /**
     * Convenient property for accessing view object
     */
    protected val view: V?
        get() = viewRef?.get()
    /**
     * Convenient property for access model object
     */
    protected val model: M?
        get() = modelRef?.get()
    /**
     * isPaused is true when presenter's lifecycle is ON_PAUSE
     */
    protected val isPaused: Boolean
        get() = mIsPaused


    override fun attach(view: V?, model: M?) {
        super.onAttach(context)
        viewRef = if (view != null) WeakReference(view) else null
        modelRef = if (model != null) WeakReference(model) else null
    }

    override fun detach() {
        super.onDetach()
        // clear the listeners to avoid strong retain cycle
        modelRef = null
        viewRef = null
    }

    override fun isAttached(): Boolean = mIsAttached

}

Model:

abstract class Model(context: Context) : IModel {

    protected val context: Context = context.applicationContext

    override fun onAttach() {}

    override fun onDetach() {}

}

以上就是我自己對(duì)mvp框架的一個(gè)封裝盟步,可能還存在著很多的漏洞。

五躏结、 mvp的劣勢(shì)以及介紹一下mvvm

首先對(duì)于mvp的優(yōu)勢(shì)却盘,我想我就不用說了。至于mvp的劣勢(shì):是需要加入Presenter來作為橋梁協(xié)調(diào)View和Model媳拴,同時(shí)也會(huì)導(dǎo)致Presenter變得很臃腫黄橘,在維護(hù)時(shí)比較不方便。而且對(duì)于每一個(gè)Activity禀挫,基本上均需要一個(gè)對(duì)應(yīng)的Presenter來進(jìn)行對(duì)應(yīng)旬陡。

如果外加上 自己封裝的話,這種代碼的框架性就會(huì)愈發(fā)明顯语婴。所以我覺得如果不是對(duì)邏輯有很大要求的情況之下,沒必要使用mvp框架了驶睦。

當(dāng)然除了mvp框架之外砰左,還有mvvm,甚至還有更加出色的mvpvm框架场航。我在這里呢缠导?就簡單介紹一下:

  • MVVM
    MVVM其實(shí)是對(duì)MVP的一種改進(jìn),他將Presenter替換成了ViewModel溉痢,并通過雙向的數(shù)據(jù)綁定來實(shí)現(xiàn)視圖和數(shù)據(jù)的交互僻造。也就是說只需要將數(shù)據(jù)和視圖綁定一次之后,那么之后當(dāng)數(shù)據(jù)發(fā)生改變時(shí)就會(huì)自動(dòng)的在UI上刷新而不需要我們自己進(jìn)行手動(dòng)刷新孩饼。在MVVM中髓削,他盡可能的會(huì)簡化數(shù)據(jù)流的走向,使其變得更加簡潔明了镀娶。示意圖如下:


  • MVPVM

MVPVM即:Model-View-Presenter-ViewModel立膛。此模式是MVVM和MVP模式的結(jié)合體。但是交互模式發(fā)生了比較大的變化梯码。

Presenter同時(shí)持有View宝泵、Model、ViewModel轩娶,負(fù)責(zé)協(xié)調(diào)三方的之間的交互儿奶。

View持有ViewModel。ViewModel是View展示數(shù)據(jù)的一個(gè)映射鳄抒,兩者之間雙向綁定:
(1)當(dāng)View的數(shù)據(jù)發(fā)生變化時(shí)闯捎,View將數(shù)據(jù)更改同步到ViewModel搅窿。比如用戶在輸入框輸入了內(nèi)容。
(2)View監(jiān)聽ViewModel的數(shù)據(jù)變化隙券,當(dāng)ViewModel的數(shù)據(jù)發(fā)生變化時(shí)男应,View根據(jù)ViewModel的數(shù)據(jù)更新UI顯示。比如更新來自后端的數(shù)據(jù)列表娱仔。

Presenter持有View沐飘,并且View的動(dòng)作響應(yīng)傳遞至Presenter。當(dāng)收到View的動(dòng)作響應(yīng)之后牲迫,Presenter通過Model獲取后端或者數(shù)據(jù)庫數(shù)據(jù)耐朴,請(qǐng)求參數(shù)來自于Presenter持有的ViewModel。

當(dāng)Model請(qǐng)求到數(shù)據(jù)之后盹憎,將數(shù)據(jù)返回給Presenter筛峭,Presenter將返回的數(shù)據(jù)傳遞到ViewModel,由于View和ViewModel之間的綁定關(guān)系陪每,View會(huì)根據(jù)ViewModel的數(shù)據(jù)更新UI顯示影晓。


說在最后

說到項(xiàng)目本身呢?我是用的最新的kotlin+anko配合rx的寫法檩禾,這也是我認(rèn)為我這篇文章不適合新手學(xué)習(xí)的原因挂签。首先你能看懂這篇文章呢?可能要對(duì)kotlin有一定的了解盼产,然后可能還需要對(duì)rx有一定的了解饵婆。這能看懂這篇文章。

至于后面的mvvm和mvpvm其實(shí)我基本上都只是有些了解戏售,具體的我沒有進(jìn)行深究侨核,如果后面有需要 我也會(huì)深究一下這里只是做簡單的介紹罷了

我接觸kotlin也有一年多了,也寫了一個(gè)大的項(xiàng)目 對(duì)于這個(gè)語法有一定的心得灌灾,后續(xù)我會(huì)結(jié)合我自己的心得和體會(huì)給諸位讀者一一講述出來搓译。好了,時(shí)間不早了紧卒,對(duì)于一個(gè)失眠的人侥衬,現(xiàn)在已經(jīng)到極點(diǎn)了。先洗澡睡覺了跑芳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末轴总,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子博个,更是在濱河造成了極大的恐慌怀樟,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盆佣,死亡現(xiàn)場(chǎng)離奇詭異往堡,居然都是意外死亡械荷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門虑灰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吨瞎,“玉大人,你說我怎么就攤上這事穆咐〔鳎” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵对湃,是天一觀的道長崖叫。 經(jīng)常有香客問我,道長拍柒,這世上最難降的妖魔是什么心傀? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮拆讯,結(jié)果婚禮上脂男,老公的妹妹穿的比我還像新娘。我一直安慰自己往果,他們只是感情好疆液,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著陕贮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪潘飘。 梳的紋絲不亂的頭發(fā)上肮之,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音卜录,去河邊找鬼戈擒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛艰毒,可吹牛的內(nèi)容都是我干的筐高。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼丑瞧,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼柑土!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绊汹,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤稽屏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后西乖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狐榔,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坛增,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了薄腻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片收捣。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖庵楷,靈堂內(nèi)的尸體忽然破棺而出罢艾,到底是詐尸還是另有隱情,我是刑警寧澤嫁乘,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布昆婿,位于F島的核電站,受9級(jí)特大地震影響蜓斧,放射性物質(zhì)發(fā)生泄漏仓蛆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一挎春、第九天 我趴在偏房一處隱蔽的房頂上張望看疙。 院中可真熱鬧,春花似錦直奋、人聲如沸能庆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搁胆。三九已至,卻和暖如春邮绿,著一層夾襖步出監(jiān)牢的瞬間渠旁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工船逮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顾腊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓挖胃,卻偏偏與公主長得像杂靶,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子酱鸭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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