Retrofit 源码深入分析 —— RxJava 和 协程的支持

一、概述

在上一篇 Retrofit 源码深入分析 —— Call 对象的诞生与请求 的文章中我们基本把 Retrofit 从如何构建一个请求到返回响应的整个过程都梳理了一遍,对 Retrofit 的基本工作原理有了一个完整的了解。按照文章的完成度来说,上一篇文章基本把 Retrofit 讲的差不多了,但笔者还是想把日常普遍使用的几种方式都梳理一遍,让两篇文章对 Retrofit 的分析更加完整。

本篇文章其实按理来说应该整合到上一篇中,但这样让本就有点长的文章变得更长,对于阅读来说可能会很累,而对于笔者来说无论是写还是校对也很累,索性单开一篇。而且也并不会妨碍彼此的连贯性。

二、Retrofit 对 RxJava 的支持

让我们看看如何用 RxJava 的方式进行请求,还是用官方 sample 的例子

  • 添加对 RxJava 的支持
addCallAdapterFactory(RxJavaCallAdapterFactory.create())
  • 声明一个返回类型为 Obervable 的接口方法
public interface GitHub {
    @GET("/repos/{owner}/{repo}/contributors")
    Observable<List<Contributor>> contributors(@Path("owner") String owner,@Path("repo") String repo);
}
  • 创建接口服务
GitHub github = retrofit.create(GitHub.class);
  • 发起一个请求
 Observable<List<Contributor>> observable = github.contributors("square", "retrofit");
 
  observable.
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<List<Contributor>>() {
            @Override
            public void onCompleted() {}

            @Override
            public void onError(Throwable throwable) { }

            @Override
            public void onNext(List<Contributor> contributors) {
                for (Contributor contributor : contributors) {
                    System.out.println(contributor.login + " (" + contributor.contributions + ")");
                }
            }
    });

上述是一个标准的支持 RxJava 的请求步骤,这里与默认的请求最大的区别除了请求的过程不同外,返回的类型由原来的 Call.class 类型变为了 Observable.class 类型也就是一个被观察者对象,所以很明显 RxJavaCallAdapterFactory 内部帮我门做了某种转换,至于注解的解析过程都是一样的,这里不在赘述。

让我们再次延续上篇文章的 6.1 小节部分。回到创建 CallAdapter 的地方,也就是 createCallAdapter 方法,上篇文章对这个方法已经进行了描述,所以废话不多说让我们直接进入 RxJavaCallAdapterFactory 的 get 方法

@Override public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
    Class<?> rawType = getRawType(returnType);
    boolean isSingle = rawType == Single.class;
    boolean isCompletable = rawType == Completable.class;
    if (rawType != Observable.class && !isSingle && !isCompletable) {
      return null;
    }

    if (isCompletable) {
      return new RxJavaCallAdapter(Void.class, scheduler, isAsync, false, true, false, true);
    }

    boolean isResult = false;
    boolean isBody = false;
    Type responseType;
    
    Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
    Class<?> rawObservableType = getRawType(observableType);
    //Model 不可声明为 Retrofit 的Response 类型
    if (rawObservableType == Response.class) {
      if (!(observableType instanceof ParameterizedType)) {
        throw new IllegalStateException("Response must be parameterized"
            + " as Response<Foo> or Response<? extends Foo>");
      }
      responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
    } else if (rawObservableType == Result.class) { //也不可声明为 RxJava 包下的 Result 类型
      if (!(observableType instanceof ParameterizedType)) {
        throw new IllegalStateException("Result must be parameterized"
            + " as Result<Foo> or Result<? extends Foo>");
      }
      responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
      isResult = true;
    } else {
      responseType = observableType;
      isBody = true;
    }

    return new RxJavaCallAdapter(responseType, scheduler, isAsync, isResult, isBody, isSingle,
        false);

