我的Kotlin 学习之路(六)Kotlin之coroutines 框架的使用

Coroutine -> 协程
不同于线程,协程不占用CPU,它只占用内存来处理耗时操作。Coroutine的原理有大牛的视频已经介绍了,我就不搬门弄斧了,这一章主要讲讲Kotlinx.coroutines 这个库的使用方法,有关于这个的中文讲解还不是很多!

我们依旧使用官方文档中加载google天气的例子来代替耗时操作

一、导入框架
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.16'

二、Request类

/**
 * 网络请求类
 * Created by zr on 2017/5/23.
 */
class Request (val url:String){

    fun run():String{
        Log.e(javaClass.simpleName,"*******************************************")
        Log.e(javaClass.simpleName,"url = "+url)

        var forecastJsonUrl = URL(url).readText()

        Log.e(javaClass.simpleName,"json = "+forecastJsonUrl)
        return forecastJsonUrl
    }
}

三、上最简单的协程


fun coroution(){
        launch(CommonPool){ //协程
            Request(url).run() //耗时操作在CommonPool线程池中
        }
        Log.e(TAG, "开始请求")
    }


结果
06-27 13:01:58.928 16919-16919/test.futsal.com.mykotlin E/MainActivity: 开始请求
06-27 13:01:58.928 16919-19406/test.futsal.com.mykotlin E/Request: *******************************************
06-27 13:01:58.928 16919-19406/test.futsal.com.mykotlin E/Request: url = http://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=metric&cnt=7&APPID=15646a06818f61f7b8d7823ca833e1ce&id=2038349
06-27 13:01:59.936 16919-19406/test.futsal.com.mykotlin E/Request: json = {"city":{"id":2038349,"name":"Beijing Shi","coord":{"lon":116.3971,"lat":39.9169},"country":"CN","population":0},"cod":"200","message":10.1232268,"cnt":7,"list":[{"dt":1498536000,"temp":{"day":31,"min":23.42,"max":33.85,"night":23.42,"eve":33.61,"morn":31},"pressure":996.6,"humidity":64,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":1.83,"deg":141,"clouds":0},{"dt":1498622400,"temp":{"day":30.6,"min":19.7,"max":33.27,"night":26.23,"eve":32.46,"morn":19.7},"pressure":996.96,"humidity":61,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":2.06,"deg":172,"clouds":0},{"dt":1498708800,"temp":{"day":30.33,"min":21.67,"max":33.34,"night":23.4,"eve":32.87,"morn":21.67},"pressure":997.1,"humidity":62,"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"speed":1.63,"deg":146,"clouds":48},{"dt":1498795200,"temp":{"day":32.12,"min":19.89,"max":32.12,"night":19.89,"eve":26.48,"morn":25.23},"pressure":966.77,"humidity":0,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":1.62,"deg":178,"clouds":27},{"dt":1498881600,"temp":{"day":31.48,"min":19.94,"max":31.48,"night":19.94,"eve":26.16,"morn":24.87},"pressure":966.47,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.54,"deg":167,"clouds":43},{"dt":1498968000,"temp":{"day":32.06,"min":20.86,"max":32.06,"night":20.86,"eve":27.1,"morn":25.03},"pressure":965.1,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.05,"deg":119,"clouds":13,"rain":0.38},{"dt":1499054400,"temp":{"day":29.36,"min":21.53,"max":29.36,"night":21.53,"eve":25.73,"morn":24.72},"pressure":965.4,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.35,"deg":136,"clouds":13,"rain":2.03}]}



CommonPool 是 Coroutine的分发类的对象

/*
 * Copyright 2016-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package kotlinx.coroutines.experimental

import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.experimental.CoroutineContext

/**
 * Represents common pool of shared threads as coroutine dispatcher for compute-intensive tasks.
 * It uses [java.util.concurrent.ForkJoinPool] when available, which implements efficient work-stealing algorithm for its queues, so every
 * coroutine resumption is dispatched as a separate task even when it already executes inside the pool.
 * When available, it wraps `ForkJoinPool.commonPool` and provides a similar shared pool where not.
 */
object CommonPool : CoroutineDispatcher() {
    private var usePrivatePool = false

    @Volatile
    private var _pool: ExecutorService? = null

    private inline fun <T> Try(block: () -> T) = try { block() } catch (e: Throwable) { null }

    private fun createPool(): ExecutorService {
        val fjpClass = Try { Class.forName("java.util.concurrent.ForkJoinPool") }
            ?: return createPlainPool()
        if (!usePrivatePool) {
            Try { fjpClass.getMethod("commonPool")?.invoke(null) as? ExecutorService }
                ?.let { return it }
        }
        Try { fjpClass.getConstructor(Int::class.java).newInstance(defaultParallelism()) as? ExecutorService }
            ?. let { return it }
        return createPlainPool()
    }

