React設(shè)計(jì)思維的啟發(fā) - Android View Component 架構(gòu)

Android View Component 架構(gòu)設(shè)計(jì)思維

重構(gòu)記事

為什么要重構(gòu)乾忱?

  • 項(xiàng)目當(dāng)前采用的DataBinding框架嚴(yán)重限制了編譯速度状土,并且DataBinding框架存在著出錯(cuò)提示混亂的毛病,在出錯(cuò)的時(shí)候大幅度降低了開(kāi)發(fā)效率(當(dāng)然沒(méi)錯(cuò)的時(shí)候還是很快的)
  • 在嘗試為Freeline適配最新的DataBinding時(shí)候遇到了巨大的阻力允瞧,實(shí)現(xiàn)的可能性很低了皱蹦,只能做到局部兼容掰担,因此需要多長(zhǎng)全量編譯续捂,開(kāi)發(fā)效率低下
  • 為Freeline適配kotlin增量成功垦垂,因此開(kāi)始使用kotlin語(yǔ)言開(kāi)發(fā)(kapt不敢用除外),準(zhǔn)備大規(guī)模遷移至kotlin開(kāi)發(fā)語(yǔ)言
  • 一些之前的邏輯存在著混亂的毛病牙瓢,模塊間耦合關(guān)系有待進(jìn)一步梳理

做什么乔外?

  • 使用自己的觀察者框架代替Google自帶的DataBinding實(shí)現(xiàn)數(shù)據(jù)流
  • 使用kotlin寫(xiě)重構(gòu)代碼,并且局部替換Java代碼
  • 去除一些不痛不癢的注解處理框架一罩,在大幅度應(yīng)用之前去除AROUTER,Butterknife

先思考 => 什么架構(gòu)

我應(yīng)該用什么架構(gòu) MVP MVVM 撇簿?

  • MVP 作為android應(yīng)用很火的架構(gòu)聂渊,因?yàn)槌浞值慕怦畋粯I(yè)界廣泛使用,蛋疼之處在于需要些大量的接口來(lái)規(guī)范每一層的行為四瘫,來(lái)進(jìn)行進(jìn)一步的解耦汉嗽。接口也可以被用于單元測(cè)試,目前的項(xiàng)目中還并沒(méi)有足夠的精力去寫(xiě)單元測(cè)試找蜜,也不存在替換model或其他某層的需求饼暑,因此可以使用只抽象View接口版的MVP架構(gòu)(如果你有MVP情節(jié)的話)
  • MVVM架構(gòu)隨著DataBinding架構(gòu)的提出而在android上被慢慢推廣,ViewModel作為數(shù)據(jù)渲染層洗做,承接著講model渲染到view上的重任弓叛,同時(shí)使用數(shù)據(jù)綁定的方式將其與view相關(guān)聯(lián),MVVM的設(shè)計(jì)原則是ViewModel層不持有View的引用诚纸,加之DataBinding功能有限和某些部分及其蛋疼撰筷,可以做到高效開(kāi)發(fā)但是某些時(shí)候及其蛋疼,當(dāng)然我個(gè)人而言是非常喜歡MVVM架構(gòu)以及數(shù)據(jù)綁定思維的畦徘,所以它也是重構(gòu)前微北洋(我的項(xiàng)目名字)主模塊的架構(gòu)

那么兩種架構(gòu)都有自己蛋疼的地方毕籽,可不可以有一種新的架構(gòu)設(shè)計(jì)方式呢

抬闯!

前些時(shí)間接觸了React設(shè)計(jì)思維,React框架將各個(gè)UI組件稱為Component关筒,每個(gè)Component內(nèi)部維護(hù)了自己的view和一些邏輯溶握,并且將狀態(tài)而非是view暴露在外,通過(guò)改變Component的狀態(tài)來(lái)實(shí)現(xiàn)這個(gè)組件UI和其他細(xì)節(jié)的改變蒸播,Component暴露在外的狀態(tài)是可以確定全局狀態(tài)的最小狀態(tài)睡榆,即狀態(tài)的最小集,Component的其他剩余狀態(tài)都可以通過(guò)暴露狀態(tài)的改變所推倒出來(lái)廉赔,當(dāng)然這個(gè)推倒應(yīng)該是響應(yīng)式的肉微,自動(dòng)的。

