Retrofit的原理解析

前言

上篇文章我們分析了OkHttp的原理幸逆,不難看出它更多的還是和TCP/IP打交道棠耕,做了請求和響應(yīng)的處理吹零,今天我們來介紹另外一位主人公,那就是我們的Retrofit滨嘱,它更多的是對OkHttp做了一層封裝峰鄙,方便了我們調(diào)用接口,并且對數(shù)據(jù)進行了轉(zhuǎn)化太雨,對業(yè)務(wù)側(cè)更加友好吟榴。
首先我們來看看它的初始化,慢慢剖析它的源碼吧囊扳。

        Retrofit retrofit = new Retrofit.Builder().baseUrl(BaseUrl.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(httpClient)
                .build();

老相識又出現(xiàn)了Builder吩翻,我們接著看

Retrofit.Builder()

    Builder(Platform platform) {
      this.platform = platform;
    }
    public Builder() {
      this(Platform.get());
    }

代碼很清楚,這里進行了平臺的獲取并且賦值锥咸。

Builder.baseUrl

    public Builder baseUrl(String baseUrl) {
      Objects.requireNonNull(baseUrl, "baseUrl == null");
      return baseUrl(HttpUrl.get(baseUrl));
    }

    public Builder baseUrl(HttpUrl baseUrl) {
      Objects.requireNonNull(baseUrl, "baseUrl == null");
      List<String> pathSegments = baseUrl.pathSegments();
      if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
      }
      this.baseUrl = baseUrl;
      return this;
    }

這里也是比較簡單就是對baseUrl進行賦值狭瞎,返回當(dāng)前的Builder對象。后面的幾個基本都一樣搏予,感興趣的可以自己追一下熊锭。

Builder.build

    public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }

      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

      // 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));

      // Make a defensive copy of the converters.
      List<Converter.Factory> converterFactories = new ArrayList<>(
          1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());

      // Add the built-in converter factory first. This prevents overriding its behavior but also
      // ensures correct behavior when using converters that consume all types.
      converterFactories.add(new BuiltInConverters());
      converterFactories.addAll(this.converterFactories);
      converterFactories.addAll(platform.defaultConverterFactories());
       //返回一個 創(chuàng)建的Retrofit對象
      return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
          unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
    }

我們接著看看Retrofit的創(chuàng)建,如下:

  Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
      List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories,
      @Nullable Executor callbackExecutor, boolean validateEagerly) {
    this.callFactory = callFactory;
    this.baseUrl = baseUrl;
    this.converterFactories = converterFactories; // Copy+unmodifiable at call site.
    this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site.
    this.callbackExecutor = callbackExecutor;
    this.validateEagerly = validateEagerly;
  }

其實也是簡單的一些屬性的賦值雪侥。到這里我們簡單的Retrofit創(chuàng)建就完成了碗殷。下面分析大頭,接口的創(chuàng)建請求以及調(diào)用回調(diào)速缨⌒科蓿’

public interface OKHttpService {
    /**
     * 登錄接口demo
     */
    @FormUrlEncoded
    @POST("user/login")
    Call<ResponseBody> login(@Field("username") String num, @Field("password") String password);

我這里簡單創(chuàng)建了個登錄接口。

        OKHttpService httpService = retrofit.create(OKHttpService.class);
        httpService.login("zc", "123123").enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                Log.i("ZC", "成功: " + Thread.currentThread());
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Log.i("ZC", "失敗: " + Thread.currentThread());
            }
        });

接著我們通過create去創(chuàng)建并且發(fā)送請求旬牲,我們一步步的看下去:

create

  public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    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) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            //loadServiceMethod創(chuàng)建的代理方法對象,其內(nèi)部包含了login方法的注解參數(shù),返回值,以及各個工廠以及各請求器轉(zhuǎn)換器等等的適配操作
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
  }

這里的invoke我們在下面分析仿粹。

動態(tài)代理模式:在程序運行期間,通過反射機制,動態(tài)創(chuàng)建OkhttpService.class的代理對象,并且對該對象中的方法增加一些其他操作

newProxyInstance

    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
        //若interface沒有重寫clone(),默認的是進行一次淺拷貝,即實例對象相同,引用不同
        final Class<?>[] intfs = interfaces.clone();
        // Android-removed: SecurityManager calls
        /*
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        */

        /*
         * Look up or generate the designated proxy class.
          *生成或獲取(緩存中)代理類的Class對象
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            // Android-removed: SecurityManager / permission checks.
            /*
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            */
            //通過Class對象獲取構(gòu)造方法
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                // BEGIN Android-removed: Excluded AccessController.doPrivileged call.
                /*
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
                */

                cons.setAccessible(true);
                // END Android-removed: Excluded AccessController.doPrivileged call.
            }
            //通過構(gòu)造方法創(chuàng)建實例對象
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

