Android Jetpack系列之MVVM使用及封裝

Android開發(fā)架構(gòu)

如果開發(fā)過程中大家各自為戰(zhàn)厦幅,沒有統(tǒng)一規(guī)范,久而久之施流,項(xiàng)目代碼會(huì)變得混亂且后續(xù)難以維護(hù)响疚。當(dāng)使用統(tǒng)一的架構(gòu)模式后,有很多的好處瞪醋,如:

  • 統(tǒng)一開發(fā)規(guī)范忿晕,使得代碼整潔、規(guī)范银受,后續(xù)易于維護(hù)及擴(kuò)展
  • 提高開發(fā)效率(尤其在團(tuán)隊(duì)人員較多時(shí))
  • 模塊單一職責(zé)践盼,使得模塊專注自己內(nèi)部(面向?qū)ο?,模塊間解耦

總之宾巍,開發(fā)架構(gòu)是前人總結(jié)出來的一套行之有效的開發(fā)模式咕幻,目的是達(dá)到高內(nèi)聚,低耦合的效果顶霞,使得項(xiàng)目代碼更健壯肄程、易維護(hù)。

Android中常見的架構(gòu)模式有MVC(Model-View-Controller)选浑、MVP(Model-View-Presenter)蓝厌、MVVM(Model-View-ViewModel),一起來看下各自的特點(diǎn):

MVC

MVC(Model-View-Controller)是比較早期的架構(gòu)模式古徒,模式整體也比較簡單拓提。

MVC模式將程序分成了三個(gè)部分:

  • Model模型層:業(yè)務(wù)相關(guān)的數(shù)據(jù)(如網(wǎng)絡(luò)請求數(shù)據(jù)、本地?cái)?shù)據(jù)庫數(shù)據(jù)等)及其對數(shù)據(jù)的處理
  • View視圖層:頁面視圖(通過XML布局編寫視圖層)隧膘,負(fù)責(zé)接收用戶輸入代态、發(fā)起數(shù)據(jù)請求及展示結(jié)果頁面
  • Controller控制器層:M與V之間的橋梁,負(fù)責(zé)業(yè)務(wù)邏輯

MVC特點(diǎn):

  • 簡單易用:上圖表述了數(shù)據(jù)整個(gè)流程:View接收用戶操作疹吃,通過Controller去處理業(yè)務(wù)邏輯蹦疑,并通過Model去獲取/更新數(shù)據(jù),然后Model層又將最新的數(shù)據(jù)傳回View層進(jìn)行頁面展示互墓。
  • 架構(gòu)簡單的另一面往往是對應(yīng)的副作用:由于XML布局能力弱必尼,我們的View層的很多操作都是寫在Activity/Fragment中,同時(shí),Controller判莉、Model層的代碼也大都寫在Activity/Fragment中豆挽,這就會(huì)導(dǎo)致一個(gè)問題,當(dāng)業(yè)務(wù)邏輯比較復(fù)雜時(shí)券盅,Activity/Fragment中的代碼量會(huì)很大帮哈,其違背了類單一職責(zé),不利于后續(xù)擴(kuò)展及維護(hù)锰镀。尤其是后期你剛接手的項(xiàng)目娘侍,一個(gè)Activity/Fragment類中的代碼動(dòng)輒上千行代碼,那感覺著實(shí)酸爽:當(dāng)然泳炉,如果業(yè)務(wù)很簡單憾筏,使用MVC模式還是一種不錯(cuò)的選擇。

MVP

MVP(Model-View-Presenter)花鹅,架構(gòu)圖如下:

MVP各模塊職責(zé)如下

  • Model模型:業(yè)務(wù)相關(guān)的數(shù)據(jù)(如網(wǎng)絡(luò)請求數(shù)據(jù)氧腰、本地?cái)?shù)據(jù)庫數(shù)據(jù)等)及其對數(shù)據(jù)的處理
  • View視圖:頁面視圖(Activity/Fragment),負(fù)責(zé)接收用戶輸入刨肃、發(fā)起數(shù)據(jù)請求及展示結(jié)果頁面
  • Presenter:M與V之間的橋梁古拴,負(fù)責(zé)業(yè)務(wù)邏輯