當(dāng)然android上無(wú)法寫(xiě)類似于JSX的東西蜡塌,如果最類似的話碉纳,那就是Anko的DSL布局框架了,這樣子就可以將view寫(xiě)在Component里面馏艾。

不過(guò)View寫(xiě)在Xml里面劳曹,然后在Component的初始化時(shí)候來(lái)find來(lái)那些view去操作也是ok的(因?yàn)閍nko的DSL寫(xiě)法依然是一種富有爭(zhēng)議的布局方式,盡管我定制的Freeline已經(jīng)可以做到kotlin修改的10s內(nèi)增量編譯琅摩,DSL還是有很多坑)

說(shuō)了這么多铁孵,這個(gè)架構(gòu)到底具體是什么樣子呢?

  • 所有的view組件抽象成Component
  • 每個(gè)Component內(nèi)維護(hù)自己的view房资,對(duì)外暴露可以推倒出全局狀態(tài)的最小狀態(tài)集蜕劝,view均為private,不可被外部訪問(wèn)到轰异,只可以修改Component的狀態(tài)而不可訪問(wèn)component的view
  • Component內(nèi)部維護(hù)自己view與狀態(tài)之間的關(guān)系岖沛,推薦使用響應(yīng)式數(shù)據(jù)流的方式來(lái)進(jìn)行綁定,某些數(shù)據(jù)發(fā)生變化的時(shí)候?qū)?yīng)的view也發(fā)生自己的改變

可見(jiàn)搭独,Component本身是高內(nèi)聚的婴削,對(duì)外暴露最小狀態(tài),所以外部只需修改最小的狀態(tài)(集)就可以完成Component行為/view的變化牙肝,因此外部調(diào)用極其方便而且也不存在邏輯之間的相互干擾

怎么做唉俗?

  • Component怎么分?
  • Component需要傳入什么配椭?
  • Component放在哪里虫溜?
  • Component內(nèi)部數(shù)據(jù)流怎么寫(xiě)?
  • Component對(duì)外暴露什么股缸?怎么暴露吼渡?
  • Component內(nèi)部狀態(tài)怎么管理?

先看一個(gè)圖來(lái)解釋

Screen Shot 2017-10-30 at 11.32.46 AM.png

圖示部分的頁(yè)面乓序,是使用Recyclerview作為頁(yè)面容器寺酪,里面的每個(gè)Item坎背,就可以作為一個(gè)Component來(lái)對(duì)待

Screen Shot 2017-10-30 at 11.34.29 AM.png

進(jìn)一步的,此Component里面的那幾個(gè)圖書(shū)詳情item寄雀,又可以作為子Component來(lái)對(duì)待

Screen Shot 2017-10-30 at 11.36.31 AM.png

他們的xml布局因?yàn)闃O其簡(jiǎn)單就跳過(guò)不談得滤,Component的設(shè)計(jì)部分我們可以從最小的item說(shuō)起

因?yàn)樗鼪](méi)有被放在Recyclerview里面,所以它繼承ViewHolder與否都是隨意的盒犹,但是為了統(tǒng)一性懂更,就繼承RecyclerView.ViewHolder好了(事實(shí)上是否繼承它都是隨意的)

先來(lái)看這個(gè)Component對(duì)應(yīng)的數(shù)據(jù)Model部分

public class Book {

    /**
     * barcode : TD002424561
     * title : 設(shè)計(jì)心理學(xué).2,與復(fù)雜共處急膀,= Living with complexity
     * author : (美) 唐納德·A·諾曼著
     * callno : TB47/N4(5) v.2
     * local : 北洋園工學(xué)閱覽區(qū)
     * type : 中文普通書(shū)
     * loanTime : 2017-01-09
     * returnTime : 2017-03-23
     */

    public String barcode;
    public String title;
    public String author;
    public String callno;
    public String local;
    public String type;
    public String loanTime;
    public String returnTime;