RxJavaCallAdapterFactory 的 get 方法的逻辑还是很清晰的,首先创建 RxJavaCallAdapter 的前置条件必须为 Observable、Single 和 Completable 而如果是 Completable 只接创建一个返回类型为 Void 的 adapter, 至于 Single 和 Completable ,前者在 RxJava 中代表只能处理一次事件,即只能发射单个数据或错误事件。而 Completable 正如它的名字,它不负责发送数据,只会处理 Rxjava 的 conComplete 和 onError 事件。

RxJavaCallAdapter 创建完成后就和 Call 对象的诞生流程差不多了。所以让们直接进入其中的 adapt 方法看看 Observable 的真面目

//形参为 OkHttpCall 创建的过程请看上一篇文章
@Override public Object adapt(Call<R> call) {
    OnSubscribe<Response<R>> callFunc = isAsync
        ? new CallEnqueueOnSubscribe<>(call)//异步,将 OkHttpCall  对象传入
        : new CallExecuteOnSubscribe<>(call);//同步,将 OkHttpCall 对象传入

    OnSubscribe<?> func;
    if (isResult) {//声明类型不是的 RxJava 包下的 Result 类型 默认 false
      func = new ResultOnSubscribe<>(callFunc);
    } else if (isBody) { //默认 true
      func = new BodyOnSubscribe<>(callFunc);
    } else {
      func = callFunc;
    }
    //创建一个被观察对象
    Observable<?> observable = Observable.create(func);

    if (scheduler != null) {
      observable = observable.subscribeOn(scheduler);
    }

    if (isSingle) {//返回一个 Single 事件
      return observable.toSingle();
    }
    if (isCompletable) {//返回一个 toCompletable 事件
      return observable.toCompletable();
    }
    return observable;
  }

注释已经进行了详细的解释就不多说了,由于我们使用的是异步请求,所以肯定创建的是 CallEnqueueOnSubscribe 对象,该对象实现了 OnSubscribe 接口并重写了 call 方法,代码如下

@Override public void call(Subscriber<? super Response<T>> subscriber) {
    // Since Call is a one-shot type, clone it for each new subscriber.
    Call<T> call = originalCall.clone();
    final CallArbiter<T> arbiter = new CallArbiter<>(call, subscriber);
    subscriber.add(arbiter);
    subscriber.setProducer(arbiter);

    call.enqueue(new Callback<T>() {
      @Override public void onResponse(Call<T> call, Response<T> response) {
        arbiter.emitResponse(response);
      }

      @Override public void onFailure(Call<T> call, Throwable t) {
        Exceptions.throwIfFatal(t);
        arbiter.emitError(t);
      }
    });
  }

代码并不复杂,可以看到内部还是委托 OkHttpCall 的 enqueue 方法来获取请求的 response ,在经过 GsonConvertAdapter 转换后,将该 Response 发射出去。那么到这里整个 RxJava 的支持过程就清晰了。

首先当我们把接口的返回类型声明为 Observable<T> 类型时,会通过 RxJavaCallAdapterFactory 的 get 方法来帮我们创建一个 RxJavaCallAdapter 对象,RxJavaCallAdapter 的 adapt 方法中会帮我们创建一个被观察者对象返回。而当我们通过如下代码发起一个订阅

observable.
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer());

订阅完成后,CallEnqueueOnSubscribe 对象的 call 方法就会被触发,剩下的就是在 onNext 方法中接收 Resposne 了。

从整体的处理环节来看,除了 RxJava 本身的特性之外,基本的逻辑和默认的 Call<T> 类型的处理是差不多的。因为都是遵循的同一套接口标准。所以如果你不喜欢默认的类型或 Rxjava,完全可以按照 Retorfit 的标准建立一套自己的 callAdapter。

三、Retrofit 对协程的支持

协程作为近几年很火的异步框架,其简便的异步操作方式可以说但凡用过的人没有不喜欢的,其热度在 Android 领域已经有超过 RxJava 的趋势。而 Retrofit 自然不会甘于人后,在 2.6.x版本以上也对协程进行了支持。

