认真看完这一篇,不懂Retrofit?不存在的(源码解析)

虽然一个人
我并不孤独
在心中你陪我看每一个日出

不要总是那么不甘寂寞,因为一个人的你可以让自己心存很多念想,这些念想也必将成为你奋斗的目标与动力。(开篇推歌一首,猛戳《陪我看日出》_






项目——SimpleRetrofit地址:https://github.com/ms-liu/SimpleRetrofit/tree/master


序言

Retrofit
 ['retroʊfɪt]
 v.  样式翻新;改进
 n.  翻新;改进

  为什么先将Retrofit的英文单词解释放在最前面?
  答:很简单就是想提高一下大家英语水平([斜眼笑])。

为什么先将Retrofit单词解释放在最前面,其实大家都知道编码当中有一个难点就是起名字,因为好的名字总是很容易知道类的功能、方法的作用。所以个人觉得在带着大家剖析Retrofit之前,有必要先让大家知道Retrofit单词的意思。因为这一个单词还是能够很好的说明Retrofit功能,可以说是它的核心思想吧。
  话不多少,下面我们正式开始,因为文章篇幅较长,所以建议在电脑上,如果可以的话,可以打开Retrofit源码或者SimpleRetrofit源码

一、如何使用?

我相信来看这篇文章的人,肯定是已经使用过Retrofit,并且乐于学习的优秀“程序猿”。这里只是想重新勾起你们的使用印象,希望耐心的看一下,防止下面剖析的时候会让自己处于懵的状态。
 Demo:

           //new出构建Retrofit的Builder器
        Retrofit.Builder builder = new Retrofit.Builder();
        
        //添加请求BaseURL、转换工厂、请求适配工厂、 调用构建方法,构建出Retrofit对象
        Retrofit retrofit = builder
                    .baseUrl(API_URL)
                    .addConverterFactory(new DefaultConverterFactory())
                    .addCallAdapterFactory(DefaultCallAdapterFactory.INSTANCE)
                    .build();

        //Retrofit调用create对象,创建API接口对象
        Weather weather = retrofit.create(Weather.class);

        //调用API接口中的方法,获取到Call对象
        Call<String> call = weather.getWeather("%E5%98%89%E5%85%B4&", "json", "5slgyqGDENN7Sy7pw29IUvrZ");

        //调用Call的请求方法,
        call.enqueue(new Callback<String>() {
            //得到相应结果
            @Override
            public void onResponse(Call<String> call, Response<String> response) {
                String body = response.body();
                System.out.println("==============="+body);
            }
            //得到失败结果
            @Override
            public void onFailure(Call<String> call, Throwable throwable) {
                System.err.println("异常:"+throwable.toString());
            }
        });

二、剖析

1、掌握Retrofit类

先看下面这个图,掌握大概流程,让脑海中有这副图的印象。


Retrofit简易流程架构图
(一)Retrofit构造器

进入Retrofit类中,先去看他的构造方法( 毕竟要去使用一个类,我们首要的是能拿到它的对象,所以先看它的构造方法)。

  Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
      List<Converter.Factory> converterFactories, List<CallAdapter.Factory> adapterFactories,
      Executor callbackExecutor, boolean validateEagerly) {
    this.callFactory = callFactory;
    this.baseUrl = baseUrl;
    this.converterFactories = unmodifiableList(converterFactories); // Defensive copy at call site.
    this.adapterFactories = unmodifiableList(adapterFactories); // Defensive copy at call site.
    this.callbackExecutor = callbackExecutor;
    this.validateEagerly = validateEagerly;
  }

不难发现的是他需要大量的构造参数,要new出这样的对象,无疑是痛苦且不具条理的。所以Retrofit选择不对外提供该构造方法,那么他是如何创建对象的呢?答案就是选择Builder模式,来一步步的传入所需要的参数,将整个构建过程清晰化,条理化。

(二)Retrofit内部类——Builder

其实Builder中,就是将Retrofit构造方法中所需的构造参数,分门别类的添加进去,并且对于部分参数可义进行默认实现或初始化添加。主要部分的如下:

  • baseUrl(String baseUrl)——添加基础URL
  • client(OkHttpClient client)——添加OkHttpClient对象
  • addConverterFactory()——添加生产Converter的工厂类
  • addCallAdapterFactory()——添加生产CallAdapter的工厂类

简单讲一下这两个工厂类:

  • Converter.Factory

那么现在开始思考,一个请求我们需要处理哪些东西呢?对~没错!就是RequestBodyResponseBody_);所以在Converter.Factory中要做的就是创建这两种对应的需求转换器Converter,而这个Factory就是在Converter这个接口中的内部类。
  所以Converter.Factory是生产RequestBodyConverter和ResponseBodyConverter的生产厂或者车间。记住!!!对于这个工厂了解到这里就足够了。