    /**
     * 距離還書(shū)日期還有多少天
     * @return
     */
    public int timeLeft(){
        return BookTimeHelper.getBetweenDays(returnTime);
//        return 20;
    }

    /**
     * 看是否超過(guò)還書(shū)日期
     * @return
     */
    public boolean isOverTime(){
        return this.timeLeft() < 0;
    }

    public boolean willBeOver(){
        return this.timeLeft() < 7 && !isOverTime();
    }
}

我們的需求是:在這個(gè)view里面有 書(shū)的名字沮协,應(yīng)還日期,書(shū)本icon的涂色方案隨著距離還書(shū)日期的長(zhǎng)短而變色

首先聲明用到的view和Context什么的

class BookItemComponent(lifecycleOwner: LifecycleOwner,itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val mContext = itemView.context
    private val cover: ImageView = itemView.findViewById(R.id.ic_item_book)
    private val name: TextView = itemView.findViewById(R.id.tv_item_book_name)
    private val returntimeText: TextView = itemView.findViewById(R.id.tv_item_book_return)
}

LifecycleOwner是來(lái)自Android Architecture Components的組件卓嫂,用來(lái)管理android生命周期用慷暂,避免組件的內(nèi)存泄漏問(wèn)題 Android Architecture Components

下來(lái)就是聲明可觀察的數(shù)據(jù)(也可以成為狀態(tài))

    private val bookData = MutableLiveData<Book>()

因?yàn)榇薈omponent邏輯簡(jiǎn)單,只需要觀測(cè)Book類即可推斷確定其狀態(tài)晨雳,因此它也是這個(gè)Component的最小狀態(tài)集合

插播一條補(bǔ)充知識(shí):

LiveData<T>,MutableLiveData<T>也都來(lái)自于Android Architecture Components的組件行瑞,是生命周期感知的可觀測(cè)動(dòng)態(tài)數(shù)據(jù)組件

Sample:

LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(this, price -> {
            // Update the UI. 
        });

當(dāng)然用kotlin給它寫(xiě)了一個(gè)簡(jiǎn)單的函數(shù)式拓展

/**
 * LiveData 自動(dòng)綁定的kotlin拓展 再也不同手動(dòng)指定重載了hhh
 */
fun <T> LiveData<T>.bind(lifecycleOwner: LifecycleOwner, block : (T?) -> Unit) {
    this.observe(lifecycleOwner,android.arch.lifecycle.Observer<T>{
        block(it)
    })
}

好了,回到正題餐禁,然后我們就該把view和Component的可觀測(cè)數(shù)據(jù)/狀態(tài)綁定起來(lái)了

    init {
        bookData.bind(lifecycleOwner) {
            it?.apply {
                name.text = this.title
                setBookCoverDrawable(book = this)
                returntimeText.text = "應(yīng)還日期: ${this.returnTime}"
            }
        }
    }
//這里是剛剛調(diào)用的函數(shù) 寫(xiě)了寫(xiě)動(dòng)態(tài)涂色的細(xì)節(jié)   
private fun setBookCoverDrawable(book: Book) {
        var drawable = ContextCompat.getDrawable(mContext, R.drawable.lib_book)
        val leftDays = book.timeLeft()
        when {
            leftDays > 20 -> DrawableCompat.setTint(drawable, Color.rgb(0, 167, 224)) //blue
            leftDays > 10 -> DrawableCompat.setTint(drawable, Color.rgb(42, 160, 74)) //green
            leftDays > 0 -> {
                if (leftDays < 5) {
                    val act = mContext as? Activity
                    act?.apply {
                        Alerter.create(this)
                                .setTitle("還書(shū)提醒")
                                .setBackgroundColor(R.color.assist_color_2)
                                .setText(book.title + "剩余時(shí)間不足5天血久,請(qǐng)盡快還書(shū)")
                                .show()
                    }
                }
                DrawableCompat.setTint(drawable, Color.rgb(160, 42, 42)) //red
            }
            else -> drawable = ContextCompat.getDrawable(mContext, R.drawable.lib_warning)
        }
        cover.setImageDrawable(drawable)
    }

通過(guò)觀測(cè)LiveData<Book>來(lái)實(shí)現(xiàn)Component狀態(tài)的改變,因此只需要修改Book就可以實(shí)現(xiàn)該Component的相關(guān)一系列改變

