即學(xué)即用Android Jetpack - WorkManger

前言

即學(xué)即用Android Jetpack系列Blog的目的是通過(guò)學(xué)習(xí)Android Jetpack完成一個(gè)簡(jiǎn)單的Demo监徘,本文是即學(xué)即用Android Jetpack系列Blog的第六篇。

經(jīng)過(guò)前面幾篇博客的學(xué)習(xí)吧碾,我們的Demo已經(jīng)基本成型凰盔,先上圖:

列表頁(yè)

詳情頁(yè)

喜歡頁(yè)

這里我得提一下,鞋子的數(shù)據(jù)不是從網(wǎng)絡(luò)請(qǐng)求中獲取的倦春,這個(gè)時(shí)候小王就舉手了户敬,那鞋子的數(shù)據(jù)是哪里來(lái)的呢落剪?其實(shí)很簡(jiǎn)單,數(shù)據(jù)是從assets目錄下的json讀取出來(lái)的尿庐,通常情況下著榴,從文件讀取數(shù)據(jù)都不會(huì)放在主線程中執(zhí)行,所以呢,我們Demo中的數(shù)據(jù)初始化當(dāng)然也沒(méi)有在主線程執(zhí)行了,這時(shí)貌虾,就得請(qǐng)出我們今天的主角——WorkManager,它是我們能夠在后臺(tái)執(zhí)行數(shù)據(jù)初始化的原因问麸。

語(yǔ)言:Kotlin
我的Demo:https://github.com/mCyp/Hoo

目錄

一、介紹

友情提示
官方文檔:WorkManager
谷歌實(shí)驗(yàn)室:官方教程
官方案例:android-workmanager
以及強(qiáng)力安利:
WorkManger介紹視頻:中文官方介紹視頻(主要是小姐姐好看~)

1. 定義

通過(guò)一開(kāi)始粗略的介紹钞翔,我們已經(jīng)了解到严卖,WorkManager是用來(lái)執(zhí)行后臺(tái)任務(wù)的,正如官方介紹:

WorkManager, a compatible, flexible and simple library for deferrable background work.
WorkManger是一個(gè)可兼容布轿、靈活和簡(jiǎn)單的延遲后臺(tái)任務(wù)哮笆。

2. 選擇WorkManager的理由

Android中處理后臺(tái)任務(wù)的選擇挺多的,比如Service汰扭、DownloadManager稠肘、AlarmManagerJobScheduler等萝毛,那么選擇WorkManager的理由是什么呢项阴?

  1. 版本兼容性強(qiáng),向后兼容至API 14笆包。
  2. 可以指定約束條件环揽,比如可以選擇必須在有網(wǎng)絡(luò)的條件下執(zhí)行。
  3. 可定時(shí)執(zhí)行也可單次執(zhí)行庵佣。
  4. 監(jiān)聽(tīng)和管理任務(wù)狀態(tài)歉胶。
  5. 多個(gè)任務(wù)可使用任務(wù)鏈。
  6. 保證任務(wù)執(zhí)行巴粪,如當(dāng)前執(zhí)行條件不滿足或者App進(jìn)程被殺死通今,它會(huì)等到下次條件滿足或者App進(jìn)程打開(kāi)后執(zhí)行。
  7. 支持省電模式验毡。

3. 多線程任務(wù)如何選擇衡创?

后臺(tái)任務(wù)會(huì)消耗設(shè)備的系統(tǒng)資源,如若處理不當(dāng)晶通,可能會(huì)造成設(shè)備電量的急劇消耗,給用戶帶來(lái)糟糕的體驗(yàn)哟玷。所以狮辽,選擇正確的后臺(tái)處理方式是每個(gè)開(kāi)發(fā)者應(yīng)當(dāng)注意的一也,如下是官方給的選擇方式:

