Retrofit源碼分析

簡介
Retrofit 是 Square 推出的類型安全的HTTP框架已亥,用于android和java,封裝了OkHttp来屠,本文簡單介紹用法虑椎,然后分析源碼流程和涉及的設(shè)計(jì)模式。


基本使用

定義API接口俱笛,下面是官方文檔的例子

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

定義接口GitHubService,創(chuàng)建接口返回值為Call<List<Repo>>的方法listRepos捆姜,注解@GET表示使用GET方法請(qǐng)求服務(wù)器,@GET注解的value是API接口的相對(duì)路徑迎膜。方法參數(shù)@Path("user") String user表示使用傳入?yún)?shù)user替換相對(duì)路徑的{user}

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        GitHubService serviceApi = retrofit.create(GitHubService.class);
        Call<List<Repo>> call = serviceApi.listRepos("octocat");
        call.enqueue(new Callback<List<Repo>>() {
            @Override
            public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
                
            }

            @Override
            public void onFailure(Call<List<Repo>> call, Throwable t) {

            }
        });
  • 實(shí)例化Retrofit泥技,Retrofit使用Builder設(shè)計(jì)模式創(chuàng)建,傳入baseUrl和gson轉(zhuǎn)換工廠構(gòu)建Retrofit實(shí)例對(duì)象磕仅。
  • create方法傳入API接口class對(duì)象珊豹,返回值是動(dòng)態(tài)代理生成的代理的接口實(shí)現(xiàn)類,使用GitHubService接收榕订。
  • 代理接口實(shí)現(xiàn)類調(diào)用定義的listRepos方法平夜,獲取Call
  • call.enqueue異步請(qǐng)求接口

源碼分析

源碼分析將按照上方的示例代碼從上往下分析

  • 創(chuàng)建Retrofit實(shí)例 (Builder設(shè)計(jì)模式)
Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();

無參Builder會(huì)調(diào)用自身的傳入平臺(tái)的構(gòu)造方法

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

Platform.get(),判斷當(dāng)前的平臺(tái)為Android還是java

class Platform {
  private static final Platform PLATFORM = findPlatform();

  static Platform get() {
    return PLATFORM;
  }

  private static Platform findPlatform() {
    try {
      Class.forName("android.os.Build");
      if (Build.VERSION.SDK_INT != 0) {
        return new Android();
      }
    } catch (ClassNotFoundException ignored) {
    }
    return new Platform(true);
  }
static final class Android extends Platform {
    Android() {
      super(Build.VERSION.SDK_INT >= 24);
    }

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

    @Nullable @Override Object invokeDefaultMethod(Method method, Class<?> declaringClass,
        Object object, @Nullable Object... args) throws Throwable {
      if (Build.VERSION.SDK_INT < 26) {
        throw new UnsupportedOperationException(
            "Calling default methods on API 24 and 25 is not supported");
      }
      return super.invokeDefaultMethod(method, declaringClass, object, args);
    }

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

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

Android平臺(tái)defaultCallbackExecutor()返回MainThreadExecutor實(shí)例卸亮,MainThreadExecutor創(chuàng)建了主線程的Handler,在execute()方法將Runnable作為callback傳入玩裙,切換到主線程

build()方法創(chuàng)建Retrofit 實(shí)例

public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }
      
      //判斷callFactory是否為空兼贸,為空給光桿OkHttpClient
      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }
      
      //判斷是否設(shè)置了callbackExecutor段直,沒有使用平臺(tái)默認(rèn),android平臺(tái)默認(rèn)主線程
      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }
      
      //callAdapterFactories中已a(bǔ)dd在builder時(shí)設(shè)置的CallAdapterFactory
      List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
      callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

      List<Converter.Factory> converterFactories = new ArrayList<>(
          1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());

      converterFactories.add(new BuiltInConverters());
      converterFactories.addAll(this.converterFactories);
      converterFactories.addAll(platform.defaultConverterFactories());
      
     //返回Retrofit實(shí)例
      return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
          unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
    }

build()方法(CallAdapterFactory,ConverterFactory抽象工廠設(shè)計(jì)模式)

  • 1.判斷callFactory是否為空溶诞,為空給光桿OkHttpClient
  • 2.判斷是否設(shè)置了callbackExecutor鸯檬,沒有就使用平臺(tái)默認(rèn)Executor,android平臺(tái)默認(rèn)主線程
  • 3.callAdapterFactories已a(bǔ)dd了通過builderaddCallAdapterFactory()的適配工廠(如支持RxJava的適配器Factory),添加平臺(tái)默認(rèn)的適配工廠
  • 4.converterFactories轉(zhuǎn)換工廠螺垢,已a(bǔ)dd了builderaddConverterFactory()的轉(zhuǎn)換工廠(如Gson轉(zhuǎn)換工廠)
    1. 創(chuàng)建Retrofit實(shí)例返回

創(chuàng)建ServiceApi代理實(shí)現(xiàn)類(動(dòng)態(tài)代理設(shè)計(jì)模式)