/**
 * ==============================================
 * 类名:Converter(Interface)
 *   装换器接口
 *
 *   内部包含了一个创建转换器的抽象工厂类
 *
 *   通过调用不同“加工方法”创建出不同转换器
 *
 *    泛型:
 *    F=>转换前类型
 *    T=>转换后类型
 * ==============================================
 */
public interface Converter<F,T> {
    /**
     * 具体转换方法
     * @param f
     * @return
     * @throws IOException
     */
    T convert(F f) throws IOException;

    abstract class Factory{

        //创建ResponseBody转换器 ResponseBody -> ?
        public Converter<ResponseBody,?> responseBodyConverter(
                Type type, Annotation[] annotations, Retrofit retrofit){
            return null;
        }

        //创建RequestBody转换器 ? -> RequestBody
        public Converter<?, RequestBody> requestBodyConverter(
                Type type,Annotation[] parameterAnnotations,Retrofit retrofit){
            return null;
        }

        //创建String转换器  ? -> String
        public Converter<?,String> stringConverter(Type iterableType, Annotation[] parameterAnnotations, Retrofit retrofit){
            return null;
        }
    }
}
  • CallAdapter.Factory

这个工厂是用来生产CallAdpter,CallAdapter是将一个Call适配给另外一个Call的适配器接口。这个Call就是用来真正调起和发送网络请求,并接受请求回调的。所以我们可以按照特定的需求(如:回调处理什么的...)去自定义这个Call,并通过CallAdapter中adpte方法去适配。再通过CallAdapter.Factory去创建CallAdapter,从而满足需求。

public interface CallAdapter<R,T> {
    Type responseType();

    T adapt(Call<R> call);

    abstract class Factory{
        public abstract CallAdapter<?,?> get(Type returnType,
                                             Annotation[] annotations,
                                             Retrofit retrofit);
    }
}

所以,如果我们想要按需求的制定Call,并且能够在Retrofit中起作用需要经过:

1、自定义CustomCall
2、自定义CustomCallAdapter,通过adpte方法返回自定义CustomCall
3、自定义CustomCallAdapterFactory,通过get方法返回自定义CustomCallAdapter
(三)Retrofit类中核心方法create();

经过上面我们只是建造了一个大的工厂(包含了两个重要车间:ConverterFactory、CallAdapterFactory),并且生产了Retrofit。
  那么我们生产的Retrofit,它又是怎样将原材料(定义的API接口)转换成产品(网络请求结果)的呢?答案就是Retrofit类中的create()方法。
  贴出代码:
(如果你选择去看源码可能与这个略有不同,因为这是本人改编的代码)

    @SuppressWarnings("unchecked")//解决代理创建对象时,泛型未检查统一问题
    public <T> T create(final Class<T> apiService){
        if (!apiService.isInterface())
            throw new IllegalArgumentException("API的定义必须是接口形式");
        if (apiService.getInterfaces().length > 0)
            throw new IllegalArgumentException("API的定义不允许继承");

        LogUtils.log("5、Retrofit中提供create()方法,通过动态代理方式,创建API接口对象");

        return (T) Proxy.newProxyInstance(
                apiService.getClassLoader(),
                new Class<?>[]{apiService},
                new InvocationHandler() {
                private final Platform platform = Platform.get();
            @Override
            public Object invoke(Object o, Method method, Object[] args) throws Throwable {
                if (method.getDeclaringClass() == Object.class)
                    return method.invoke(this,args);

                //加载处理API接口方法
                ServiceMethod<Object,Object> serviceMethod =
                        (ServiceMethod<Object, Object>) loadServiceMethod(method);
                //创建OkHttpCall
                OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);

                //通过对应的CallAdapter适配自定义并期望返回的Call
                return serviceMethod.mCallAdapter.adapt(okHttpCall);
            }
        });
    }