    private fun createPlainPool(): ExecutorService {
        val threadId = AtomicInteger()
        return Executors.newFixedThreadPool(defaultParallelism()) {
            Thread(it, "CommonPool-worker-${threadId.incrementAndGet()}").apply { isDaemon = true }
        }
    }

    private fun defaultParallelism() = (Runtime.getRuntime().availableProcessors() - 1).coerceAtLeast(1)

    @Synchronized
    private fun getOrCreatePoolSync(): ExecutorService =
        _pool ?: createPool().also { _pool = it }

    override fun dispatch(context: CoroutineContext, block: Runnable) =
        (_pool ?: getOrCreatePoolSync()).execute(block)

    // used for tests
    @Synchronized
    internal fun usePrivatePool() {
        shutdownAndRelease(0)
        usePrivatePool = true
    }

    // used for tests
    @Synchronized
    internal fun shutdownAndRelease(timeout: Long) {
        _pool?.apply {
            shutdown()
            if (timeout > 0)
                awaitTermination(timeout, TimeUnit.MILLISECONDS)
            _pool = null
        }
        usePrivatePool = false
    }

    override fun toString(): String = "CommonPool"
}

四、在主线程中操作

fun coroution() = runBlocking<Unit> { //runBlocking代表在主线程中
        launch(CommonPool){
            Request(url).run()
        }
        Log.e(TAG, "开始请求")
    }

五、带返回值的协程

fun coroution()= runBlocking<Unit> {
        val job = async(CommonPool){ //async 替代了launch (defer已过时)
            Request(url).run()
        }
        job.join() // async()结束后才进行下面的代码,否则挂起等待
        
        var result:String = job.await() //await()获得async方法的返回值
        Log.e(TAG,"result = $result")

        result?.let {
            var forecastResult: ResponseClasses.ForecastResult
            forecastResult = Gson().fromJson(result, ResponseClasses.ForecastResult::class.java)
            longToast("成功")
            toolbar.title = forecastResult.city.name
            tv_main.text = ""
        }

    }


结果
06-27 13:17:19.214 21646-22256/test.futsal.com.mykotlin E/Request: *******************************************
06-27 13:17:19.215 21646-22256/test.futsal.com.mykotlin E/Request: url = http://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=metric&cnt=7&APPID=15646a06818f61f7b8d7823ca833e1ce&id=2038349
06-27 13:17:20.213 21646-22256/test.futsal.com.mykotlin E/Request: json = {"city":{"id":2038349,"name":"Beijing Shi","coord":{"lon":116.3971,"lat":39.9169},"country":"CN","population":0},"cod":"200","message":11.058764,"cnt":7,"list":[{"dt":1498536000,"temp":{"day":31,"min":23.42,"max":33.85,"night":23.42,"eve":33.61,"morn":31},"pressure":996.6,"humidity":64,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":1.83,"deg":141,"clouds":0},{"dt":1498622400,"temp":{"day":30.6,"min":19.7,"max":33.27,"night":26.23,"eve":32.46,"morn":19.7},"pressure":996.96,"humidity":61,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":2.06,"deg":172,"clouds":0},{"dt":1498708800,"temp":{"day":30.33,"min":21.67,"max":33.34,"night":23.4,"eve":32.87,"morn":21.67},"pressure":997.1,"humidity":62,"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"speed":1.63,"deg":146,"clouds":48},{"dt":1498795200,"temp":{"day":32.12,"min":19.89,"max":32.12,"night":19.89,"eve":26.48,"morn":25.23},"pressure":966.77,"humidity":0,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":1.62,"deg":178,"clouds":27},{"dt":1498881600,"temp":{"day":31.48,"min":19.94,"max":31.48,"night":19.94,"eve":26.16,"morn":24.87},"pressure":966.47,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.54,"deg":167,"clouds":43},{"dt":1498968000,"temp":{"day":32.06,"min":20.86,"max":32.06,"night":20.86,"eve":27.1,"morn":25.03},"pressure":965.1,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.05,"deg":119,"clouds":13,"rain":0.38},{"dt":1499054400,"temp":{"day":29.36,"min":21.53,"max":29.36,"night":21.53,"eve":25.73,"morn":24.72},"pressure":965.4,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.35,"deg":136,"clouds":13,"rain":2.03}]}
06-27 13:17:20.214 21646-21646/test.futsal.com.mykotlin E/MainActivity: result = {"city":{"id":2038349,"name":"Beijing Shi","coord":{"lon":116.3971,"lat":39.9169},"country":"CN","population":0},"cod":"200","message":11.058764,"cnt":7,"list":[{"dt":1498536000,"temp":{"day":31,"min":23.42,"max":33.85,"night":23.42,"eve":33.61,"morn":31},"pressure":996.6,"humidity":64,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":1.83,"deg":141,"clouds":0},{"dt":1498622400,"temp":{"day":30.6,"min":19.7,"max":33.27,"night":26.23,"eve":32.46,"morn":19.7},"pressure":996.96,"humidity":61,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":2.06,"deg":172,"clouds":0},{"dt":1498708800,"temp":{"day":30.33,"min":21.67,"max":33.34,"night":23.4,"eve":32.87,"morn":21.67},"pressure":997.1,"humidity":62,"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"speed":1.63,"deg":146,"clouds":48},{"dt":1498795200,"temp":{"day":32.12,"min":19.89,"max":32.12,"night":19.89,"eve":26.48,"morn":25.23},"pressure":966.77,"humidity":0,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":1.62,"deg":178,"clouds":27},{"dt":1498881600,"temp":{"day":31.48,"min":19.94,"max":31.48,"night":19.94,"eve":26.16,"morn":24.87},"pressure":966.47,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.54,"deg":167,"clouds":43},{"dt":1498968000,"temp":{"day":32.06,"min":20.86,"max":32.06,"night":20.86,"eve":27.1,"morn":25.03},"pressure":965.1,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.05,"deg":119,"clouds":13,"rain":0.38},{"dt":1499054400,"temp":{"day":29.36,"min":21.53,"max":29.36,"night":21.53,"eve":25.73,"morn":24.72},"pressure":965.4,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.35,"deg":136,"clouds":13,"rain":2.03}]}