選擇方式
圖片來(lái)自:官方文檔
關(guān)于一些后臺(tái)任務(wù)的知識(shí),我推薦你閱讀:[譯] 從Service到WorkManager喉脖,很好的一篇文章椰苟。

二、實(shí)戰(zhàn)

本次的實(shí)戰(zhàn)來(lái)自于我上面的介紹的官方例子树叽,最終我將它添加進(jìn)我的Demo里面:

效果

如圖所見(jiàn)舆蝴,我們要做的就是選取一張圖片,將圖片做模糊處理题诵,之后顯示在我們的頭像上洁仗。

第一步 添加依賴

ext.workVersion = "2.0.1"
dependencies {
    // ...省略

    implementation "androidx.work:work-runtime-ktx:$rootProject.workVersion"
}

第二步 自定義Worker

構(gòu)建Worker之前,我們有必要了解一下WorkManger中重要的幾個(gè)類:

作用
Worker 需要繼承Worker性锭,并復(fù)寫doWork()方法赠潦,在doWork()方法中放入你需要在后臺(tái)執(zhí)行的代碼。
WorkRequest 指后臺(tái)工作的請(qǐng)求草冈,你可以在后臺(tái)工作的請(qǐng)求中添加約束條件
WorkManager 真正讓Worker在后臺(tái)執(zhí)行的類

除了這幾個(gè)重要的類她奥,我們?nèi)孕枇私?code>WorkManger的執(zhí)行流程,以便于我們能夠更好的使用:

WorkerManger
圖片來(lái)自:谷歌工程師的博客
主要分為三步:

  1. WorkRequest生成以后怎棱,Internal TaskExecutor將它存入WorkManger的數(shù)據(jù)庫(kù)中哩俭,這也是為什么即使在程序退出之后,WorkManger也能保證后臺(tái)任務(wù)在下次啟動(dòng)后條件滿足的情況下執(zhí)行拳恋。
  2. 當(dāng)約束條件滿足的情況下携茂,Internal TaskExecutor告訴WorkFactory生成Worker
  3. 后臺(tái)任務(wù)Worker執(zhí)行诅岩。

下面開(kāi)始我們的構(gòu)建Worker讳苦,為了生成一張模糊圖片,我們需要:清除之前的緩存路徑吩谦、圖片模糊的處理和圖片的生成鸳谜。我們可以將這三個(gè)步驟分為三個(gè)后臺(tái)任務(wù),三個(gè)后臺(tái)任務(wù)又分別涉及到無(wú)變量情況式廷、往外傳參和讀取參數(shù)這三種情況:

通常情況

/**
 * 清理臨時(shí)文件的Worker
 */
class CleanUpWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
    private val TAG by lazy {
        this::class.java.simpleName
    }

    override fun doWork(): Result {
        // ... 省略

        return try {
            // 刪除邏輯
            // ...代碼省略
            // 成功時(shí)返回
            Result.success()
        } catch (exception: Exception) {
            // 失敗時(shí)返回
            Result.failure()
        }
    }
}

輸出參數(shù)

/**
 * 模糊處理的Worker
 */
class BlurWorker(context: Context, params: WorkerParameters) : Worker(context, params) {

    override fun doWork(): Result {
        //...
        return try {
            // 圖片處理邏輯
            // 圖片處理邏輯省略...

            // 將路徑輸出
            val outPutData = workDataOf(KEY_IMAGE_URI to outputUri.toString())
            makeStatusNotification("Output is $outputUri", context)
            Result.success(outPutData)
        }catch (throwable: Throwable){
            Result.failure()
        }
    }
}

讀取參數(shù)

/**
 * 存儲(chǔ)照片的Worker
 */
class SaveImageToFileWorker(ctx:Context,parameters: WorkerParameters):Worker(ctx,parameters) {
    //...