在这个方法中通过加载ServiceMethod(什么玩意?不急,后面会详细说明),进行API接口中的各种注解和参数值的处理。并通过CallAdapter去适配OkHttpCall从而得到自己所期望的Call或者对象。
  现在我们再来看这张图,是不是已经能够很好的梳理逻辑了呢?如果还不可以,那就自己再去翻翻Retrofit这个类的源码看看。


Retrofit简易流程架构图

2、理解ServiceMethod类

因为原Retrofit中的ServiceMethod相对较复杂,讲解可能会比较复杂且混乱,所以在这里将会主要通过本人自己抽取ServiceMethod来进行讲解(因为我们不再需要再去造这一副轮子了,所以掌握理解即可)。
  在ServiceMethod类中,同样的也是包含了一个内部类Builder,用于构建ServiceMethod,下面我们一起,来一步步揭开这个Builder面纱[斜眼笑]

(一)ServiceMethod.Builder的构造方法

在这个构造里面我们主要获取Retrofit对象和API接口方法上的注解。

        Builder(Retrofit retrofit, Method method){
            LogUtils.log("7、初次创建API接口方法处理中心(ServiceMethod)");
            //Retrofit实例
            this.mRetrofit = retrofit;

            //API接口中定义的方法
            this.mMethod = method;

            //API接口中方法上定义的注解--->@GET
            this.mMethodAnnotations = method.getAnnotations();

            //API接口中方法中的参数化注解
            this.mParameterTypes = method.getGenericParameterTypes();

            //API接口中方法中的所有参数注解
            this.mParameterAnnotationsArray = method.getParameterAnnotations();

        }
(二)解析构建方法build()

在该方法中,最主要的:

  • 创建CallAdapter——createCallAdapter()
  • 创建ResponseConverter——createResponseConverter()
  • 解析方法注解,获取请求类型,获取相对URL——parserMethodAnnotation()
  • 解析参数注解,并创建对应ParameterHandler,放入ParameterHandler [ ] 数组中
  • 返回ServiceMethod对象,传入Builder本身对象引用
public ServiceMethod build(){
            this.mCallAdapter = createCallAdapter();
            LogUtils.log("10、获取CallAdapter中适配好的响应类型");
            this.mResponseType = mCallAdapter.responseType();

            if (mResponseType == okhttp3.Response.class || mResponseType == Response.class)
                throw new IllegalStateException("方法返回类型错误,需要的是ResponseBody");

            this.mResponseConverter = createResponseConverter();

            for (Annotation annotation:
                 mMethodAnnotations) {
                LogUtils.log("13、解析方法上注解");
                parseMethodAnnotation(annotation);
            }

            if (mMethodType == null)
                throw new IllegalStateException("接口方法中必须需要是哪一种请求类型:@GET,@POST...");

            int parameterCount = mParameterAnnotationsArray.length;
            LogUtils.log("16、根据API接口中方法,注解参数的数量,创建对应长度的参数处理器数组");
            //根据参数数量,创建对应长度的参数处理器数组。
            mParameterHandlers = new ParameterHandler<?>[parameterCount];
            LogUtils.log("循环遍历注解参数====START");
            for (int i = 0; i < parameterCount; i++){
                Type parameterType = mParameterTypes[i];
                Annotation[] parameterAnnotations = mParameterAnnotationsArray[i];
                mParameterHandlers[i] = parserParameter(i,parameterType,parameterAnnotations);
            }
            LogUtils.log("循环遍历注解参数====END");
            return new ServiceMethod<>(this);
        }