MVP特點(diǎn)View層接收用戶操作,并通過持有的Presenter去處理業(yè)務(wù)邏輯真友,請求數(shù)據(jù)黄痪;接著Presenter層通過Model去獲取數(shù)據(jù),然后Model又將最新的數(shù)據(jù)傳回Presenter層盔然,Presenter層又持有View層的引用桅打,進(jìn)而將數(shù)據(jù)傳給View層進(jìn)行展示。

MVP相比MVC的幾處變化

  • View層與Model層不再交互愈案,而是通過Presenter去進(jìn)行聯(lián)系
  • 本質(zhì)上MVP是面向接口編程油额,Model/View/Presenter每層的職責(zé)分工明確,當(dāng)業(yè)務(wù)復(fù)雜時(shí)刻帚,整個(gè)流程邏輯也是很清晰的

當(dāng)然,MVP也不是十全十美的涩嚣,MVP本身也存在以下問題:

  • View層會(huì)抽象成IView接口崇众,并在IView中聲明一些列View相關(guān)的方法;同樣的航厚,Presenter會(huì)被抽象成IPresenter接口及其一些列方法顷歌,每當(dāng)實(shí)現(xiàn)一個(gè)功能時(shí),都需要編寫多個(gè)接口及其對應(yīng)的方法幔睬,實(shí)現(xiàn)起來相對比較繁瑣眯漩,而且每次有改動(dòng)時(shí),對應(yīng)的接口方法也基本都會(huì)再去改動(dòng)。
  • View層與Presenter層相互持有赦抖,當(dāng)View層關(guān)閉時(shí)舱卡,由于Presenter層不是生命周期感知的,可能會(huì)導(dǎo)致內(nèi)存泄漏甚至是崩潰队萤。

ps:如果你的項(xiàng)目中使用了RxJava轮锥,可以使用 AutoDispose 自動(dòng)解綁。

MVVM

MVVM(Model-View-ViewModel)要尔,架構(gòu)圖如下:

MVVM各職責(zé)如下

  • Model模型:業(yè)務(wù)相關(guān)的數(shù)據(jù)(如網(wǎng)絡(luò)請求數(shù)據(jù)舍杜、本地?cái)?shù)據(jù)庫數(shù)據(jù)等)及其對數(shù)據(jù)的處理
  • View視圖:頁面視圖(Activity/Fragment),負(fù)責(zé)接收用戶輸入赵辕、發(fā)起數(shù)據(jù)請求及展示結(jié)果頁面
  • ViewModel:M與V之間的橋梁既绩,負(fù)責(zé)業(yè)務(wù)邏輯

MVVM特點(diǎn)

  • View層接收用戶操作,并通過持有的ViewModel去處理業(yè)務(wù)邏輯还惠,請求數(shù)據(jù)饲握;
  • ViewModel層通過Model去獲取數(shù)據(jù),然后Model又將最新的數(shù)據(jù)傳回ViewModel層吸重,到這里互拾,ViewModel與Presenter所做的事基本是一樣的。但是ViewModel不會(huì)也不能持有View層的引用嚎幸,而是View層會(huì)通過觀察者模式監(jiān)聽ViewModel層的數(shù)據(jù)變化颜矿,當(dāng)有新數(shù)據(jù)時(shí),View層能自動(dòng)收到新數(shù)據(jù)并刷新界面嫉晶。

UI驅(qū)動(dòng) vs 數(shù)據(jù)驅(qū)動(dòng)

MVP中骑疆,Presenter中需要持有View層的引用,當(dāng)數(shù)據(jù)變化時(shí)替废,需要主動(dòng)調(diào)用View層對應(yīng)的方法將數(shù)據(jù)傳過去并進(jìn)行UI刷新箍铭,這種可以認(rèn)為是UI驅(qū)動(dòng);而MVVM中椎镣,ViewModel并不會(huì)持有View層的引用诈火,View層會(huì)監(jiān)聽數(shù)據(jù)變化,當(dāng)ViewModel中有數(shù)據(jù)更新時(shí)状答,View層能直接拿到新數(shù)據(jù)并完成UI更新冷守,這種可以認(rèn)為是數(shù)據(jù)驅(qū)動(dòng),顯然惊科,MVVM相比于MVP來說更加解耦拍摇。

MVVM的具體實(shí)現(xiàn)

上面介紹了MVC/MVP/MVVM的各自特點(diǎn),其中MVC/MVP的具體使用方式馆截,本文不再展開實(shí)現(xiàn)充活,接下來主要聊一下MVVM的使用及封裝蜂莉,MVVM也是官方推薦的架構(gòu)模式。