然后我們只需要把相關(guān)函數(shù)暴露出來(lái)

    fun render(): View = itemView

    fun bindBook(book: Book){
        bookData.value = book
    }

然后在需要的時(shí)候創(chuàng)建調(diào)用它就可以了

val view = inflater.inflate(R.layout.item_common_book, bookContainer, false)
val bookItem = BookItemComponent(life cycleOwner = lifecycleOwner, itemView = view)
bookItem.bindBook(it)
bookItemViewContainer.add(view)

來(lái)點(diǎn)復(fù)雜的帮非?

來(lái)看主頁(yè)的圖書(shū)館模塊


Screen Shot 2017-10-30 at 11.34.29 AM.png

圖書(shū)館模塊本身也是一個(gè)Component氧吐。

需求:第二行的圖標(biāo)在刷新的時(shí)候顯示progressbar,刷新成功后顯示imageview(對(duì)勾)末盔,刷新錯(cuò)誤的時(shí)候imageview顯示錯(cuò)誤的的圖片

  1. 這個(gè)Item要放在Recyclerview里面副砍,所以要繼承ViewHolder

    class HomeLibItemComponent(private val lifecycleOwner: LifecycleOwner, itemView: View) : RecyclerView.ViewHolder(itemView) {
    }
    
  2. 聲明該Component里面用到的view

    class HomeLibItemComponent(private val lifecycleOwner: LifecycleOwner, itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val context = itemView.context
        private val stateImage: ImageView = itemView.findViewById(R.id.ic_home_lib_state)
        private val stateProgressBar: ProgressBar = itemView.findViewById(R.id.progress_home_lib_state)
        private val stateMessage: TextView = itemView.findViewById(R.id.tv_home_lib_state)
        private val bookContainer: LinearLayout = itemView.findViewById(R.id.ll_home_lib_books)
        private val refreshBtn: Button = itemView.findViewById(R.id.btn_home_lib_refresh)
        private val renewBooksBtn: Button = itemView.findViewById(R.id.btn_home_lib_renew)
        private val loadMoreBooksBtn: Button = itemView.findViewById(R.id.btn_home_lib_more)
    
    
  3. 聲明Component里面的可觀測(cè)數(shù)據(jù)流

    private val loadMoreBtnText = MutableLiveData<String>()
    private val loadingState = MutableLiveData<Int>()
    private val message = MutableLiveData<String>()
    private var isExpanded = false
    
  4. 聲明一些其他的用到的東西

    //對(duì)應(yīng)barcode和book做查詢
    private val bookHashMap = HashMap<String, Book>()
    private val bookItemViewContainer = mutableListOf<View>() //緩存的LinearLayout里面的view 折疊提高效率用
    private val libApi = RetrofitProvider.getRetrofit().create(LibApi::class.java)
    
  5. 建立綁定關(guān)系

        init {
            //這里bind一下 解個(gè)耦
            message.bind(lifecycleOwner) { message ->
                stateMessage.text = message
            }
    
            loadingState.bind(lifecycleOwner) { state ->
                when (state) {
                    PROGRESSING -> {
                        stateImage.visibility = View.INVISIBLE
                        stateProgressBar.visibility = View.VISIBLE
                        message.value = "正在刷新"
    
                    }
                    OK -> {
                        stateImage.visibility = View.VISIBLE
                        stateProgressBar.visibility = View.INVISIBLE
                        Glide.with(context).load(R.drawable.lib_ok).into(stateImage)
    
                    }
                    WARNING -> {
                        stateImage.visibility = View.VISIBLE
                        stateProgressBar.visibility = View.INVISIBLE
                        Glide.with(context).load(R.drawable.lib_warning).into(stateImage)
                    }
                }
            }
     
            loadMoreBtnText.bind(lifecycleOwner) {
                loadMoreBooksBtn.text = it
                if (it == NO_MORE_BOOKS) {
                    loadMoreBooksBtn.isEnabled = false
                }
            }
        }
    
    
  6. 再寫(xiě)一個(gè)OnBindViewHolder的回調(diào)(到時(shí)候手動(dòng)調(diào)用就可以了,會(huì)考慮使用接口規(guī)范這部分內(nèi)容)

    fun onBind() {
            refreshBtn.setOnClickListener {
                refresh(true)
            }
            refresh()
            renewBooksBtn.setOnClickListener {
                renewBooksClick()
            }
            loadMoreBooksBtn.setOnClickListener { view: View ->
                if (isExpanded) {
                    // LinearLayout remove的時(shí)候會(huì)數(shù)組順延 所以要從后往前遍歷
                    (bookContainer.childCount - 1 downTo 0)
                            .filter { it >= 3 }
                            .forEach { bookContainer.removeViewAt(it) }
                    loadMoreBtnText.value = "顯示剩余(${bookItemViewContainer.size - 3})"
                    isExpanded = false
                } else {
                    (0 until bookItemViewContainer.size)
                            .filter { it >= 3 }
                            .forEach { bookContainer.addView(bookItemViewContainer[it]) }
                    loadMoreBtnText.value = "折疊顯示"
                    isExpanded = true
                }
            }
        }
    
  7. 剩下的就是方法的具體實(shí)現(xiàn)了這個(gè)看個(gè)人喜歡的處理方式來(lái)處理庄岖,比如說(shuō)我喜歡協(xié)程處理網(wǎng)絡(luò)請(qǐng)求,然后用LiveData處理多種請(qǐng)求的映射

    比如說(shuō)一個(gè)簡(jiǎn)單的網(wǎng)絡(luò)請(qǐng)求以及緩存的封裝

    object LibRepository {
        private const val USER_INFO = "LIB_USER_INFO"
        private val libApi = RetrofitProvider.getRetrofit().create(LibApi::class.java)
    
        fun getUserInfo(refresh: Boolean = false): LiveData<Info> {
            val livedata = MutableLiveData<Info>()
            async(UI) {
                if (!refresh) {
                    val cacheData: Info? = bg { Hawk.get<Info>(USER_INFO) }.await()
                    cacheData?.let {
                        livedata.value = it
                    }
                }
    
                val networkData: Info? = bg { libApi.libUserInfo.map { it.data }.toBlocking().first() }.await()
                networkData?.let {
                    livedata.value = it
                    bg { Hawk.put(USER_INFO, networkData) }
                }
    
            }
            return livedata
        }
    
    }
    

8.與其他Component的組合
使用簡(jiǎn)單的方法即可相互集成角骤,傳入inflate好的view和對(duì)應(yīng)的LifecycleOwener即可

   data?.books?.forEach {
     bookHashMap[it.barcode] = it
     val view = inflater.inflate(R.layout.item_common_book, bookContainer, false)
     val bookItem = BookItemComponent(lifecycleOwner = lifecycleOwner, itemView = view)
     bookItem.bindBook(it)
     bookItemViewContainer.add(view)
 }

小總結(jié):狀態(tài)綁定隅忿,數(shù)據(jù)觀測(cè)

在圖書(shū)館的這個(gè)Component的開(kāi)發(fā)中,只需要在發(fā)起各種任務(wù)以及處理任務(wù)返回信息的時(shí)候邦尊,改變相關(guān)的狀態(tài)值和可觀測(cè)數(shù)據(jù)流即可背桐,便可實(shí)現(xiàn)Component一系列狀態(tài)的改變,因?yàn)樗羞壿嫴灰蕾囃獠坎踝幔心壳霸揅omponent不對(duì)外暴露任何狀態(tài)和view链峭。實(shí)現(xiàn)了模塊內(nèi)的數(shù)據(jù)流和高內(nèi)聚。
模塊內(nèi)數(shù)據(jù)流可以大幅度簡(jiǎn)化代碼又沾,避免某種程度上對(duì)view直接操作所造成的混亂弊仪,例如異常處理方法