对接口方法中参数的解析,其实就是循环遍历所有参数注解,然后获取注解类型,根据不同类型创建不同的ParameterHandler。这里就不帖解析方法了,我相信大家自己去看源码,肯定都是能弄明白的。这里选择带大家一起看一下ParameterHandler这里类。
  对于ParameterHandler这个类的作用,其实就是将一个个的请求参数,添加到网络请求当中去。

public abstract class ParameterHandler<T> {
    /**
     * 实现方法参数添加的方法
     */
    abstract void apply(RequestBuilder builder,T value) throws IOException;

    /**
     * 针对@Query这种参数注解的ParameterHandler
     * @param <T>
     */
    static final class Query<T> extends ParameterHandler<T>{

        private final String mQueryName;
        private final Converter<T, String> mValueConverter;
        private final boolean mUrlEncode;

        Query(String name, Converter<T,String> valueConverter, boolean urlEncode){
            this.mQueryName = name;
            this.mValueConverter = valueConverter;
            this.mUrlEncode = urlEncode;
        }

        @Override
        void apply(RequestBuilder builder, T value) throws IOException {
            if (mValueConverter == null) return;
            //添加请求参数
         builder.addQueryParams(mQueryName,mValueConverter.convert(value),mUrlEncode);
        }
    }
}
(三)拿到ServiceMethod对象后,应该干什么?
  • toRequest(Object...args),将请求中的参数,通过ParameterHandler,添加到请求Request中,并返回Request
  • toResponse(ResponseBody rawResponseBody),将原始的OKHttp的响应体,通过Converter转换
public  ServiceMethod(Builder<R, T> builder) {
        this.mCallFactory = builder.mRetrofit.mCallFactory;
        this.mCallAdapter = builder.mCallAdapter;
        this.mResponseConverter = builder.mResponseConverter;
        this.mBaseUrl = builder.mRetrofit.mBaseUrl;
        this.mRelativeUrl = builder.mRelativeUrl;
        this.mMethod = builder.mMethod;
        this.mMethodType = builder.mMethodType;
        this.mParameterHandlers = builder.mParameterHandlers;
    }

    public Request toRequest(Object... args) throws IOException {
        RequestBuilder requestBuilder = new RequestBuilder(mMethodType, mBaseUrl, mRelativeUrl);

        @SuppressWarnings("unchecked")
        ParameterHandler<Object>[] mParameterHandlers = (ParameterHandler<Object>[]) this.mParameterHandlers;

        int argumentCount = args != null ? args.length : 0;

        if (argumentCount != mParameterHandlers.length){
            throw new IllegalStateException(
                    "待处理参数数量("+argumentCount+")与参数处理器数量("+mParameterHandlers.length+")不对应"
            );
        }

        for (int i=0;i<argumentCount;i++){
            mParameterHandlers[i].apply(requestBuilder,args[i]);
        }

        return requestBuilder.build();
    }

    public R toResponse(ResponseBody rawBody) throws IOException {
        return mResponseConverter.convert(rawBody);
    }

在这里我们再用一张图来进行说明一下ServiceMethod这个类,帮助大家去理解。

Retrofit中ServiceMethod类

3、再来讲讲OkHttpCall

OkHttpCall其实Retrofit内部封装的类,在OkHttpCall中封装了OkHttp中的Call(用于网络请求的发送或执行)对象。它是Retrofit网络请求发送过程中的实际发送者和网络请求结果回调的第一手接受者。当然也理解为OkHttpCall是Call(OkHttp中的)代理类。
  另外在OkHttpCall中,还做了请求是否发起,是否取消等状态的判读和监听。当然在当前这个抽取的案例中没有体现,可以去实际源码中查看。看明白也是不难的。

public class OkHttpCall<T> implements Call<T> {

    private final ServiceMethod<T, ?> mServiceMethod;
    private final Object[] mArgs;
    private okhttp3.Call mRawCall;

    OkHttpCall(ServiceMethod<T,?> serviceMethod, Object [] args){
        LogUtils.log("17、创建OkHttpCall对象");
        this.mServiceMethod = serviceMethod;
        this.mArgs = args;
    }