如果你还不了解协程,建议先 Google/Baidu 查询一下相关文章了解一番,否则可能无法无法愉快的阅读。

接下来让还是让我们以官方 Sample 为例看看 Retrofit 在 Android 开发中使用协程的基本流程(下面的步骤是 Kotlin 代码)

  • 添加基础依赖

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
//Android 协程依赖库
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
// 包含协程的 Activity lifecycle 扩展
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0"
  • 构建 Retrofit 实例
val API_URL = "https://api.github.com"

val retrofit = Retrofit.Builder()
                .baseUrl(API_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
  • 创建挂起函数
interface Github {
    @GET("/repos/{owner}/{repo}/contributors")
    suspend fun contributors(
        @Path("owner") owner: String,
        @Path("repo") repo: String) : List<Contributor>
}
  • 创建接口服务
val github = retrofit.create(Github::class.java)
  • 开启一个协程进行请求
lifecycleScope.launch{
    val contributors = github.contributors("square", "retrofit")
    for ((login, contributions) in contributors) {
        println("$login ($contributions)")
    }
}

上述流程算是在 Android 中的常用步骤,但在实际使用中 lifecycleScope.launch 会单独封装不会像上面那样单独拿出来使用。

简单了解了上面的流程后,我们来看看 Retrofit 是如何对携程进行支持的。

3.1、Retrofit 是如何知道我们用的是协程?

要想知道这些让我们回到创建 RequestFactory 的地方(上一篇文章第五节)

     
RequestFactory build() {
      //解析接口方法的注解,列如 @GET @POST @Headers 的值等等...
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }

      //省略一些判断代码...

      //解析形参的注解并获取注解对应的 value
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
        parameterHandlers[p] =
            parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
      }

      //省略一些判断代码...

      return new RequestFactory(this);
}

这里我们重点要关注的是解析形参的部分也就是 parseParameter 方法,该方法的完整签名如下