private fun handleException(throwable: Throwable?) {
        //錯(cuò)誤處理時(shí)候的卡片顯示狀況
        throwable?.let {
            Logger.e(throwable, "主頁(yè)圖書(shū)館模塊錯(cuò)誤")
            when (throwable) {
                is HttpException -> {
                    try {
                        val errorJson = throwable.response().errorBody()!!.string()
                        val errJsonObject = JSONObject(errorJson)
                        val errcode = errJsonObject.getInt("error_code")
                        val errmessage = errJsonObject.getString("message")
                        loadingState.value = WARNING
                        message.value = errmessage
                    } catch (e: IOException) {
                        e.printStackTrace()
                    } catch (e: JSONException) {
                        e.printStackTrace()
                    }

                }
                is SocketTimeoutException -> {
                    loadingState.value = WARNING
                    this.message.value = "網(wǎng)絡(luò)超時(shí)...很絕望"
                }
                else -> {
                    loadingState.value = WARNING
                    this.message.value = "粗線蜜汁錯(cuò)誤"
                }
            }
        }
    }

在收到相關(guān)錯(cuò)誤碼的時(shí)候熙卡,修改state和message的觀測(cè)值,相關(guān)的數(shù)據(jù)流會(huì)根據(jù)最初的綁定關(guān)系自動(dòng)通知到相關(guān)的view
比如說(shuō)loadingstate的觀測(cè):

        loadingState.bind(lifecycleOwner) { state ->
            when (state) {
                PROGRESSING -> {
                    stateImage.visibility = View.INVISIBLE
                    stateProgressBar.visibility = View.VISIBLE
                    message.value = "正在刷新"

                }
                OK -> {
                    stateImage.visibility = View.VISIBLE
                    stateProgressBar.visibility = View.INVISIBLE
                    Glide.with(context).load(R.drawable.lib_ok).into(stateImage)

                }
                WARNING -> {
                    stateImage.visibility = View.VISIBLE
                    stateProgressBar.visibility = View.INVISIBLE
                    Glide.with(context).load(R.drawable.lib_warning).into(stateImage)


                }
            }
        }