    override fun doWork(): Result {
        //...
        return try {
            // 獲取從外部傳入的參數(shù)
            val resourceUri = inputData.getString(KEY_IMAGE_URI)
            //... 存儲(chǔ)邏輯
            val imageUrl = MediaStore.Images.Media.insertImage(
                resolver, bitmap, Title, dateFormatter.format(Date()))
            if (!imageUrl.isNullOrEmpty()) {
                val output = workDataOf(KEY_IMAGE_URI to imageUrl)
                Result.success(output)
            } else {
                // 失敗時(shí)返回
                Result.failure()
            }
        } catch (exception: Exception) {
            // 異常時(shí)返回
            Result.failure()
        }
    }
}

第三步 創(chuàng)建WorkManger

這一步還是挺簡(jiǎn)單的咐扭,MeModel中單例獲取:

class MeModel(val userRepository: UserRepository) : ViewModel() {
    //...
    private val workManager = WorkManager.getInstance()
    // ...
}

第四步 構(gòu)建WorkRequest

WorkRequest可以分為兩類:

名稱 作用
PeriodicWorkRequest 多次滑废、定時(shí)執(zhí)行的任務(wù)請(qǐng)求蝗肪,不支持任務(wù)鏈
OneTimeWorkRequest 只執(zhí)行一次的任務(wù)請(qǐng)求,支持任務(wù)鏈
1. 執(zhí)行一個(gè)任務(wù)

我們以OneTimeWorkRequest為例蠕趁,如果我們只有一個(gè)任務(wù)請(qǐng)求薛闪,這樣寫就行:

        val request = OneTimeWorkRequest.from(CleanUpWorker::class.java)
        workManager.enqueue(request)
2. 執(zhí)行多個(gè)任務(wù)

但是,這樣寫顯然不適合我們當(dāng)前的業(yè)務(wù)需求俺陋,因?yàn)槲覀冇腥齻€(gè)Worker豁延,并且三個(gè)Worker有先后順序昙篙,因此我們可以使用任務(wù)鏈:

        // 多任務(wù)按順序執(zhí)行
        workManager.beginWith(
            mutableListOf(
                OneTimeWorkRequest.from(CleanUpWorker::class.java)
            ))
            .then(OneTimeWorkRequestBuilder<BlurWorker>().setInputData(createInputDataForUri()).build())
            .then(OneTimeWorkRequestBuilder<SaveImageToFileWorker>().build())
            .enqueue()

等等,假設(shè)我多次點(diǎn)擊圖片更換頭像诱咏,提交多次請(qǐng)求苔可,由于網(wǎng)絡(luò)等原因(雖然我們的Demo沒(méi)有網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求部分),最后返回的很有可能不是我們最后一次請(qǐng)求的圖片袋狞,這顯然是糟糕的焚辅,不過(guò),WorkManger能夠滿足你的需求苟鸯,保證任務(wù)的唯一性:

        // 多任務(wù)按順序執(zhí)行
        workManager.beginUniqueWork(
            IMAGE_MANIPULATION_WORK_NAME, // 任務(wù)名稱
            ExistingWorkPolicy.REPLACE, // 任務(wù)相同的執(zhí)行策略 分為REPLACE同蜻,KEEP,APPEND
            mutableListOf(
                OneTimeWorkRequest.from(CleanUpWorker::class.java) 
            ))
            .then(OneTimeWorkRequestBuilder<BlurWorker>().setInputData(createInputDataForUri()).build())
            .then(OneTimeWorkRequestBuilder<SaveImageToFileWorker>().build())
            .enqueue()

無(wú)順序多任務(wù)
這里有必要提一下倔毙,如果并行執(zhí)行沒(méi)有順序的多個(gè)任務(wù)埃仪,無(wú)論是beginUniqueWork還是beginWith方法都可以接受一個(gè)List<OneTimeWorkRequest>

3. 使用約束