getProxyClass0獲取的Class對象其實是個單例模式,在第一次時會創(chuàng)建實例并將其存入WeakCache做緩存,之后的獲取會直接從緩存中獲取Class對象,至于其他一些操作,多為反射機制實例化對象常用的調(diào)用引谜。

還有一點我們需要記住的是牍陌,InvocationHandler內(nèi)部的invoke被調(diào)用是我們通過newProxyInstance()返回的代理類對象(OKHttpService的代理實現(xiàn)類)調(diào)用login()時,觸發(fā)invoke()员咽。

至此我們就簡單的分析完了Proxy.newProxyInstance()中的源碼,我們繼續(xù)回到create()源碼中,return代理對象,至此OKHttpService接口的代理對象就被創(chuàng)建了,接下來就要使用代理對象OKHttpService調(diào)用login()方法了毒涧。

調(diào)用login()時我們會調(diào)用InvocationHandler#invoke()

ServiceMethod對象實例化
一個ServiceMethod對象對應(yīng)了一個網(wǎng)絡(luò)接口中的方法,該對象中保存有方法的處理網(wǎng)絡(luò)請求所要用到的各種參數(shù)及方法的屬性注解等,閱讀源碼時要搞清楚serviceMethod的性質(zhì),這里調(diào)用了loadServiceMethod()我們進入方法內(nèi)部看看贝室。

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;
  }

同樣,首先會從緩存中獲取對象,為null才會創(chuàng)建實例對象,也是一個單例對象,在Builer()中會將解析method一些信息(參數(shù),注解,參數(shù)注解)和retrofit對象中的屬性,并保存到serviceMethod對象中契讲。
延伸知識點(繼續(xù)深追源碼):ServiceMethod不僅存儲了method(也就是login()方法)的參數(shù),屬性等,還存儲有各種適配器,轉(zhuǎn)換器,和http協(xié)議要求的一些相關(guān)check,需要結(jié)合http相關(guān)的知識來理解。這里直接給結(jié)論滑频,就不繼續(xù)追了捡偏。

HttpServiceMethod#invoke

  @Override final @Nullable ReturnT invoke(Object[] args) {
    //創(chuàng)建一個網(wǎng)絡(luò)請求器,所以說Retrofit底層默認是使用okhttp實現(xiàn)的
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
     //將okhttpcall對象與網(wǎng)絡(luò)請求適配器進行綁定
    return adapt(call, args);
  }

不難看出,它創(chuàng)建了個OkHttpCall對象峡迷,retrofit2#OkHttpCall重寫了execute()和enqueue方法等,在隨后的網(wǎng)絡(luò)請求適配器調(diào)用中,就會執(zhí)行此方法來通過OkHttp請求訪問網(wǎng)絡(luò)

adapt(call, args)

將創(chuàng)建的okHttpCall對象與serviceMethod中的網(wǎng)絡(luò)請求適配器適配,內(nèi)部return callAdapter.adapt(call);而此callAdapter還記得在哪獲取的?
它是在serviceMethod.loadServiceMethod(method)內(nèi)的createCallAdapter()中選擇的,所以這里的引用是之前選擇的網(wǎng)絡(luò)請求適配器類對象的adapt(),我們就先看一下它默認的網(wǎng)絡(luò)請求適配器也就是DefaultCallAdapterFactory银伟。

DefaultCallAdapterFactory #get#CallAdapter#adapt

  @Override public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }
    if (!(returnType instanceof ParameterizedType)) {
      throw new IllegalArgumentException(
          "Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
    }
    final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);

    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
            : new ExecutorCallbackCall<>(executor, call);
      }
    };
  }