    @Override
    public Response<T> execute() throws IOException {
        okhttp3.Call call = null;
        synchronized (this){
            call = mRawCall;
            if (call == null){
                call = mRawCall = createRawCall();
            }
        }
        LogUtils.log("18、调用execute,内部调用OkHTTP3.Call的execute()");
        return parserResponse(call.execute());
    }

    @Override
    public void enqueue(final Callback<T> callback) {
        okhttp3.Call call = null;
        synchronized (this){
            call = mRawCall;
            if (call == null){
                try {
                    call = mRawCall = createRawCall();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        if (call == null){
            callback.onFailure(OkHttpCall.this,new Throwable("不能成功创建请求对象:call == null"));
            return;
        }
        LogUtils.log("18、调用enqueue(带有请求回调监听),内部调用OkHTTP3.Call的enqueue()");
        call.enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {
                callback.onFailure(OkHttpCall.this,e);
            }

            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
                try {
                    //给一点点延时感
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Response<T> tResponse = parserResponse(response);
                callback.onResponse(OkHttpCall.this,tResponse);
            }
        });
    }

    @Override
    public synchronized Request request()throws IOException {
        okhttp3.Call call = mRawCall;
        if (call != null){
            return call.request();
        }

        return (mRawCall = createRawCall()).request();
    }

    private Response<T> parserResponse(okhttp3.Response rawResponse) throws IOException {
        ResponseBody rawBody = rawResponse.body();
        int code = rawResponse.code();
        if (code < 200 || code >= 300) {
            try {
                // Buffer the entire body to avoid future I/O.
                ResponseBody bufferedBody = Utils.buffer(rawBody);
                return Response.error(bufferedBody, rawResponse);
            } finally {
                rawBody.close();
            }
        }

        if (code == 204 || code == 205) {
            rawBody.close();
            return Response.success(null, rawResponse);
        }
        LogUtils.log("19、调用ServiceMethod.toResponse(),将OkHttp.ResponseBody转换成期望的对象类");
        T body = mServiceMethod.toResponse(rawBody);
        return Response.success(body, rawResponse);
    }

    private okhttp3.Call createRawCall() throws IOException {
        Request request = mServiceMethod.toRequest(mArgs);
        okhttp3.Call call = mServiceMethod.mCallFactory.newCall(request);
        if (call == null)
            throw new NullPointerException("不能创建请求对象");
        return call;
    }
}

为了梳理与整理逻辑,现在我们再来一起看一下,一次简单的Retrofit网络请求中的日志输出情况:

System.err: ============>0、创建Retrofit中Builder对象
System.err: ============>1、添加BaseUrl
System.err: ============>2、添加格式转换工厂
System.err: ============>3、添加适配工厂
System.err: ============>4、构建Retrofit对象
System.err: ============>5、Retrofit中提供create()方法,通过动态代理方式,创建API接口对象
System.err: ============>6、加载API接口方法处理中心(ServiceMethod),并缓存(优先加载缓存中)
System.err: ============>初次做当前API请,创建并缓存
System.err: ============>7、初次创建API接口方法处理中心(ServiceMethod)
System.err: ============>8、在ServiceMethod中创建适配器
System.err: ============>9、通过构建Retrofit添加进来的CallAdapterFactory创建CallAdapter
System.err: ============>10、获取CallAdapter,适配好的响应类型
System.err: ============>11、创建请求响应转换器
System.err: ============>12、通过构建Retrofit添加进来的ConverterFactory,创建请求响应转换器
System.err: ============>13、解析方法上注解
System.err: ============>14、判断注解,获取请求类型
System.err: ============>15、获取相对的URL路径
System.err: ============>16、根据API接口中方法,注解参数的数量,创建对应长度的参数处理器数组
System.err: ============>循环遍历注解参数====START
System.err: ============>>>>>>>根据API接口中方法的每一个注解参数,创建对应的参数处理器
System.err: ============>>>>>>>判断参数注解类型是Query,创建对应Query参数处理器
System.err: ============>>>>>>>根据API接口中方法的每一个注解参数,创建对应的参数处理器
System.err: ============>>>>>>>判断参数注解类型是Query,创建对应Query参数处理器
System.err: ============>>>>>>>根据API接口中方法的每一个注解参数,创建对应的参数处理器
System.err: ============>>>>>>>判断参数注解类型是Query,创建对应Query参数处理器
System.err: ============>循环遍历注解参数====END
System.err: ============>17、创建OkHttpCall对象
System.err: ============>18、调用enqueue(带有请求回调监听),内部调用OkHTTP3.Call的enqueue()
System.err: ============>19、调用ServiceMethod.toResponse(),将OkHttp.ResponseBody转换成期望的对象类

System.out: ===============>结果:{"status":201,"message":"APP被用户自己禁用,请在控制台解禁"}

三、回顾与总结

现在不知道有没有理解Retrofit(翻修,改进)这个单词的意思了呢。其实Retrofit(项目)就一个大的工厂,里面包含了多个工作车间,进行着不同的分工合作。在ConverterFactory中创建着Converter(转换器),在CallAdapterFactory中创建着CallAdapter(Call适配器),在ServiceMethod中调度着API接口中的各种注解并生组装出对应的ParameterHandler,在ParameterHandler中将各种参数值添加到Request(请求)当中去,最后我们通过OkHttpCall调用真正的OkHttp中的Call进行网络请求,并且处理部分部分逻辑,返回结果。
  所以Retrofit真的只是在翻修和改进OkHttp,只需要将原材料(API接口),投入进来,就可以生产出想要的产品(请求结果)。

至此,我们整个Retrofit主要东西已经讲完了。现在如果你还处于懵的状态,那我相信你有可能是没有认真看完文章的内容(这是对本人写文章的“蜜汁”自信,我觉得最大原因应该是本人真的没有写好吧)。如果是处于一知半解但是有那么一点思路的话,不着急,如果你愿意的话,你可以选择继续阅读一遍文章。不过更加建议你的是带着这么一点点思路自己去看看一遍Retrofit源码,或者看SimpleRetrofit源码。如果有不懂的话,可以选择再来看看文章里面有没有讲到,或者选择评论区里留言讨论。

其实,从“站在树荫下的后人”的角度来说Retrofit,其实真的不难,因为他的代码真的不多,设计模式也不复杂(工厂和Builder)。但其实如果是作为开发者和设计者,其实真的挺难的。因为Retrofit的扩展性和易用性真的蛮高的,以及一些问题代码的处理思想,如果不经过磨练也确实是很难想到和做到的。所以“骚年”革命尚未成功,同志仍需努力。

结尾PS:

其实这篇文章写的很纠结,统共写了三遍,有种想要放弃的念头。因为感觉有些东西特别难阐述清楚,有些地方就是自己能够清楚知道是有什么作用。但是很难组成书面词表达出来(请对我这样的一个“文科生”,投以关怀)。另外,虽然这篇已经写得很长了,但其实Retrofit中还有不少东西我都是没有表达出来的,没有写全的,感觉还是蛮遗憾的。  
  所以,好几次都想直接录一个视频放出来。因为在视频中可以更好的表达一些东西出来,那些很难表达出来的,直接可以通过“这个傻X东西”,“这玩意”、、、等一些词直接带过去,并且大家也不会歧义,感觉气氛也会好一些_
  另外如果你能够看到这个地方,如果觉得本人解析源码的思路还可以的话,就请给出你想了解的框架或者项目的源码。在评论区里面留言告知。本人尝试看看能不能解析一下。多看别人代码,永远比自己闷头想,进步更大。

实际代码请下载或者Frok项目,若果能给start那就万分感谢。

项目——SimpleRetrofit地址:https://github.com/ms-liu/SimpleRetrofit/tree/master

欢迎大家给出中肯的建议和提高意见,你的鼓励将是我最大的动力。

个人邮箱:ms_liu163@163.com

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

推荐阅读更多精彩内容