/*
* @p 传入的循环索引
* @parameterType 参数类型 例如 String Int
* @annotations 参数注解数组 例如 @Path @Query
* @allowContinuation 是否使用了协程
*/
 private @Nullable ParameterHandler<?> parseParameter(
        int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {

了解了上面的内容,我们现在再看那个循环解析参数注解的逻辑,如果对协程不了解的话阅读这段代码的时候你可能会很疑惑,为什么当索引 P == lastparameter 的时候就可以允许支持协程了, 而且还有一个重要的布尔字段那就是 isKotlinSuspendFunction 的改为 true 的过程,可能看了也会让你迷惑,让我们进入 parseParameter 方法看看关键代码

 private @Nullable ParameterHandler<?> parseParameter(
        int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
      ParameterHandler<?> result = null;
      //省略参数解析代码...

      if (result == null) {
        if (allowContinuation) { //当 allowContinuation 为 true 也就是最后一个参数的索引和注解数组长度相等
          try {
            //最后一个参数类型为 Continuation.class 类型 isKotlinSuspendFunction 为true
            if (Utils.getRawType(parameterType) == Continuation.class) {
              isKotlinSuspendFunction = true;
              return null;
            }
          } catch (NoClassDefFoundError ignored) {
          }
        }
        throw parameterError(method, p, "No Retrofit annotation found.");
      }

      return result;
    }

可以看到表面逻辑倒也没什么难以理解的,就是判断参数是否为最后一个并判断最后一个参数是否为 Continuation.class ,奇怪了,我们并未声明 Continuation.class 类型的参数,它判断个什么?要想解答这些困惑,我们需要对 suspend 函数进行一下反编译来看看它在 java 中是个什么样子

//kotlin 代码
 @GET("/repos/{owner}/{repo}/contributors")
suspend fun contributors(
        @Path("owner") owner: String,
        @Path("repo") repo: String) : List<Contributor>

//反编译后的 Java 代码
@GET("/repos/{owner}/{repo}/contributors")
@Nullable
Object contributors(
@Path("owner") @NotNull String var1, 
@Path("repo") @NotNull String var2,
@NotNull Continuation var3);

看了上面的反编译代码是不是有点恍然大明白的感觉,原来 suspend 转换为 java 代码后返回的类型变为了最原始的 Object ,同时形参自动增加了一个 Continuation.class 类型的参数,至此也就明白了为什么要判断最后一个参数为 Continuation.class 类型才能把 isKotlinSuspendFunction 字段改为 true 的处理逻辑。

3.2、Retrofit 对协程的处理

上篇文章中笔者故意将协程相关代码隐藏,主要是为了专注非协程情况下的源码分析,而本节将会重点展示协程相关代码,以梳理 Retrofit 对协程的支持过程

上一节中我们已经明白了 isKotlinSuspendFunction 字段的处理逻辑,现在让我们把视线转到 HttpServiceMethod 的 parseAnnotations 方法,看看协程部分的代码

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
    boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
    boolean continuationWantsResponse = false;
    boolean continuationBodyNullable = false;

    Annotation[] annotations = method.getAnnotations();
    Type adapterType;
    //是否为 Suspend 挂起函数,也就是 Kotlin 的协程
    if (isKotlinSuspendFunction) {
      //获取所有参数类型
      Type[] parameterTypes = method.getGenericParameterTypes();
      //获取参数类型的下边界
      Type responseType = Utils.getParameterLowerBound(0,
          (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      //获取类型全限定类名并判断是否为 Response 类型
      if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
        // Unwrap the actual body type from Response<T>.
        responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
        continuationWantsResponse = true; //协程返回类型为 Response<T> 类型标记位 true 
      } else {
        //省略...
      }

      //获取要适配的 call 类型 ,其实就是创建了一个 ParameterizedType 的实例,类型为 call
      adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      //这里调用了一个实现了 SkipCallbackExecutor 注解的类,
      //ensurePresent 方法主要作用是将原注解数组替换为 SkipCallbackExecutor 注解
      annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
      //获取要适配的 call 类型
      adapterType = method.getGenericReturnType();
    }

    //省略若干代码...

    //获取 OkHttpClient 实例
    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if (!isKotlinSuspendFunction) {
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    } else if (continuationWantsResponse) {
      //返回的是 Response<T> 类型
      return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
          callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
    } else {
      //返回的是 Body 
      return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
          callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
          continuationBodyNullable);
    }
  }

注释解释的很清楚了,这里我们重点关注一下 SkipCallbackExecutorImpl.ensurePresent() 方法,代码如下

 static Annotation[] ensurePresent(Annotation[] annotations) {
    if (Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)) {
      return annotations;
    }
    
    //创建一个新的注解数组
    Annotation[] newAnnotations = new Annotation[annotations.length + 1];
    // 利用系统深拷贝将原数组注解类型替换为 SkipCallbackExecutor 注解
    newAnnotations[0] = SkipCallbackExecutorImpl.INSTANCE;
    System.arraycopy(annotations, 0, newAnnotations, 1, annotations.length);
    return newAnnotations;
  }

看完上面的代码你可能会觉得这个 SkipCallbackExecutor 似曾相识,还记得 DefaultCallAdapterFactory 的 get 方法吗

@Override 
public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
  
    //省略若干代码...
  
    //这里其实主要的作用是为了判断是否使用了协程,如果实现了协程那么则不使用系统的回调线程并返回null
    final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
        ? null
        : callbackExecutor;

    return new CallAdapter<Object, Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }

      @Override public Call<Object> adapt(Call<Object> call) {
        return executor == null
            ? call // OkHttpCall 对象
            : new ExecutorCallbackCall<>(executor, call);
      }
    };
  }

如此 SkipCallbackExecutor 注解的作用就清晰了,首先当为 Suspend 函数时,Retrofit 会将原注解进行深拷贝变为 SkipCallbackExecutor 注解类型,然后会在创建 Call 对象的时候判断,如果符合条件则不用默认的 Executor , 直接通过 OkHttpCall 进行 async / enqueue 请求。