當(dāng)執(zhí)行了serviceMethod.adapt(okHttpCall)時,其實(默認)調(diào)用了此處的adapt()方法,返回一個call對象你虹。
小小整理一下:
當(dāng)客戶端調(diào)用login()方法時,是使用的Proxy.newProxyInstance()返回的代理實例對象httpService去調(diào)用的,并且會回調(diào)InvocationHandler中的invoke()方法,執(zhí)行代理操作,最后通過adapt()返回一個call對象。

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;
        }

        public void enqueue(final Callback<T> callback) {
            Objects.requireNonNull(callback, "callback == null");
            //delegate是在實例化ExecutorCallbackCall時傳遞的第二個參數(shù),對象為OkHttpCall彤避,也就是說這里enqueue()又委托給了OkHttpCall來執(zhí)行
            this.delegate.enqueue(new Callback<T>() {
                public void onResponse(Call<T> call, Response<T> response) {
                //在Android環(huán)境中運行callbackExecutor默認為MainThreadExecutor,當(dāng)網(wǎng)絡(luò)請求成功,執(zhí)行切回到主線程中,回調(diào)客戶端的callback對象的onResponse()方法
                    ExecutorCallbackCall.this.callbackExecutor.execute(() -> {
                        if (ExecutorCallbackCall.this.delegate.isCanceled()) {
                            callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
                        } else {
                            callback.onResponse(ExecutorCallbackCall.this, response);
                        }

                    });
                }

                public void onFailure(Call<T> call, Throwable t) {
                    //失敗同樣也會切換到主線程去回調(diào)callback的onFailure()
                    ExecutorCallbackCall.this.callbackExecutor.execute(() -> {
                        callback.onFailure(ExecutorCallbackCall.this, t);
                    });
                }
            });
        }

        public boolean isExecuted() {
            return this.delegate.isExecuted();
        }

        public Response<T> execute() throws IOException {
            return this.delegate.execute();
        }

        public void cancel() {
            this.delegate.cancel();
        }

        public boolean isCanceled() {
            return this.delegate.isCanceled();
        }

        public Call<T> clone() {
            return new DefaultCallAdapterFactory.ExecutorCallbackCall(this.callbackExecutor, this.delegate.clone());
        }

        public Request request() {
            return this.delegate.request();
        }
    }

調(diào)用login的時候返回了一個Call傅物,然后調(diào)用enqueue,我們就能看的出就是調(diào)用的這里(默認)琉预,其實內(nèi)部調(diào)用的是delegate.enqueue()董饰,也就是我們上文提到過得OkHttpCall的enqueue,調(diào)用okhttp3完成請求圆米,拿到回調(diào)并且處理Response回調(diào)卒暂。

ExecutorCallbackCall.this.callbackExecutor

這里其實還有個這個東西需要說明下,callbackExecutor默認是個啥娄帖,這里我們就可以追到

Android

  static final class Android extends Platform {
    Android() {
      super(Build.VERSION.SDK_INT >= 24);
    }

    @Override public Executor defaultCallbackExecutor() {
      return new MainThreadExecutor();
    }

    static class MainThreadExecutor implements Executor {
      private final Handler handler = new Handler(Looper.getMainLooper());

      @Override public void execute(Runnable r) {
        handler.post(r);
      }
    }
  }

真正調(diào)用接口的實現(xiàn)

OkHttpCall#enqueue

  @Override public void enqueue(final Callback<T> callback) {
    Objects.requireNonNull(callback, "callback == null");

    okhttp3.Call call;
    Throwable failure;

    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = rawCall;
      failure = creationFailure;
      if (call == null && failure == null) {
        try {
          //創(chuàng)建okhttp3.Call
          call = rawCall = createRawCall();
        } catch (Throwable t) {
          throwIfFatal(t);
          failure = creationFailure = t;
        }
      }
    }

    if (failure != null) {
      callback.onFailure(this, failure);
      return;
    }

    if (canceled) {
      call.cancel();
    }

    call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
        Response<T> response;
        try {
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          throwIfFatal(e);
          callFailure(e);
          return;
        }

        try {
          callback.onResponse(OkHttpCall.this, response);
        } catch (Throwable t) {
          throwIfFatal(t);
          t.printStackTrace(); // TODO this is not great
        }
      }

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

      private void callFailure(Throwable e) {
        try {
          callback.onFailure(OkHttpCall.this, e);
        } catch (Throwable t) {
          throwIfFatal(t);
          t.printStackTrace(); // TODO this is not great
        }
      }
    });
  }

接著追一下createRawCall()

createRawCall

  private okhttp3.Call createRawCall() throws IOException {
    okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
    if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
  }

RequestFactory#create()

  okhttp3.Request create(Object[] args) throws IOException {
    @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

    int argumentCount = args.length;
    if (argumentCount != handlers.length) {
      throw new IllegalArgumentException("Argument count (" + argumentCount
          + ") doesn't match expected count (" + handlers.length + ")");
    }

    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl,
        headers, contentType, hasBody, isFormEncoded, isMultipart);

    省略...

    return requestBuilder.get()
        .tag(Invocation.class, new Invocation(method, argumentList))
        .build();
  }

這里可以看到創(chuàng)建了個RequestBuilder也祠,對一些屬性進行了賦值,接著看get块茁,

