Retrofit源码解析

Retrofit的简单使用

1. 添加依赖
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
2. 创建一个interface作为Web Service的请求集合,在上面用注解写入需要配置的请求方法
interface GithubService {
    @GET("/users/{user}/repos")
    fun listRepos(@Path("user") user: String): Call<List<Repo>>
}
3. 获取Retrofit实例(单例对象)
object Net {
    fun instance() =
        Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl("https://api.github.com/")
            .build()
}
4. 创建出Service interface实例,调用对应的接口方法,创建出相应的可以用来发起网络请求的Call对象
val githubService = Net.instance().create(GithubService::class.java)
val listRepos = githubService.listRepos("hsicen")
5. 使用Call.execute()或者Call.enqueue()发起请求
listRepos.enqueue(object : Callback<List<Repo>> {
    override fun onFailure(call: Call<List<Repo>>, t: Throwable) {
        tv_content.text = t.message
    }
    override fun onResponse(call: Call<List<Repo>>, response: Response<List<Repo>>) {
        tv_content.text = response.body().toString()
    }
})

Retrofit源码分析

首先我们从离我们最近的位置作为切入点(通常是业务代码的最后一行),这里我们点进Call.enqueue(),看看里面的代码逻辑

/**
  * Asynchronously send the request and notify {@code callback} of its response or if an error
  * occurred talking to the server, creating the request, or processing the response.
  */
void enqueue(Callback<T> callback);

发现这是一个接口,没有具体实现,切入失败;现在需要逐步回退寻找到下一个切入点Retrofit.create()

public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);  //接口验证
    if (validateEagerly) {eagerlyValidateMethods(service);}  //提前验证所有方法的正确性,会用到反射
    //动态代理  创建一个运行时的类,Interface的每一个方法调用都会交由InvocationHandler来处理
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },new InvocationHandler() {
        private final Platform platform = Platform.get();
        private final Object[] emptyArgs = new Object[0];

        @Override public @Nullable Object invoke(Object proxy, Method method,@Nullable Object[] args) throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {//继承自Object的方法
                return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {  //接口默认方法 Java8
                return platform.invokeDefaultMethod(method, service, proxy, args);
            }

            //关键代码
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
        }
    });
}

可以看到前面两行是代码健壮性验证,核心代码Proxy.newProxyInstance()方法来创建Service实例。这个方法会为参数中的Interface创建一个对象,这个对象实现了Interface的每个方法,并且每个方法的实现都会雷同的:调用对象实例内部的一个InvocationHandler成员变量的invoke方法,并把自己的方法信息传递进去。这样就在实质上实现了代理逻辑;Interface中的方法全部由一个另外设定的InvocationHandler对象来进行代理操作。并且这些方法的具体实现是在运行时生成Interface实例时才确定的,而不是在编译时。这就是动态代理机制,大致像下面这样:

//GithubService.kt
interface GithubService {
    @GET("/users/{user}/repos")
    fun listRepos(@Path("user") user: String): Call<List<Repo>>

    @GET("/users/hsicen")
    fun getUser(): Call<User>
}

//realService
class RealService : GithubService {
    private val invocationHandler = object : InvocationHandler {
        private val platform = Platform.get();

        override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any {
            //扮演代理角色,对不同的方法做不同的处理

            return Any()
        }
    }

    override fun listRepos(user: String): Call<List<Repo>> {
        val method = GithubService::class.java.getMethod("listRepos")
        return invocationHandler.invoke(this, method, null) as Call<List<Repo>>
    }

    override fun getUser(): Call<User> {
        val method = GithubService::class.java.getMethod("getUser")
        return invocationHandler.invoke(this, method, null) as Call<User>
    }
}

接下主要关注loadServiceMethod(method).invoke(args != null ? args : emptyArgs)这句代码,loadServiceMethod()invoke()是这个方法中关键作用的代码;invoke()就是Retrofit创建Service实例的关键

abstract @Nullable T invoke(Object[] args);