ServiceApi serviceApi = retrofit.create(ServiceApi.class);
public <T> T create(final Class<T> service) {
    //驗(yàn)證接口
    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 {
            // 判斷方法是否屬于Object喧务,如equals、hashCode方法
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            //是否是平臺(tái)默認(rèn)方法
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            //重點(diǎn)
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
  }

動(dòng)態(tài)代理動(dòng)態(tài)創(chuàng)建了接口的代理實(shí)現(xiàn)類枉圃,調(diào)用的接口方法都會(huì)經(jīng)過這個(gè)方法功茴,如果還不是特別了解代理設(shè)計(jì)模式的同學(xué),可結(jié)合我的這篇文章看看代理設(shè)計(jì)模式-從Retrofit的create方法分析動(dòng)態(tài)代理

  • 1.驗(yàn)證是否是接口和是否有父接口
  • 2.判斷方法是否屬于Object孽亲,如equals坎穿、hashCode方法
  • 3.是否是平臺(tái)默認(rèn)方法
  • 4.loadServiceMethod(重點(diǎn)方法接下來重點(diǎn)分析)

loadServiceMethod方法(享元設(shè)計(jì)模式)

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

serviceMethodCache是一個(gè)ConcurrentHashMap<Method, ServiceMethod<?>>,key值為method,value值為封裝了請(qǐng)求方法和注解等等ServiceMethod。

abstract class ServiceMethod<T> {
  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);
  }

  abstract @Nullable T invoke(Object[] args);
}
final class RequestFactory {
  static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
    return new Builder(retrofit, method).build();
  }

//此處省略大量代碼

static final class Builder {
    Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;
      this.method = method;
      this.methodAnnotations = method.getAnnotations();
      this.parameterTypes = method.getGenericParameterTypes();
      this.parameterAnnotationsArray = method.getParameterAnnotations();
    }

    RequestFactory build() {
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }

      if (httpMethod == null) {
        throw methodError(method, "HTTP method annotation is required (e.g., @GET, @POST, etc.).");
      }

      if (!hasBody) {
        if (isMultipart) {
          throw methodError(method,
              "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
        }
        if (isFormEncoded) {
          throw methodError(method, "FormUrlEncoded can only be specified on HTTP methods with "
              + "request body (e.g., @POST).");
        }
      }

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

      if (relativeUrl == null && !gotUrl) {
        throw methodError(method, "Missing either @%s URL or @Url parameter.", httpMethod);
      }
      if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
        throw methodError(method, "Non-body HTTP method cannot contain @Body.");
      }
      if (isFormEncoded && !gotField) {
        throw methodError(method, "Form-encoded method must contain at least one @Field.");
      }
      if (isMultipart && !gotPart) {
        throw methodError(method, "Multipart method must contain at least one @Part.");
      }

      return new RequestFactory(this);
    }
    private void parseMethodAnnotation(Annotation annotation) {
      if (annotation instanceof DELETE) {
        parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
      } else if (annotation instanceof GET) {
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
      } else if (annotation instanceof HEAD) {
        parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
      } else if (annotation instanceof PATCH) {
        parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
      } else if (annotation instanceof POST) {
        parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
      } else if (annotation instanceof PUT) {
        parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
      } else if (annotation instanceof OPTIONS) {
        parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
      } else if (annotation instanceof HTTP) {
        HTTP http = (HTTP) annotation;
        parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
      } else if (annotation instanceof retrofit2.http.Headers) {
        String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
        if (headersToParse.length == 0) {
          throw methodError(method, "@Headers annotation is empty.");
        }
        headers = parseHeaders(headersToParse);
      } else if (annotation instanceof Multipart) {
        if (isFormEncoded) {
          throw methodError(method, "Only one encoding annotation is allowed.");
        }
        isMultipart = true;
      } else if (annotation instanceof FormUrlEncoded) {
        if (isMultipart) {
          throw methodError(method, "Only one encoding annotation is allowed.");
        }
        isFormEncoded = true;
      }
    }
}

RequestFactory.Builder的構(gòu)造方法返劲,獲取了method的方法注解玲昧、返回值類型、參數(shù)注解篮绿。
接下來看 build()方法

 for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
private void parseMethodAnnotation(Annotation annotation) {
      if (annotation instanceof DELETE) {
        parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
      } else if (annotation instanceof GET) {
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
      } else if (annotation instanceof HEAD) {
        parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
      } else if (annotation instanceof PATCH) {
        parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
      } else if (annotation instanceof POST) {
        parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
      } else if (annotation instanceof PUT) {
        parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
      } else if (annotation instanceof OPTIONS) {
        parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
      } else if (annotation instanceof HTTP) {
        HTTP http = (HTTP) annotation;
        parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
      } else if (annotation instanceof retrofit2.http.Headers) {
        String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
        if (headersToParse.length == 0) {
          throw methodError(method, "@Headers annotation is empty.");
        }
        headers = parseHeaders(headersToParse);
      } else if (annotation instanceof Multipart) {
        if (isFormEncoded) {
          throw methodError(method, "Only one encoding annotation is allowed.");
        }
        isMultipart = true;
      } else if (annotation instanceof FormUrlEncoded) {
        if (isMultipart) {
          throw methodError(method, "Only one encoding annotation is allowed.");
        }
        isFormEncoded = true;
      }
    }