接下来就是协程和非协程返回对象的区别,从上面的代码中我们可以很清晰的看到,对于协程返回有两种Response 类型,一种为自定义的 Model , 一种为 responseBody 类型,分别对应 SuspendForResponse 和 SuspendForBody 对象。而两者和非协程环境下的 CallAdapted 对象相同,都是 HttpServiceMethod 的子类,所以无论哪种环境,抛开其它因素,两者的调用过程都是相同的,即先调用 invoke 方法创建 OkHttpCall 对象,在调用 adapt 方法进行具体的请求,SuspendForResponse 类的 adapt 方法如下

/*
*  @call 是 OkHttpCall 对象
*  @args 是我们请求的参数 就是 github.contributors("square", "retrofit")
*/
 @Override 
 protected Object adapt(Call<ResponseT> call, Object[] args) {
      call = callAdapter.adapt(call);

      //获取参数列表中的最后一个 continuation 类型参数 ,详看上面 suspend 反编译成 Java 代码
      Continuation<Response<ResponseT>> continuation =
          (Continuation<Response<ResponseT>>) args[args.length - 1];

      // 调用 Kotlin 扩展函数
      try {
        return KotlinExtensions.awaitResponse(call, continuation);
      } catch (Exception e) {
        return KotlinExtensions.suspendAndThrow(e, continuation);
      }
}

在 Retrofit 源码中有一个 KotlinExtensions.kt 文件,是一个 Kotlin 的扩展文件,专门用来处理 suspend 函数,我们进入 awaitResponse 函数看看

suspend fun <T : Any> Call<T>.awaitResponse(): Response<T> {
  return suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
      cancel()
    }
    //调用 OkHttpCall 的 enqueue 方法获取转换后的响应(和上篇文章中的过程是一样的)
    enqueue(object : Callback<T> {
      override fun onResponse(call: Call<T>, response: Response<T>) {
        //唤醒挂起函数,返回 response
        //表现形式看开头通过协程获取 model 的代码
        continuation.resume(response)
      }

      override fun onFailure(call: Call<T>, t: Throwable) {
        //唤醒挂起函数,抛出异常
        continuation.resumeWithException(t)
      }
    })
  }
}

注释解释的很清楚了,其中 suspendCancellableCoroutine 是创建协程的方式之一,感兴趣的可以了解下。至于 SuspendForBody 对象,它和 SuspendForResponse 的处理过程是一样的,这里就不多说了。

所以回顾整个流程对于 suspend 函数的处理步骤如下

  • 如果形参列表的最后一个参数是为 Continuation.class 类型,则 isKotlinSuspendFunction 赋值为 true
  • 获取 suspend 函数返回类型,如果为 Response<T> 类型,continuationWantsResponse = true
  • 获取接口方法所有注解,深拷贝替换为 SkipCallbackExecutor 类型,并以此为条件决定采用默认的 Excutor 还是 直接使用 OkHttpCall
  • 根据 continuationWantsResponse 判断返回 SuspendForResponse 还是 SuspendForBody 对象
  • 在 SuspendForResponse / SuspendForBody 的 adapt 方法中调用 KotlinExtensions.awaitResponse / awaitNullable 方法获取 response 并唤起协程返回数据

如果有些地方还是不明白可以跟着笔者的步骤跟进源码一步步看,这样会更清晰。

四、结语

终于,用了两篇文章将 Retrofit 分析完了,没有拉下任何一个常用的关键步骤,可以说是非常全面的分析了。而对于自己来说也有种难言的收获感,文章很长,代码也是反复看了很久很久,而过程中的抓耳挠腮,反复 debug, 在理解完最后一行源码的时候,顿时有一种通透的舒爽感,久久不能平静,这里也吐槽一下自己,其实关于 Retrofit 的文章在前几年就该写出来,但一直拖到了现在,再次感到自己是真的懒,以后尽量勤快点吧!

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

推荐阅读更多精彩内容