点进去发现,这是ServiceMethod的抽象方法;那么只好看看loadServiceMethod方法的具体逻辑

ServiceMethod<?> loadServiceMethod(Method method) {
    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;
    synchronized (serviceMethodCache) {
        result = serviceMethodCache.get(method);
            if (result == null) {
                result = ServiceMethod.parseAnnotations(this, method);
                serviceMethodCache.put(method, result);
            }
    }
    return result;
}

然后再点进ServiceMethod.parseAnnotations()方法

static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
        throw methodError(method,"Method return type must not include a type variable or wildcard: %s", returnType);
    }
    if (returnType == void.class) {
        throw methodError(method, "Service methods cannot return void.");
    }

    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}

在这个方法中主要看HttpServiceMethod.parseAnnotations(),这个方法的代码就比较多了,我们主要看返回处的代码

if (!isKotlinSuspendFunction) {
    return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
    //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
    return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
    //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
    return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,continuationBodyNullable);
}

HttpServiceMethodServiceMethod的子类,而HttpServiceMethod.parseAnnotations()是返回ServiceMethod对象,然后再执行ServiceMethod的invoke方法,这里我们可以直接查看HttpServiceMethod的invoke方法

@Override final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
}

在invoke()中创建了一个OkHttpCall,然后调用了adapt()方法,返回我们需要的对象,那么我们就来看这个adapt()方法干了什么,继续点进去

protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args);

又是一个抽象方法,看来直接查看adapt()方法行不通,那么就只好看看OkHttpCall做了什么了,我们点进OkHttpCall看一下吧

final class OkHttpCall<T> implements Call<T> {
   .......
}

OkHttpCall是继承自Retrofit的Call的,也就是我们第一步没走通的那个Call(因为Call的enqueue方法是抽象的),那么它有没有实现Call的enqueue()方法呢?

@Override public void enqueue(final Callback<T> callback) {
    okhttp3.Call call;
    synchronized (this) {
        call = rawCall;
        if (call == null && failure == null) {
            call = rawCall = createRawCall();
        }
    }

    call.enqueue(new okhttp3.Callback() {
        @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
            Response<T> response = parseResponse(rawResponse);
            callback.onResponse(OkHttpCall.this, response);
        }

        @Override public void onFailure(okhttp3.Call call, IOException e) {
            callFailure(e);
        }
    });
}

由于enqueue()中代码量较大,我精简了这部分代码,enqueue()主要的工作是创建了一个okhttp3的Call,然后调用这个call的enqueue()方法去发起网络请求,然后将回调结果预处理之后,交由Retrofit的Callback

因此,到这里我们对Retrofit的工作流程就有了一个大致的了解,Retrofit通过动态代理创建出Service实例,然后通过这个实例调用对应的api得到一个Retrofit的Call对象,这个Call对象又创建了一个OkHttp3的Call实例去发起网络请求,然后将结果回调给Retrofit的Callback

在上面的分析过程中,有一个点还没有解决,那就是我们在创建出OkHttpCall对象后,然后调用了adapt()方法,这个adapt()方法是干什么用的呢?做了哪些处理?

在工作中我们知道,adapter是适配器的意思,作为中间桥梁的作用;猜测是不是对OkHttpCall的转换处理呢?因为我们知道Retrofit可以和RxJava结合使用,是不是就是因为这个adapt()的作用呢?当然,我们还需要查看源码,才能找到答案

找到刚才那个抽象的adapt()方法,查看其实现的地方,我们会看到HttpServiceMethod内部有一个静态内部类实现了该抽象方法

static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
    private final CallAdapter<ResponseT, ReturnT> callAdapter;

    CallAdapted(RequestFactory requestFactory, okhttp3.Call.Factory callFactory,Converter<ResponseBody, ResponseT> responseConverter,CallAdapter<ResponseT, ReturnT> callAdapter) {
        super(requestFactory, callFactory, responseConverter);
        this.callAdapter = callAdapter;
    }

    @Override protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
        return callAdapter.adapt(call);
    }
}