遍歷了method注解孵延,用于判斷請(qǐng)求方法和注解是否正確使用。繼續(xù)往下看

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);
      }
if (relativeUrl == null && !gotUrl) {
        throw methodError(method, "Missing either @%s URL or @Url parameter.", httpMethod);
      }
      if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
        throw methodError(method, "Non-body HTTP method cannot contain @Body.");
      }
      if (isFormEncoded && !gotField) {
        throw methodError(method, "Form-encoded method must contain at least one @Field.");
      }
      if (isMultipart && !gotPart) {
        throw methodError(method, "Multipart method must contain at least one @Part.");
      }

      return new RequestFactory(this);

解析了參數(shù)和參數(shù)注解亲配,判斷參數(shù)和注解是否正確使用尘应,返回了RequestFactory實(shí)例∑ィ回到ServiceMethod類繼續(xù)往下分析

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

獲取方法返回值類型的下面是一些防御性的代碼菩收,不是我們關(guān)注的重點(diǎn),直接來看重點(diǎn)方法HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory)

 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;
    //判斷是否用了kotlin協(xié)程鲸睛,這里不關(guān)心娜饵,先忽略,直接看else 
    if (isKotlinSuspendFunction) {
      Type[] parameterTypes = method.getGenericParameterTypes();
      Type responseType = Utils.getParameterLowerBound(0,
          (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
        // Unwrap the actual body type from Response<T>.
        responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
        continuationWantsResponse = true;
      } else {
        // TODO figure out if type is nullable or not
        // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
        // Find the entry for method
        // Determine if return type is nullable or not
      }

      adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
      adapterType = method.getGenericReturnType();
    }

    CallAdapter<ResponseT, ReturnT> callAdapter =
        createCallAdapter(retrofit, method, adapterType, annotations);
    Type responseType = callAdapter.responseType();
    if (responseType == okhttp3.Response.class) {
      throw methodError(method, "'"
          + getRawType(responseType).getName()
          + "' is not a valid response body type. Did you mean ResponseBody?");
    }
    if (responseType == Response.class) {
      throw methodError(method, "Response must include generic type (e.g., Response<String>)");
    }
    // TODO support Unit for Kotlin?
    if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
      throw methodError(method, "HEAD method must use Void as response type.");
    }

    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);

    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    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);
    }
  }
@Override final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }

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

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

運(yùn)用了Adapter設(shè)計(jì)模式將okhttp的call轉(zhuǎn)換成retrofit的call官辈,完成了請(qǐng)求參數(shù)的封裝并使用okhttp請(qǐng)求網(wǎng)絡(luò)箱舞,okhttp源碼不在本文的分析范圍,有興趣可以再去了解下了拳亿。

總結(jié)
Retrofit運(yùn)用了大量的設(shè)計(jì)模式晴股,對(duì)okhttp進(jìn)行了高度的定制和功能拓展。是一款比較值得分析網(wǎng)絡(luò)框架

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肺魁,一起剝皮案震驚了整個(gè)濱河市电湘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖寂呛,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怎诫,死亡現(xiàn)場離奇詭異,居然都是意外死亡贷痪,警方通過查閱死者的電腦和手機(jī)幻妓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劫拢,“玉大人肉津,你說我怎么就攤上這事〔詹祝” “怎么了妹沙?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長狗唉。 經(jīng)常有香客問我初烘,道長,這世上最難降的妖魔是什么分俯? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任肾筐,我火速辦了婚禮,結(jié)果婚禮上缸剪,老公的妹妹穿的比我還像新娘吗铐。我一直安慰自己,他們只是感情好杏节,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布唬渗。 她就那樣靜靜地躺著,像睡著了一般奋渔。 火紅的嫁衣襯著肌膚如雪镊逝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天嫉鲸,我揣著相機(jī)與錄音撑蒜,去河邊找鬼。 笑死玄渗,一個(gè)胖子當(dāng)著我的面吹牛座菠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播藤树,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼浴滴,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了岁钓?” 一聲冷哼從身側(cè)響起升略,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤微王,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后品嚣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骂远,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年腰根,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拓型。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡额嘿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出劣挫,到底是詐尸還是另有隱情册养,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布压固,位于F島的核電站球拦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏帐我。R本人自食惡果不足惜坎炼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拦键。 院中可真熱鬧谣光,春花似錦、人聲如沸芬为。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽媚朦。三九已至氧敢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間询张,已是汗流浹背孙乖。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瑞侮,地道東北人的圆。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像半火,于是被迫代替她去往敵國和親越妈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350