Jetpack MVVM

Jetpack是官方推出的一系列組件庫混卵,使用組件庫開發(fā)有很多好處映穗,如:

  • 遵循最佳做法:采用最新的設(shè)計(jì)方法構(gòu)建,具有向后兼容性淮菠,可以減少崩潰和內(nèi)存泄漏
  • 消除樣板代碼:開發(fā)者可以更好地專注業(yè)務(wù)邏輯
  • 減少不一致:可以在各種Android版本中運(yùn)行男公,兼容性更好。

為了實(shí)現(xiàn)上面的MVVM架構(gòu)模式合陵,Jetpack提供了多個(gè)組件來實(shí)現(xiàn)枢赔,具體來說有Lifecycle、LiveData拥知、ViewModel(這里的ViewModel是MVVM中ViewModel層的具體實(shí)現(xiàn))踏拜,其中Lifecycle負(fù)責(zé)生命周期相關(guān);LiveData賦予類可觀察低剔,同時(shí)還是生命周期感知的(內(nèi)部使用了Lifecycle)速梗;ViewModel旨在以注重生命周期的方式存儲(chǔ)和管理界面相關(guān)的數(shù)據(jù),針對這幾個(gè)庫的詳細(xì)介紹及使用方式就不再展開了襟齿。

通過這幾個(gè)庫姻锁,就可以實(shí)現(xiàn)MVVM了,官方也發(fā)布了MVVM的架構(gòu)圖:

其中Activity/FragmentView層猜欺,ViewModel+LiveDataViewModel層位隶,為了統(tǒng)一管理網(wǎng)絡(luò)數(shù)據(jù)及本地?cái)?shù)據(jù)數(shù)據(jù),又引入了Repository中間管理層开皿,本質(zhì)上是為了更好地管理數(shù)據(jù)涧黄,為了簡單把他們統(tǒng)稱為Model層吧。

使用舉例

  • View層代碼:
//MvvmExampleActivity.kt
class MvvmExampleActivity : BaseActivity() {

    private val mTvContent: TextView by id(R.id.tv_content)
    private val mBtnQuest: Button by id(R.id.btn_request)
    private val mToolBar: Toolbar by id(R.id.toolbar)

    override fun getLayoutId(): Int {
        return R.layout.activity_wan_android
    }

    override fun initViews() {
        initToolBar(mToolBar, "Jetpack MVVM", true)
    }

    override fun init() {
        //獲取ViewModel實(shí)例赋荆,注意這里不能直接new笋妥,因?yàn)閂iewModel的生命周期比Activity長
        mViewModel = ViewModelProvider(this).get(WanViewModel::class.java)

        mBtnQuest.setOnClickListener {
            //請求數(shù)據(jù)
            mViewModel.getWanInfo()
        }

        //ViewModel中的LiveData注冊觀察者并監(jiān)聽數(shù)據(jù)變化
        mViewModel.mWanLiveData.observe(this) { list ->
            val builder = StringBuilder()
            for (index in list.indices) {
                //每條數(shù)據(jù)進(jìn)行折行顯示
                if (index != list.size - 1) {
                    builder.append(list[index])
                    builder.append("\n\n")
                } else {
                    builder.append(list[index])
                }
            }
            mTvContent.text = builder.toString()
        }
    }
}

  • ViewModel層代碼:
//WanViewModel.kt
class WanViewModel : ViewModel() {
    //LiveData
    val mWanLiveData = MutableLiveData<List<WanModel>>()
    //loading
    val loadingLiveData = SingleLiveData<Boolean>()
    //異常
    val errorLiveData = SingleLiveData<String>()

    //Repository中間層 管理所有數(shù)據(jù)來源 包括本地的及網(wǎng)絡(luò)的
    private val mWanRepo = WanRepository()

    fun getWanInfo(wanId: String = "") {
        //展示Loading
        loadingLiveData.postValue(true)
        viewModelScope.launch(Dispatchers.IO) {
            try {
                val result = mWanRepo.requestWanData(wanId)
                when (result.state) {
                    State.Success -> mWanLiveData.postValue(result.data)
                    State.Error -> errorLiveData.postValue(result.msg)
                }
            } catch (e: Exception) {
                error(e.message ?: "")
            } finally {
                loadingLiveData.postValue(false)
            }
        }
    }
}

  • Repository層(Model層)代碼:
class WanRepository {

    //請求網(wǎng)絡(luò)數(shù)據(jù)
    suspend fun requestWanData(drinkId: String): BaseData<List<WanModel>> {
        val service = RetrofitUtil.getService(DrinkService::class.java)

        val baseData = service.getBanner()
        if (baseData.code == 0) {
            //正確
            baseData.state = State.Success
        } else {
            //錯(cuò)誤
            baseData.state = State.Error
        }
        return baseData
    }
}

這里只通過Retrofit請求了網(wǎng)絡(luò)數(shù)據(jù) 玩Android 開放API,如果需要添加本地?cái)?shù)據(jù)窄潭,只需要在方法里添加本地?cái)?shù)據(jù)處理即可春宣,即 Repository是數(shù)據(jù)的管理中間層,對數(shù)據(jù)進(jìn)行統(tǒng)一管理嫉你,ViewModel層中不需要關(guān)心數(shù)據(jù)的來源信认,大家各司其職即可,符合單一職責(zé)均抽,代碼可讀性更好,同時(shí)也更加解耦其掂。在View層點(diǎn)擊按鈕請求數(shù)據(jù)油挥,執(zhí)行結(jié)果如下:

以上就完成了一次網(wǎng)絡(luò)請求,相比于MVPMVVM既不用聲明多個(gè)接口及方法深寥,同時(shí)ViewModel也不會(huì)像Presenter那樣去持有View層的引用攘乒,而是生命周期感知的,MVVM方式更加解耦惋鹅。

封裝

上一節(jié)介紹了Jetpack MVVM的使用例子则酝,可以看到有一些代碼邏輯是可以抽離出來封裝到公共部分的,那么本節(jié)就嘗試對其做一次封裝闰集。

首先沽讹,請求數(shù)據(jù)時(shí)可能會(huì)展示Loading,請求完后可能是空數(shù)據(jù)武鲁、錯(cuò)誤數(shù)據(jù)爽雄,對應(yīng)下面的IStatusView接口聲明:

interface IStatusView {
    fun showEmptyView() //空視圖
    fun showErrorView(errMsg: String) //錯(cuò)誤視圖
    fun showLoadingView(isShow: Boolean) //展示Loading視圖
}

因?yàn)閂iewModel是在Activity中初始化的,所以可以封裝成一個(gè)Base類:

abstract class BaseMvvmActivity<VM : BaseViewModel> : BaseActivity(), IStatusView {

    protected lateinit var mViewModel: VM
    protected lateinit var mView: View
    private lateinit var mLoadingDialog: LoadingDialog

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mLoadingDialog = LoadingDialog(this, false)
        mViewModel = getViewModel()!!
        init()
        registerEvent()
    }

    /**
     * 獲取ViewModel 子類可以復(fù)寫沐鼠,自行初始化
     */
    protected open fun getViewModel(): VM? {
        //當(dāng)前對象超類的Type
        val type = javaClass.genericSuperclass
        //ParameterizedType表示參數(shù)化的類型
        if (type != null && type is ParameterizedType) {
            //返回此類型實(shí)際類型參數(shù)的Type對象數(shù)組
            val actualTypeArguments = type.actualTypeArguments
            val tClass = actualTypeArguments[0]
            return ViewModelProvider(this).get(tClass as Class<VM>)
        }
        return null
    }

    override fun showLoadingView(isShow: Boolean) {
        if (isShow) {
            mLoadingDialog.showDialog(this, false)
        } else {
            mLoadingDialog.dismissDialog()
        }
    }

    override fun showEmptyView() {
       ......
    }

    //錯(cuò)誤視圖 并且可以重試
    override fun showErrorView(errMsg: String) {
       .......
    }

    private fun registerEvent() {
       //接收錯(cuò)誤信息
       mViewModel.errorLiveData.observe(this) { errMsg ->
           showErrorView(errMsg)
       }
       //接收Loading信息
       mViewModel.loadingLiveData.observe(this, { isShow ->
           showLoadingView(isShow)
       })
    }

    abstract fun init()
}

Base類中初始化ViewModel挚瘟,還可以通過官方activity-ktxfragment-ktx擴(kuò)展庫饲梭,初始化方式:val model: VM by viewModels()乘盖。

子類中繼承如下:

class MvvmExampleActivity : BaseMvvmActivity<WanViewModel>() {