六、取消

fun coroution()= runBlocking<Unit> {
        var result:String? = null
        val job = launch(CommonPool){
            result = Request(url).run()
        }
    job.cancel(Exception("Activity is onPause")) //取消并告知原因
    job.join()
    Log.e(TAG,"result = $result")
        result?.let {
            var forecastResult: ResponseClasses.ForecastResult
            forecastResult = Gson().fromJson(result, ResponseClasses.ForecastResult::class.java)
            longToast("成功")
            toolbar.title = forecastResult.city.name
            
            tv_main.text = ""
        }
    }

取消后的异常

QQ图片20170627101735.png

七、async(CommonPool)的构造方法

/**
 * Creates new coroutine and returns its future result as an implementation of [Deferred].
 *
 * The running coroutine is cancelled when the resulting object is [cancelled][Job.cancel].
 * The [context] for the new coroutine must be explicitly specified.
 * See [CoroutineDispatcher] for the standard [context] implementations that are provided by `kotlinx.coroutines`.
 * The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
 * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
 *
 * By default, the coroutine is immediately scheduled for execution.
 * Other options can be specified via `start` parameter. See [CoroutineStart] for details.
 * An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case,,
 * the resulting [Deferred] is created in _new_ state. It can be explicitly started with [start][Job.start]
 * function and will be started implicitly on the first invocation of [join][Job.join] or [await][Deferred.await].
 *
 * @param context context of the coroutine
 * @param start coroutine start option
 * @param block the coroutine code
 */
public fun <T> async(
    context: CoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.initParentJob(context[Job])
    start(block, coroutine, coroutine)
    return coroutine
}

试试 第二个参数 start: CoroutineStart
async(CommonPool, CoroutineStart.LAZY)
async(CommonPool, CoroutineStart.ATOMIC)
async(CommonPool, CoroutineStart.UNDISPATCHED)
再看CoroutineStart类

/*
 * Copyright 2016-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package kotlinx.coroutines.experimental

import kotlinx.coroutines.experimental.CoroutineStart.*
import kotlinx.coroutines.experimental.intrinsics.startCoroutineUndispatched
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.startCoroutine

/**
 * Defines start option for coroutines builders.
 * It is used in `start` parameter of [launch], [async], and [actor][kotlinx.coroutines.experimental.channels.actor]
 * coroutine builder functions.
 *
 * The summary of coroutine start options is:
 * * [DEFAULT] -- immediately schedules coroutine for execution according to its context;
 * * [LAZY] -- starts coroutine lazily, only when it is needed;
 * * [ATOMIC] -- atomically (non-cancellably) schedules coroutine for execution according to its context;
 * * [UNDISPATCHED] -- immediately executes coroutine until its first suspension point _in the current thread_.
 */