假設(shè)我們需要將生成的圖片上傳到服務(wù)端陕赃,并且需要將圖片存儲(chǔ)到本地卵蛉,顯然,我們需要設(shè)備網(wǎng)絡(luò)條件良好并且有存儲(chǔ)空間么库,這時(shí)候傻丝,我們可以給WorkRequest指明約束條件:

        // 構(gòu)建約束條件
        val constraints = Constraints.Builder()
            .setRequiresBatteryNotLow(true) // 非電池低電量
            .setRequiredNetworkType(NetworkType.CONNECTED) // 網(wǎng)絡(luò)連接的情況
            .setRequiresStorageNotLow(true) // 存儲(chǔ)空間足
            .build()

        // 儲(chǔ)存照片
        val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
            .setConstraints(constraints)
            .addTag(TAG_OUTPUT)
            .build()
        continuation = continuation.then(save)

可以指明的約束條件有:電池電量充電诉儒、網(wǎng)絡(luò)條件葡缰、存儲(chǔ)延遲等,具體的可以使用的時(shí)候查看接口忱反。

以下則是我們Demo中的具體使用:

class MeModel(val userRepository: UserRepository) : ViewModel() {
    //... 
    private val workManager = WorkManager.getInstance()
    val user = userRepository.findUserById(AppPrefsUtils.getLong(BaseConstant.SP_USER_ID))

    internal fun applyBlur(blurLevel: Int) {
       //... 創(chuàng)建任務(wù)鏈

        var continuation = workManager
            .beginUniqueWork(
                IMAGE_MANIPULATION_WORK_NAME,
                ExistingWorkPolicy.REPLACE,
                OneTimeWorkRequest.from(CleanUpWorker::class.java)
            )

        for (i in 0 until blurLevel) {
            val builder = OneTimeWorkRequestBuilder<BlurWorker>()
            if (i == 0) {
                builder.setInputData(createInputDataForUri())
            }
            continuation = continuation.then(builder.build())
        }

        // 構(gòu)建約束條件
        val constraints = Constraints.Builder()
            .setRequiresBatteryNotLow(true) // 非電池低電量
            .setRequiredNetworkType(NetworkType.CONNECTED) // 網(wǎng)絡(luò)連接的情況
            .setRequiresStorageNotLow(true) // 存儲(chǔ)空間足
            .build()

        // 儲(chǔ)存照片
        val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
            .setConstraints(constraints)
            .addTag(TAG_OUTPUT)
            .build()
        continuation = continuation.then(save)

        continuation.enqueue()
    }

    private fun createInputDataForUri(): Data {
        val builder = Data.Builder()
        imageUri?.let {
            builder.putString(KEY_IMAGE_URI, imageUri.toString())
        }
        return builder.build()
    }

    //... 省略
}

第四步 取消任務(wù)

如果想取消所有的任務(wù)workManager.cancelAllWork()泛释,當(dāng)然如果想取消我們上面執(zhí)行的唯一任務(wù),需要我們上面的唯一任務(wù)名:

class MeModel(val userRepository: UserRepository) : ViewModel() {
    fun cancelWork() {
        workManager.cancelUniqueWork(IMAGE_MANIPULATION_WORK_NAME)
    }
}

第五步 觀察任務(wù)狀態(tài)

任務(wù)狀態(tài)的變化過(guò)程:

狀態(tài)觀測(cè)
圖片來(lái)自于:How to use WorkManager with RxJava
其中温算,SUCCEEDED怜校、FAILEDCANCELLED都屬于任務(wù)已經(jīng)完成。觀察任務(wù)狀態(tài)需要使用到LiveData

class MeModel(val userRepository: UserRepository) : ViewModel() {
    //... 省略
    private val workManager = WorkManager.getInstance()
    val user = userRepository.findUserById(AppPrefsUtils.getLong(BaseConstant.SP_USER_ID))

    init {
        outPutWorkInfos = workManager.getWorkInfosByTagLiveData(TAG_OUTPUT)
    }

    // ...省略
}