    private val mTvContent: TextView by id(R.id.tv_content)
    private val mBtnQuest: Button by id(R.id.btn_request)
    private val mToolBar: Toolbar by id(R.id.toolbar)

    override fun getLayoutId(): Int {
        return R.layout.activity_wan_android
    }

    override fun initViews() {
        initToolBar(mToolBar, "Jetpack MVVM", true)
    }

    override fun init() {
        mBtnQuest.setOnClickListener {
            //請求數(shù)據(jù)
            mViewModel.getWanInfo()
        }
        /**
         * 這里使用了擴(kuò)展函數(shù),等同于mViewModel.mWanLiveData.observe(this) {}
         */
        observe(mViewModel.mWanLiveData) { list ->
            val builder = StringBuilder()
            for (index in list.indices) {
                //每條數(shù)據(jù)進(jìn)行折行顯示
                if (index != list.size - 1) {
                    builder.append(list[index])
                    builder.append("\n\n")
                } else {
                    builder.append(list[index])
                }
            }
            mTvContent.text = builder.toString()
        }
    }
}

我們把ViewModel的初始化放到了父類里進(jìn)行憔涉,代碼看上去更簡單了订框。監(jiān)聽數(shù)據(jù)變化mViewModel.mWanLiveData.observe(this) {} 方式改成observe(mViewModel.mWanLiveData) {}方式,少傳了一個(gè)LifecycleOwner监氢,其實(shí)這是一個(gè)擴(kuò)展函數(shù)布蔗,如下:

fun <T> LifecycleOwner.observe(liveData: LiveData<T>, observer: (t: T) -> Unit) {
    liveData.observe(this, { observer(it) })
}

ps:我們初始化View控件時(shí),如 private val mBtnQuest: Button by id(R.id.btn_request)浪腐,依然使用了擴(kuò)展函數(shù)纵揍,如下:

fun <T : View> Activity.id(id: Int) = lazy {
    findViewById<T>(id)
}

不用像寫java代碼中那樣時(shí)刻要想著判空,同時(shí)只會(huì)在使用時(shí)才會(huì)進(jìn)行初始化议街,很實(shí)用泽谨!

說回來,接著是ViewModel層的封裝特漩,BaseViewModel.kt

abstract class BaseViewModel : ViewModel() {
    //loading
    val loadingLiveData = SingleLiveData<Boolean>()
    //異常
    val errorLiveData = SingleLiveData<String>()

    /**
     * @param request 正常邏輯
     * @param error 異常處理
     * @param showLoading 請求網(wǎng)絡(luò)時(shí)是否展示Loading
     */
    fun launchRequest(
        showLoading: Boolean = true,
        error: suspend (String) -> Unit = { errMsg ->
            //默認(rèn)異常處理吧雹,子類可以進(jìn)行覆寫
            errorLiveData.postValue(errMsg)
        }, request: suspend () -> Unit
    ) {
        //是否展示Loading
        if (showLoading) {
            loadStart()
        }

        //使用viewModelScope.launch開啟協(xié)程
        viewModelScope.launch(Dispatchers.IO) {
            try {
                request()
            } catch (e: Exception) {
                error(e.message ?: "")
            } finally {
                if (showLoading) {
                    loadFinish()
                }
            }
        }
    }

    private fun loadStart() {
        loadingLiveData.postValue(true)
    }

    private fun loadFinish() {
        loadingLiveData.postValue(false)
    }
}

擴(kuò)展一下1、上面執(zhí)行網(wǎng)絡(luò)請求時(shí)涂身,使用viewModelScope.launch來啟動(dòng)協(xié)程雄卷,引入方式:

implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'

這樣就可以直接在ViewModel中啟動(dòng)協(xié)程并且當(dāng)ViewModel生命周期結(jié)束時(shí)協(xié)程也會(huì)自動(dòng)關(guān)閉,避免使用GlobalScope.launch { }MainScope().launch { }還需自行關(guān)閉協(xié)程蛤售, 當(dāng)然丁鹉,如果是在Activity/Fragment妒潭、liveData中使用協(xié)程,也可以按需引入:

implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'

具體可以參見官方的 將 Kotlin 協(xié)程與生命周期感知型組件一起使用 這篇文章揣钦。

2雳灾、另外細(xì)心的讀者可能觀察到,上面我們的Loading冯凹、Error信息監(jiān)聽都是用的SingleLiveData谎亩,把這個(gè)類打代碼貼一下:

/**
 * 多個(gè)觀察者存在時(shí),只有一個(gè)Observer能夠收到數(shù)據(jù)更新
 * https://github.com/android/architecture-samples/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java
 */
class SingleLiveData<T> : MutableLiveData<T>() {
    companion object {
        private const val TAG = "SingleLiveEvent"
    }
    private val mPending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
        }
        // Observe the internal MutableLiveData
        super.observe(owner) { t ->
            //如果expect為true宇姚,那么將值update為false匈庭,方法整體返回true,
            //即當(dāng)前Observer能夠收到更新空凸,后面如果還有訂閱者嚎花,不能再收到更新通知了
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        }
    }

    override fun setValue(@Nullable value: T?) {
        //AtomicBoolean中設(shè)置的值設(shè)置為true
        mPending.set(true)
        super.setValue(value)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }
}

可以看到SingleLiveData還是繼承自MutableLiveData,區(qū)別是當(dāng)多個(gè)觀察者存在時(shí)呀洲,只有一個(gè)Observer能夠收到數(shù)據(jù)更新紊选,本質(zhì)上是在observe()時(shí)通過CAS加了限制,注釋已經(jīng)很詳細(xì)了道逗,不再贅述兵罢。

子類中繼承如下:

class WanViewModel : BaseViewModel() {
    //LiveData
    val mWanLiveData = MutableLiveData<List<WanModel>>()

    //Repository中間層 管理所有數(shù)據(jù)來源 包括本地的及網(wǎng)絡(luò)的
    private val mWanRepo = WanRepository()

    fun getWanInfo(wanId: String = "") {
        launchRequest {
            val result = mWanRepo.requestWanData(wanId)
            when (result.state) {
                State.Success -> mWanLiveData.postValue(result.data)
                State.Error -> errorLiveData.postValue(result.msg)
            }
        }
    }
}

最后是對Model層的封裝,BaseRepository.kt

open class BaseRepository {
    suspend fun <T : Any> executeRequest(
        block: suspend () -> BaseData<T>
    ): BaseData<T> {
        val baseData = block.invoke()
        if (baseData.code == 0) {
            //正確
            baseData.state = State.Success
        } else {
            //錯(cuò)誤
            baseData.state = State.Error
        }
        return baseData
    }
}

數(shù)據(jù)基類BaseData.kt

class BaseData<T> {
    @SerializedName("errorCode")
    var code = -1
    @SerializedName("errorMsg")
    var msg: String? = null
    var data: T? = null
    var state: State = State.Error
}

enum class State {
    Success, Error
}

子類中繼承如下:

class WanRepository : BaseRepository() {
    suspend fun requestWanData(drinkId: String): BaseData<List<WanModel>> {
        val service = RetrofitUtil.getService(DrinkService::class.java)
        return executeRequest {
            service.getBanner()
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末滓窍,一起剝皮案震驚了整個(gè)濱河市卖词,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吏夯,老刑警劉巖此蜈,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異噪生,居然都是意外死亡裆赵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門跺嗽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來战授,“玉大人,你說我怎么就攤上這事桨嫁≈怖迹” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵璃吧,是天一觀的道長楣导。 經(jīng)常有香客問我,道長畜挨,這世上最難降的妖魔是什么爷辙? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任彬坏,我火速辦了婚禮,結(jié)果婚禮上膝晾,老公的妹妹穿的比我還像新娘。我一直安慰自己务冕,他們只是感情好血当,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著禀忆,像睡著了一般臊旭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上箩退,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天离熏,我揣著相機(jī)與錄音,去河邊找鬼戴涝。 笑死滋戳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼壁榕,長吁一口氣:“原來是場噩夢啊……” “哼棚点!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起工腋,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蓄拣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡努隙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年球恤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剃法。...
    茶點(diǎn)故事閱讀 40,427評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碎捺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贷洲,到底是詐尸還是另有隱情收厨,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布优构,位于F島的核電站诵叁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏钦椭。R本人自食惡果不足惜拧额,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一碑诉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侥锦,春花似錦进栽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至番挺,卻和暖如春唠帝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玄柏。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工襟衰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粪摘。 一個(gè)月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓瀑晒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親赶熟。 傳聞我的和親對象是個(gè)殘疾皇子瑰妄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評論 2 359

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