public enum class CoroutineStart {
    /**
     * Default -- immediately schedules coroutine for execution according to its context.
     *
     * If the [CoroutineDispatcher] of the coroutine context returns `true` from [CoroutineDispatcher.isDispatchNeeded]
     * function as most dispatchers do, then the coroutine code is dispatched for execution later, while the code that
     * invoked the coroutine builder continues execution.
     *
     * Note, that [Unconfined] dispatcher always returns `false` from its [CoroutineDispatcher.isDispatchNeeded]
     * function, so starting coroutine with [Unconfined] dispatcher by [DEFAULT] is the same as using [UNDISPATCHED].
     *
     * If coroutine [Job] is cancelled before it even had a chance to start executing, then it will not start its
     * execution at all, but complete with an exception.
     *
     * Cancellability of coroutine at suspension points depends on the particular implementation details of
     * suspending functions. Use [suspendCancellableCoroutine] to implement cancellable suspending functions.
     */
    DEFAULT,

    /**
     * Starts coroutine lazily, only when it is needed.
     *
     * See the documentation for the corresponding coroutine builders for details:
     * [launch], [async], and [actor][kotlinx.coroutines.experimental.channels.actor].
     *
     * If coroutine [Job] is cancelled before it even had a chance to start executing, then it will not start its
     * execution at all, but complete with an exception.
     */
    LAZY,

    /**
     * Atomically (non-cancellably) schedules coroutine for execution according to its context.
     * This is similar to [DEFAULT], but the coroutine cannot be cancelled before it starts executing.
     *
     * Cancellability of coroutine at suspension points depends on the particular implementation details of
     * suspending functions as in [DEFAULT].
     */
    ATOMIC,

    /**
     * Immediately executes coroutine until its first suspension point _in the current thread_ as if it the
     * coroutine was started using [Unconfined] dispatcher. However, when coroutine is resumed from suspension
     * it is dispatched according to the [CoroutineDispatcher] in its context.
     *
     * This is similar to [ATOMIC] in the sense that coroutine starts executing even if it was already cancelled,
     * but the difference is that it start executing in the same thread.
     *
     * Cancellability of coroutine at suspension points depends on the particular implementation details of
     * suspending functions as in [DEFAULT].
     */
    UNDISPATCHED;

    /**
     * Starts the corresponding block as a coroutine with this coroutine start strategy.
     *
     * * [DEFAULT] uses [startCoroutineCancellable].
     * * [ATOMIC] uses [startCoroutine].
     * * [UNDISPATCHED] uses [startCoroutineUndispatched].
     * * [LAZY] does nothing.
     */
    public operator fun <T> invoke(block: suspend () -> T, completion: Continuation<T>) =
        when (this) {
            CoroutineStart.DEFAULT -> block.startCoroutineCancellable(completion)
            CoroutineStart.ATOMIC -> block.startCoroutine(completion)
            CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(completion)
            CoroutineStart.LAZY -> Unit // will start lazily
        }

    /**
     * Starts the corresponding block with receiver as a coroutine with this coroutine start strategy.
     *
     * * [DEFAULT] uses [startCoroutineCancellable].
     * * [ATOMIC] uses [startCoroutine].
     * * [UNDISPATCHED] uses [startCoroutineUndispatched].
     * * [LAZY] does nothing.
     */
    public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>) =
        when (this) {
            CoroutineStart.DEFAULT -> block.startCoroutineCancellable(receiver, completion)
            CoroutineStart.ATOMIC -> block.startCoroutine(receiver, completion)
            CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
            CoroutineStart.LAZY -> Unit // will start lazily
        }

    /**
     * Returns `true` when [LAZY].
     */
    public val isLazy: Boolean get() = this === LAZY
}


(我英语不好,成人三级一直没过,请包涵,但我不会瞎说的)
DEFAULT -> 立即执行耗时操作
LAZY -> 在async 的 await()时才进行耗时操作 Starts coroutine lazily, only when it is needed.
ATOMIC -> 和DEFAULT很接近,但是不能取消 (non-cancellably)
UNDISPATCHED-> 只在当前线程执行,不切换线程,如果是在主线程,刚才的方法会抛出这样的异常

QQ图片20170627105748.png

我最后的代码

val url = "http://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=metric&cnt=7&APPID=15646a06818f61f7b8d7823ca833e1ce&id=2038349"

fun coroution()= runBlocking<Unit> {
        val result = async(CommonPool){
            Request(url).run()
        }.await()

        Log.e(TAG,"result = $result")
        result?.let {
            var forecastResult: ResponseClasses.ForecastResult
            forecastResult = Gson().fromJson(result, ResponseClasses.ForecastResult::class.java)
            longToast("成功")
            toolbar.title = forecastResult.city.name
            tv_main.text = ""
            //加载adapter的略过
        }

    }

最后的样子

QQ图片20170627123059.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容