當(dāng)圖片處理的時(shí)候注竿,程序彈出加載框茄茁,圖片處理完成,程序會(huì)將圖片路徑保存到User里的headImage并存儲(chǔ)到數(shù)據(jù)庫(kù)中巩割,任務(wù)狀態(tài)觀測(cè)參見(jiàn)MeFragment中的onSubscribeUi方法:

class MeFragment : Fragment() {
    private val TAG by lazy { MeFragment::class.java.simpleName }
    // 選擇圖片的標(biāo)識(shí)
    private val REQUEST_CODE_IMAGE = 100
    // 加載框
    private val sweetAlertDialog: SweetAlertDialog by lazy {
        SweetAlertDialog(requireContext(), SweetAlertDialog.PROGRESS_TYPE)
            .setTitleText("頭像")
            .setContentText("更新中...")
            /*
            .setCancelButton("取消") {
                model.cancelWork()
                sweetAlertDialog.dismiss()
            }*/
    }

    // MeModel懶加載
    private val model: MeModel by viewModels {
        CustomViewModelProvider.providerMeModel(requireContext())
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Data Binding
        val binding: FragmentMeBinding = FragmentMeBinding.inflate(inflater, container, false)
        initListener(binding)
        onSubscribeUi(binding)
        return binding.root
    }

    /**
     * 初始化監(jiān)聽(tīng)器
     */
    private fun initListener(binding: FragmentMeBinding) {
        binding.ivHead.setOnClickListener {
            // 選擇處理的圖片
            val chooseIntent = Intent(
                Intent.ACTION_PICK,
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI
            )
            startActivityForResult(chooseIntent, REQUEST_CODE_IMAGE)
        }
    }

    /**
     * Binding綁定
     */
    private fun onSubscribeUi(binding: FragmentMeBinding) {
        model.user.observe(this, Observer {
            binding.user = it
        })

        // 任務(wù)狀態(tài)的觀測(cè)
        model.outPutWorkInfos.observe(this, Observer {
            if (it.isNullOrEmpty())
                return@Observer

            val state = it[0]
            if (state.state.isFinished) {
                // 更新頭像
                val outputImageUri = state.outputData.getString(KEY_IMAGE_URI)
                if (!outputImageUri.isNullOrEmpty()) {
                    model.setOutputUri(outputImageUri)
                }
                sweetAlertDialog.dismiss()
            }
        })
    }

    /**
     * 圖片選擇完成的回調(diào)
     */
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
                REQUEST_CODE_IMAGE -> data?.let { handleImageRequestResult(data) }
                else -> Log.d(TAG, "Unknown request code.")
            }
        } else {
            Log.e(TAG, String.format("Unexpected Result code %s", resultCode))
        }
    }

    /**
     * 圖片選擇完成的處理
     */
    private fun handleImageRequestResult(intent: Intent) {
        // If clipdata is available, we use it, otherwise we use data
        val imageUri: Uri? = intent.clipData?.let {
            it.getItemAt(0).uri
        } ?: intent.data

        if (imageUri == null) {
            Log.e(TAG, "Invalid input image Uri.")
            return
        }

        sweetAlertDialog.show()
        // 圖片模糊處理
        model.setImageUri(imageUri.toString())
        model.applyBlur(3)
    }
}

寫完以后裙顽,動(dòng)圖的效果就會(huì)出現(xiàn)了。

三宣谈、更多

選擇適合自己的Worker

谷歌提供了四種Worker給我們使用愈犹,分別為:自動(dòng)運(yùn)行在后臺(tái)線程的Worker、結(jié)合協(xié)程的CoroutineWorker蒲祈、結(jié)合RxJava2RxWorker和以上三個(gè)類的基類的ListenableWorker甘萧。

由于本文使用的Kotlin萝嘁,故打算簡(jiǎn)單的介紹CoroutineWorker梆掸,其他的可以自行探索扬卷。

我們使用ShoeWorker來(lái)從文件中讀取鞋子的數(shù)據(jù)并完成數(shù)據(jù)庫(kù)的插入工作,使用方式基本與Worker一致:

class ShoeWorker(
    context: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {

    private val TAG by lazy {
        ShoeWorker::class.java.simpleName
    }

    // 指定Dispatchers
    override val coroutineContext: CoroutineDispatcher
        get() = Dispatchers.IO

    override suspend fun doWork(): Result = coroutineScope {
        try {
            applicationContext.assets.open("shoes.json").use {
                JsonReader(it.reader()).use {
                    val shoeType = object : TypeToken<List<Shoe>>() {}.type
                    val shoeList: List<Shoe> = Gson().fromJson(it, shoeType)

                    val shoeDao = RepositoryProvider.providerShoeRepository(applicationContext)
                    shoeDao.insertShoes(shoeList)
                    for (i in 0..2) {
                        for (shoe in shoeList) {
                            shoe.id += shoeList.size
                        }
                        shoeDao.insertShoes(shoeList)
                    }
                    Result.success()
                }

            }
        } catch (ex: Exception) {
            Log.e(TAG, "Error seeding database", ex)
            Result.failure()
        }
    }
}

四酸钦、總結(jié)

總結(jié)

可以發(fā)現(xiàn)怪得,大部分的后臺(tái)任務(wù)處理,WorkManager都可以勝任卑硫,這也是我們需要學(xué)習(xí)WorkManger的原因徒恋。本次WorkManger學(xué)習(xí)完畢,本人水平有限欢伏,難免有誤入挣,歡迎指正。
Over~

參考文章:

《Android Jetpack - 使用 WorkManager 管理后臺(tái)任務(wù)》
《[譯] 從Service到WorkManager》
《官方文檔:Guide to background processing》
《谷歌實(shí)驗(yàn)室》
《官方文檔:WorkManager》
《WorkManager Basics》

??如果覺(jué)得本文不錯(cuò)硝拧,可以查看Android Jetpack系列的其他文章:

第一篇:《即學(xué)即用Android Jetpack - Navigation》
第二篇:《即學(xué)即用Android Jetpack - Data Binding》
第三篇:《即學(xué)即用Android Jetpack - ViewModel & LiveData》
第四篇:《即學(xué)即用Android Jetpack - Room》
第五篇:《即學(xué)即用Android Jetpack - Paging》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末径筏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子障陶,更是在濱河造成了極大的恐慌滋恬,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抱究,死亡現(xiàn)場(chǎng)離奇詭異恢氯,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)鼓寺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門勋拟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人妈候,你說(shuō)我怎么就攤上這事敢靡。” “怎么了州丹?”我有些...
    開(kāi)封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵醋安,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我墓毒,道長(zhǎng)吓揪,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任所计,我火速辦了婚禮柠辞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘主胧。我一直安慰自己叭首,他們只是感情好习勤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著焙格,像睡著了一般图毕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上眷唉,一...
    開(kāi)封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天予颤,我揣著相機(jī)與錄音,去河邊找鬼冬阳。 笑死蛤虐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肝陪。 我是一名探鬼主播驳庭,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼氯窍!你這毒婦竟也來(lái)了饲常?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤荞驴,失蹤者是張志新(化名)和其女友劉穎不皆,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體熊楼,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡霹娄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鲫骗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片犬耻。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖执泰,靈堂內(nèi)的尸體忽然破棺而出枕磁,到底是詐尸還是另有隱情,我是刑警寧澤术吝,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布计济,位于F島的核電站,受9級(jí)特大地震影響排苍,放射性物質(zhì)發(fā)生泄漏沦寂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一淘衙、第九天 我趴在偏房一處隱蔽的房頂上張望传藏。 院中可真熱鬧,春花似錦、人聲如沸毯侦。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)侈离。三九已至试幽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間霍狰,已是汗流浹背抡草。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工饰及, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蔗坯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓燎含,卻偏偏與公主長(zhǎng)得像宾濒,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屏箍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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