RequestBuilder#get()

  Request.Builder get() {
    HttpUrl url;
    HttpUrl.Builder urlBuilder = this.urlBuilder;
    if (urlBuilder != null) {
      url = urlBuilder.build();
    } else {
      // No query parameters triggered builder creation, just combine the relative URL and base URL.
      //noinspection ConstantConditions Non-null if urlBuilder is null.
      url = baseUrl.resolve(relativeUrl);
      if (url == null) {
        throw new IllegalArgumentException(
            "Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl);
      }
    }

    RequestBody body = this.body;
    if (body == null) {
      // Try to pull from one of the builders.
      if (formBuilder != null) {
        body = formBuilder.build();
      } else if (multipartBuilder != null) {
        body = multipartBuilder.build();
      } else if (hasBody) {
        // Body is absent, make an empty body.
        body = RequestBody.create(null, new byte[0]);
      }
    }

    MediaType contentType = this.contentType;
    if (contentType != null) {
      if (body != null) {
        body = new ContentTypeOverridingRequestBody(body, contentType);
      } else {
        headersBuilder.add("Content-Type", contentType.toString());
      }
    }

    return requestBuilder
        .url(url)
        .headers(headersBuilder.build())
        .method(method, body);
  }

到這里我們就能看到Okhttp3的Request以及它的內(nèi)部類Builder了齿坷,到這里就終于揭開神秘面紗,打通Retrofit和OkHttp了数焊。后面就是構(gòu)建OkHttp的request以及Call了,然后通過OkHttp的Call去請求崎场,回調(diào)處理轉(zhuǎn)換類型佩耳。這里就不再細分下去了。對于OkHttp的原理感興趣的可以看我之前的博客谭跨。

總結(jié)一下上面的流程:

這里首先根據(jù)serviceMethod中關(guān)于login()方法的屬性以及解析的注解信息創(chuàng)建request請求對象,最后通過return callFactory.newCall(requestBuilder.build());
callFactory
默認是OkHttpClient,我們就不在深入展示了,最終在其內(nèi)部返回的是一個RealCall,有興趣的可以進去了解一下OkHttpClient源碼,到了這里,Retrofit的網(wǎng)絡(luò)請求底層便顯露在我們眼前,從這里我們就可以看到,Retrofit使用的網(wǎng)絡(luò)底層為OkHttp
接下來退回到enqueue()方法體內(nèi),之后調(diào)用call(RealCall)對象的enqueue()來處理網(wǎng)絡(luò)請求了,至于之后的事情,就是OkHttp底層要做的了干厚。
最后我們使用RealCall調(diào)用enqueue()傳入的Callback對象,Callback的onResponse通過parseResponse()完成數(shù)據(jù)轉(zhuǎn)換螃宙。成功回調(diào)onResponse,失敗回調(diào)onFailure(),最后客戶端就可以看到網(wǎng)絡(luò)的訪問狀況了蛮瞄。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谆扎,隨后出現(xiàn)的幾起案子挂捅,更是在濱河造成了極大的恐慌,老刑警劉巖堂湖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闲先,死亡現(xiàn)場離奇詭異,居然都是意外死亡无蜂,警方通過查閱死者的電腦和手機伺糠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來斥季,“玉大人训桶,你說我怎么就攤上這事。” “怎么了舵揭?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵慰照,是天一觀的道長。 經(jīng)常有香客問我琉朽,道長毒租,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任箱叁,我火速辦了婚禮墅垮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘耕漱。我一直安慰自己算色,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布螟够。 她就那樣靜靜地躺著灾梦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪妓笙。 梳的紋絲不亂的頭發(fā)上若河,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音寞宫,去河邊找鬼萧福。 笑死,一個胖子當(dāng)著我的面吹牛辈赋,可吹牛的內(nèi)容都是我干的鲫忍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼钥屈,長吁一口氣:“原來是場噩夢啊……” “哼悟民!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起篷就,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤射亏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后腻脏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸦泳,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年永品,在試婚紗的時候發(fā)現(xiàn)自己被綠了做鹰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鼎姐,死狀恐怖钾麸,靈堂內(nèi)的尸體忽然破棺而出更振,到底是詐尸還是另有隱情,我是刑警寧澤饭尝,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布肯腕,位于F島的核電站,受9級特大地震影響钥平,放射性物質(zhì)發(fā)生泄漏实撒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一涉瘾、第九天 我趴在偏房一處隱蔽的房頂上張望知态。 院中可真熱鬧,春花似錦立叛、人聲如沸负敏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽其做。三九已至,卻和暖如春赁还,著一層夾襖步出監(jiān)牢的瞬間妖泄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工秽浇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留浮庐,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓柬焕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親梭域。 傳聞我的和親對象是個殘疾皇子斑举,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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