最近更新的

這個(gè)架構(gòu)比較適合的場(chǎng)景就是励饵,多個(gè)業(yè)務(wù)模塊作為Card出現(xiàn)的時(shí)候驳癌。(或者說(shuō)是Feed流里面的item,或者是你喜歡使用Recyclerview作為頁(yè)面組件的容器)等等... 對(duì)于單頁(yè)場(chǎng)景役听,其實(shí)一頁(yè)就可以認(rèn)為是一個(gè)Component颓鲜,在頁(yè)面的內(nèi)部管理可觀察數(shù)據(jù)流即可。
架構(gòu)不是死的典予,思維也不是甜滨。大家還是要根據(jù)自己的業(yè)務(wù)場(chǎng)景適當(dāng)發(fā)揮啊~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瘤袖,隨后出現(xiàn)的幾起案子衣摩,更是在濱河造成了極大的恐慌,老刑警劉巖孽椰,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昭娩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡黍匾,警方通過(guò)查閱死者的電腦和手機(jī)栏渺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)锐涯,“玉大人磕诊,你說(shuō)我怎么就攤上這事∥齐纾” “怎么了霎终?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)升薯。 經(jīng)常有香客問(wèn)我莱褒,道長(zhǎng),這世上最難降的妖魔是什么涎劈? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任广凸,我火速辦了婚禮,結(jié)果婚禮上蛛枚,老公的妹妹穿的比我還像新娘谅海。我一直安慰自己,他們只是感情好蹦浦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布扭吁。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪侥袜。 梳的紋絲不亂的頭發(fā)上蝌诡,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音系馆,去河邊找鬼送漠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛由蘑,可吹牛的內(nèi)容都是我干的闽寡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼尼酿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼爷狈!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起裳擎,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涎永,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后鹿响,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體羡微,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年惶我,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妈倔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绸贡,死狀恐怖盯蝴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情听怕,我是刑警寧澤捧挺,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站尿瞭,受9級(jí)特大地震影響闽烙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜声搁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一黑竞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酥艳,春花似錦、人聲如沸爬骤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)霞玄。三九已至骤铃,卻和暖如春拉岁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背惰爬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工喊暖, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人撕瞧。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓陵叽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親丛版。 傳聞我的和親對(duì)象是個(gè)殘疾皇子巩掺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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