在这个类中,创建了一个CallAdapter类,并将adapt()交由CallAdapter的adapt()来实现,这时候我们可以联想到在Retrofit初始化的时候,有这么一句代码 addCallAdapterFactory(RxJava2CallAdapterFactory.create()),加上这句代码后,我们就可以将Call对象转化为Observable对象,和RxJava进行交互了,看来这个adapt()方法很可能就是将OkHttpCall进行转换的作用,为了验证我们的猜想,继续深入源码分析

这个静态内部类的callAdapter是构造方法传进来的,我们一步一步的向上找

CallAdapted -> callAdapter
HttpServiceMethod -> createCallAdapter()
Retrofit -> callAdapter()
Retrofit -> nextCallAdapter() callAdapterFactories.get()

最后我们发现CallAdapter来自callAdapterFactories变量,那么我们现在就需要找到callAdapterFactories的来源

// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

我们可以发现这个callAdapterFactories分了两次添加,一次是callAdapterFactories变量,另一次是和平台相关的默认CallAdapterFactories,我们先看这个和平台相关默认的CallAdapterFactories,我们这里就只看Android平台

@Override List<? extends CallAdapter.Factory> defaultCallAdapterFactories(@Nullable Executor callbackExecutor) {
    if (callbackExecutor == null) throw new AssertionError();
    DefaultCallAdapterFactory executorFactory = newDefaultCallAdapterFactory(callbackExecutor);
    return Build.VERSION.SDK_INT >= 24
        ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
        : singletonList(executorFactory);
}

首先声明了一个Executor,看命名,这个Executor是用于回调的;然后创建了DefaultCallAdapterFactory(),我们点进这个类,看它的get()方法做了什么操作

@Override public @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    return new CallAdapter<Object, Call<?>>() {
        @Override public Type responseType() {
            return responseType;
        }

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

我只截取了核心代码,我们可以看到在get()方法中返回了一个CallAdapter,还有一点应该注意,我们找这个CallAdapter的原因是什么,是不是要弄清楚它的adapt()方法做了什么,刚开始由于CallAdapter的adapt()是抽象方法,所以我们找到了现在这里;那么,现在我们可以看到DefaultCallAdapterFactory类的get()方法返回的是一个CallAdapter,在它的adapt()方法里返回了ExecutorCallbackCall对象,还记得OkHttpCall么?当时OkHttpCall是通过adapt(call)方法传进来的,DefaultCallAdapterFactory类里又原封不动的传给了ExecutorCallbackCall类,现在ExecutorCallbackCall对象做了什么处理

static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;

    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
        this.callbackExecutor = callbackExecutor;
        this.delegate = delegate;
    }

    @Override public void enqueue(final Callback<T> callback) {
        delegate.enqueue(new Callback<T>() {
            @Override public void onResponse(Call<T> call, final Response<T> response) {
                callbackExecutor.execute(new Runnable() {
                    @Override public void run() {
                        if (delegate.isCanceled()) {
                            // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
                            callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
                        } else {
                            callback.onResponse(ExecutorCallbackCall.this, response);
                        }
                    }
                });
            }

            @Override public void onFailure(Call<T> call, final Throwable t) {
                callbackExecutor.execute(new Runnable() {
                    @Override public void run() {
                        callback.onFailure(ExecutorCallbackCall.this, t);
                    }
                });
            }
        });
    }
}

可以看到,ExecutorCallbackCall是DefaultCallAdapterFactory的静态内部类,继承自Retrofit的Call,与OkHttpCall是同类的,它的作用是把操作切回主线程后再交给Callback

分析完这个和平台相关的默认CallAdapter,我们再来看看callAdapterFactories变量中的CallAdapter,我们点击callAdapterFactories变量,看看哪里对它进行了添加操作

public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
    callAdapterFactories.add(checkNotNull(factory, "factory == null"));
    return this;
}

fun instance(): Retrofit =
    Retrofit.Builder()
     .addConverterFactory(GsonConverterFactory.create())
     .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
     .baseUrl("https://api.github.com/")
     .build()

这些就明白了,这个CallAdapter是我们初始化Retrofit时我们主动添加进去的,我们这里添加的CallAdapterFactory是和RxJava结合使用的,像下面这样

//GithubService.kt
@GET("/users/{user}/repos")
fun listRepos(@Path("user") user: String): Observable<List<Repo>>

//发起网络请求
val repoService = Net.instance().create(GithubService::class.java)
repoService.listRepos("hsicen")
 .observeOn(Schedulers.newThread())
 .subscribe(object : Observer<List<Repo>> {
    override fun onComplete() {

    }

    override fun onSubscribe(d: Disposable?) {

    }

    override fun onNext(value: List<Repo>?) {

    }

    override fun onError(e: Throwable?) {

    }
 })

但是Observable回调的接口太多了,在App中更推荐使用Single

//GithubService.kt
@GET("/users/{user}/repos")
fun listRepos(@Path("user") user: String): Observable<List<Repo>>

//发起网络请求
val repoService = Net.instance().create(GithubService::class.java)
repoService.listRepos("hsicen")
    .observeOn(Schedulers.newThread())
    .subscribe(object : SingleObserver<List<Repo>> {
        override fun onSubscribe(d: Disposable?) {

        }

        override fun onSuccess(value: List<Repo>?) {

        }

        override fun onError(e: Throwable?) {

        }
    })

源码分析总结

  • 通过 Retrofit.create(Class) ⽅法创建出 Service interface 的实例,从⽽使得 Service 中配置的方法变得可用,这是 Retrofit 代码结构的核心

  • Retrofit.create() ⽅法内部,使⽤的是Proxy.newProxyInstance() ⽅法来创建Service实例。这个方法会为参数中的多 interface(具体到Retrofit来说,是固定传入⼀个interface)创建一个对象,这个对象实现了所有interface的每个方法,并且每个方法的实现都是雷同的:调⽤对象实例内部的一个InvocationHandler 成员变量的 invoke() ⽅法,并把⾃⼰的⽅法信息传递进去。这样就在实质上实现了代理逻辑:interface 中的⽅法全部由⼀个另外设定的InvocationHandler对象来进⾏代理操作。并且,这些⽅法的具体实现是在运行时⽣ interface实例时才确定的,⽽不是在编译时(虽然在编译时就已经可以通过代码逻辑推断出来)。这就是「动态代理理机制」的具体含义。

  • 因此,invoke() ⽅法中的逻辑,就是 Retrofit 创建 Service 实例的关键。这个⽅法内有三行关键代码,共同组成了具体逻辑:

    1. ServiceMethod 的创建 loadServiceMethod(method)
      这⾏代码负责读取 interface 中原方法的信息(包括返回值类型、⽅法注解、参数类型、参数注解),并将这些信息做初步分析。实际返回的是一个 HttpServiceMethod

    2. OkHttpCall 的创建 new OkHttpCall<>(requestFactory, args, callFactory, responseConverter)
      OkHttpCall是retrofit2.Call 的子类。这行代码负责将ServiceMethod解读到的信息(主要是一个 RequestFactory 、一个 OkHttpClient 和⼀个 ResponseConverter )封装进 OkHttpCall ;而这个对象可以在需要的时候(例如它的 enqueue() ⽅法被调⽤的时候),利用RequestFactory和OkHttpClient来创建一个okhttp3.Call对象,并调⽤这个okhttp3.Call对象来进行⽹络请求的发起,然后利用ResponseConverter对结果进行预处理之后,交回给 Retrofit 的 Callback

    3. adapt()⽅法 return callAdapter.adapt(new OkHttpCall...)
      这个方法会使用一个CallAdapter对象来把OkHttpCall对象进行转换,⽣成⼀个新的对象。默认情况下,返回的是⼀个ExecutorCallbackCall,它的作用是把操作切回主线程后再交给Callback 。另外,如果有自定义的CallAdapter,这里也可以⽣成别的类型的对象,例例如 RxJava 的Observable ,来让 Retrofit 可以和 RxJava 结合使用。

Retrofit使用总结

1. 如果要直接获取网络返回的字符串,使用ResponseBody作为参数
@GET("sore")
Call<ResponseBody>  getLol();
2. 一个Call只能被执行一次,如果要多次执行Call对象,可以通过clone,来clone一份call,从新调用
Call newCall = mCall.clone()
3. 固定参数查询,不需要动态添加参数,直接调用查询
@GET("/some/endpoint?fixed=query")
Call<User> someEndpoint();
apiService.someEndpoint();

GET  /some/endpoint?fixed=query  HTTP/1.1
4. 动态参数查询,每次网络请求查询指定参数的内容
@GET("/some/endpoint")
Call<User> someEndpoint( @Query("dynamic") String dynamic);
apiService.someEndpoint("query");

GET  /some/endpoint?dynamic=query  HTTP/1.1
5. 动态Map参数查询
@GET("/some/endpoint")
Call<User> someEndpoint(@QueryMap Map<String, String> dynamic);
apiService.someEndpoint(Collections.singletonMap("dynamic", "query"));

GET  /some/endpoint?dynamic=query  HTTP/1.1
6. 省略动态参数查询
@GET("/some/endpoint")
Call<User> someEndpoint(@Query("dynamic") String dynamic);
apiService.someEndpoint(null);

GET  /some/endpoint  HTTP/1.1
7. 固定+动态参数查询
@GET("/some/endpoint?fixed=query")
Call<User> someEndpoint(@Query("dynamic") String dynamic);
apiService.someEndpoint("query");

GET  /some/endpoint?fixed=query&dynamic=query  HTTP/1.1
8. 路径替换查询
@GET("/some/endpoint/{thing}")
Call<User> someEndpoint( @Path("thing") String thing);
apiService.someEndpoint("bar");

GET  /some/endpoint/bar  HTTP/1.1
8. 固定头查询
@GET("/some/endpoint")
@Headers("Accept-Encoding: application/json")
Call<User> someEndpoint();
apiService.someEndpoint();

GET  /some/endpoint  HTTP/1.1
Accept-Encoding: application/json
9. 动态头查询
@GET("/some/endpoint")
Call<User> someEndpoint(@Header("Location") String location);
apiService.someEndpoint("Droidcon NYC 2015");

GET  /some/endpoint  HTTP/1.1
Location: Droidcon NYC 2015
10. 固定+动态头查询
@GET("/some/endpoint")
@Headers("Accept-Encoding: application/json")
Call<User> someEndpoint(@Header("Location") String location);
apiService.someEndpoint("Droidcon NYC 2015");

GET  /some/endpoint  HTTP/1.1
Accept-Encoding: application/json
Location: Droidcon NYC 2015
11. Post请求,无Body
@POST("/some/endpoint")
Call<SomeResponse> someEndpoint();
apiService.someEndpoint();

POST  /some/endpoint  HTTP/1.1
Content-Length: 0
12. Post请求有Body
@POST("/some/endpoint")
Call<SomeResponse> someEndpoint(@Body SomeRequest body);
apiService.someEndpoint();

POST  /some/endpoint  HTTP/1.1
Content-Length: 3
Content-Type: text/html

xdadasd
13. 表单编码字段
@FormUrlEncoded
@POST("/some/endpoint")
Call<User> someEndpoint( @Field("name1") String name1, @Field("name2") String name2);
apiService.someEndpoint("value1", "value2");

POST /some/endpoint HTTP/1.1
Content-Length: 25
Content-Type: application/x-www-form-urlencoded

name1=value1&name2=value2
14. 表单编码字段(Map)
@FormUrlEncoded
@POST("/some/endpoint")
Call<User> someEndpoint( @FieldMap Map<String, String> names);
apiService.someEndpoint(ImmutableMap.of("name1", "value1", "name2", "value2"));

POST /some/endpoint HTTP/1.1
Content-Length: 25
Content-Type: application/x-www-form-urlencoded

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

推荐阅读更多精彩内容