Retrofit源碼閱讀

前言

  • 為什么要看Retrofit源碼?

    • 因為目前項目使用的Retrofit網(wǎng)絡(luò)請求秸架。想去詳細了解下。
  • 這次閱讀重點關(guān)注的點通過Create方法追蹤工作流程

    • 如何進行線程切換
    • 用到反射如何去優(yōu)化性能的
    • 怎么解析參數(shù)和注解的
    • 它針對Kotlin做哪些處理
  • 本次閱讀源碼版本依賴

    • implementation 'com.squareup.retrofit2:retrofit:2.9.0'
      implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
      
  • 分析不對的地方請指出。。奄抽。

  • 來張圖

    retrofit.png

Retrofit 基本使用

  • 創(chuàng)建接口

    用于存放請求地址參數(shù)信息

    interface ApiService {
        @GET("users/{user}/repos")
        fun listRepos(@Path("user") user: String?): Call<ResponseBody>?
    }
    
  • 發(fā)起請求與Retrofit配置

    class MainActivity : AppCompatActivity(), View.OnClickListener {
        private var mApiService: ApiService? = null
    
        private var mBinding: ActivityMainBinding? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            mBinding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(mBinding?.root)
            mBinding?.tvData?.setOnClickListener(this)
            //retrofit配置
            val retrofit = Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .build()
           //接口實例
            mApiService = retrofit.create(ApiService::class.java)
        }
    
        override fun onClick(v: View?) {
            requestData()
        }
       
        //發(fā)起請求
        private fun requestData() {
            mApiService?.listRepos("brycecui")?.enqueue(object : Callback<ResponseBody> {
                override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
                    Log.e("MainActivity", "onResponse:${response.body().toString()}")
                }
    
                override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
                    Log.e("MainActivity", "onFailure${t.localizedMessage}")
                }
    
            })
        }
    
    }
    

問題分析

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);
              }
              args = args != null ? args : emptyArgs;
              //注釋②
              return platform.isDefaultMethod(method)
                  ? platform.invokeDefaultMethod(method, service, proxy, args)
                  : loadServiceMethod(method).invoke(args);
            }
          });
}
  • 注釋①。獲取對應(yīng)平臺

    • private static final Platform PLATFORM = findPlatform();
      
      static Platform get() {
        return PLATFORM;
      }
      
      //得到Android平臺
      private static Platform findPlatform() {
        return "Dalvik".equals(System.getProperty("java.vm.name"))
            ? new Android() //
            : new Platform(true);
      }
      //省略部分代碼
        static final class Android extends Platform {
          Android() {
            super(Build.VERSION.SDK_INT >= 24);
          }
          @Override
          public Executor defaultCallbackExecutor() {
            return new MainThreadExecutor();
          }
      //省略部分代碼
          
          static final class MainThreadExecutor implements Executor {
            private final Handler handler = new Handler(Looper.getMainLooper());
      
            @Override
            public void execute(Runnable r) {
              //切換線程
              handler.post(r);
            }
          }
        }
      

      會得到一個Android平臺甩鳄。

  • 注釋②逞度。會返回false走到loadServiceMethod方法

    • 看下loadServiceMethod源碼

      private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
      ServiceMethod<?> loadServiceMethod(Method method) {
        ServiceMethod<?> result = serviceMethodCache.get(method);
        if (result != null) return result;
      
        synchronized (serviceMethodCache) {
          result = serviceMethodCache.get(method);
          if (result == null) {
            //解析數(shù)據(jù)得到ServiceMethod
            result = ServiceMethod.parseAnnotations(this, method);
            serviceMethodCache.put(method, result);
          }
        }
        return result;
      }
      

      如果(線程安全的ConcurrentHashMap)緩存中有直接返回。沒有解析并儲存之后返回ServiceMethod妙啃。

    • 看下他解析數(shù)據(jù)的過程源碼

      abstract class ServiceMethod<T> {
        static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
          
          RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
      
          //省略部分代碼
          
          return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
        }
      
        abstract @Nullable T invoke(Object[] args);
      }
      

      ServiceMethod是一個抽象類档泽。

      • RequestFactory的parseAnnotations源碼

        //建造者模式
        static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
          return new Builder(retrofit, method).build();
        }
        
        
      • RequestFactory構(gòu)造方法

        RequestFactory(Builder builder) {
          method = builder.method;
          baseUrl = builder.retrofit.baseUrl;
          httpMethod = builder.httpMethod;
          relativeUrl = builder.relativeUrl;
          headers = builder.headers;
          contentType = builder.contentType;
          hasBody = builder.hasBody;
          isFormEncoded = builder.isFormEncoded;
          isMultipart = builder.isMultipart;
          parameterHandlers = builder.parameterHandlers;
          isKotlinSuspendFunction = builder.isKotlinSuspendFunction;
        }
        
      • RequestFactory.Builder和它的build方法

         Builder(Retrofit retrofit, Method method) {
              this.retrofit = retrofit;
              this.method = method;
           //通過 反射得到注解、參數(shù)等信息揖赴。
              this.methodAnnotations = method.getAnnotations();
              this.parameterTypes = method.getGenericParameterTypes();
              this.parameterAnnotationsArray = method.getParameterAnnotations();
            }
        RequestFactory build() {
          for (Annotation annotation : methodAnnotations) {
            parseMethodAnnotation(annotation);
          }
          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);
        }
        
      • 我看看下返回的RequestFactory都有啥馆匿。看下圖是使用上面簡單使用的例子debug的


        retrofit-debug

        baseUrl:就是retrofit傳過來的baseUrl

        httpMethod:我們用的GET注解燥滑。(用的POST就是POST)我們可以看下如何進行解析的

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

        根據(jù)使用instanceof 判斷是那種注解進行對應(yīng)的方法解析渐北。也就是parseHttpMethodAndPath方法。

        private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
          //省略httpMethod空異常檢查
          this.httpMethod = httpMethod;
          this.hasBody = hasBody;
          if (value.isEmpty()) {
            return;
          }
          // Get the relative URL path and existing query string, if present.
          int question = value.indexOf('?');
          if (question != -1 && question < value.length() - 1) {
            // Ensure the query string does not have any named parameters.
            String queryParams = value.substring(question + 1);
            Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
            if (queryParamMatcher.find()) {
              throw methodError(
                  method,
                  "URL query string \"%s\" must not have replace block. "
                      + "For dynamic query parameters use @Query.",
                  queryParams);
            }
          }
        
          this.relativeUrl = value;
          this.relativeUrlParamNames = parsePathParameters(value);
        }
        /***********************************手動分割線******************************************/
        static Set<String> parsePathParameters(String path) {
              Matcher m = PARAM_URL_REGEX.matcher(path);
              Set<String> patterns = new LinkedHashSet<>();
              while (m.find()) {
                patterns.add(m.group(1));
              }
              return patterns;
            }
        

        parseHttpMethodAndPath 的value是 請求方法注解里面的值铭拧。對應(yīng)示例中是users/{user}/repos赃蛛。由于沒有所以不會走if語句羽历。

        relativeUrl也就等于users/{user}/repos

  • 在看下ServiceMethod返回的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;
      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();
      
      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);
      }
    }
    

    isKotlinSuspendFunction用來判斷是否使用kotlin協(xié)程請求焊虏。

    true代表使用掛起函數(shù)。那么會創(chuàng)建SuspendForResponse和SuspendForBody秕磷。分別是返回值為Ressponse<T>和ResponseBody诵闭。

    分別在內(nèi)部調(diào)用了擴展函數(shù)。使用協(xié)程切換線程

    //返回值為Ressponse使用
    suspend fun <T> Call<T>.awaitResponse(): Response<T> {
      return suspendCancellableCoroutine { continuation ->
        continuation.invokeOnCancellation {
          cancel()
        }
        enqueue(object : Callback<T> {
          override fun onResponse(call: Call<T>, response: Response<T>) {
            continuation.resume(response)
          }
    
          override fun onFailure(call: Call<T>, t: Throwable) {
            continuation.resumeWithException(t)
          }
        })
      }
    }
    //返回值為ResponseBody使用澎嚣。成功直接返回ResponseBody
    suspend fun <T : Any> Call<T>.await(): T {
      return suspendCancellableCoroutine { continuation ->
        continuation.invokeOnCancellation {
          cancel()
        }
        enqueue(object : Callback<T> {
          override fun onResponse(call: Call<T>, response: Response<T>) {
            if (response.isSuccessful) {
              val body = response.body()
              if (body == null) {
                val invocation = call.request().tag(Invocation::class.java)!!
                val method = invocation.method()
                val e = KotlinNullPointerException("Response from " +
                    method.declaringClass.name +
                    '.' +
                    method.name +
                    " was null but response body type was declared as non-null")
                continuation.resumeWithException(e)
              } else {
                continuation.resume(body)
              }
            } else {
              continuation.resumeWithException(HttpException(response))
            }
          }
    
          override fun onFailure(call: Call<T>, t: Throwable) {
            continuation.resumeWithException(t)
          }
        })
      }
    }
    

    最終會返回一個CallAdapted<>疏尿,而CallAdapted<>是繼承的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);
      }
    }
    
                            而loadServiceMethod(method)的invoke(args)方法易桃。會調(diào)用CallAdapted的adapte方法褥琐。而這個CallAdapted是由HttpServiceMethod的 createCallAdapter方法創(chuàng)建的。也就是retrofit的addCallAdapterFactory方法添加的晤郑。示例中我們沒有添加所以走得是默認的CallAdapted敌呈。在retrofit的Builder類中可以找到
    
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
//這添加一個平臺默認的CallAdapter并且傳一個線程池callbackExecutor
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
//可以看到defaultCallbackExecutor也是由平臺默認的額
 Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

我們看下平臺platform這倆個默認方法贸宏。

List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
    @Nullable Executor callbackExecutor) {
  DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);
  return hasJava8Types
      ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
      : singletonList(executorFactory);
}

繼續(xù)看DefaultCallAdapterFactory

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
  private final @Nullable Executor callbackExecutor;

  DefaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
    this.callbackExecutor = callbackExecutor;
  }

  @Override
  public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
//省略空檢查
    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) {
        //當(dāng)時kotlin協(xié)程請求時返回okhttpcall 否則返回ExecutorCallbackCall
        return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
      }
    };
  }

  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) {
      Objects.requireNonNull(callback, "callback == null");

      //(OkHttp封裝類)OkHttpCall的enqueue方法進行請求
      delegate.enqueue(
          new Callback<T>() {
            @Override
            public void onResponse(Call<T> call, final Response<T> response) {
              //線程切換
              callbackExecutor.execute(
                  () -> {
                    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(() -> callback.onFailure(ExecutorCallbackCall.this, t));
            }
          });
    }

    @Override
    public boolean isExecuted() {
      return delegate.isExecuted();
    }

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

    @Override
    public void cancel() {
      delegate.cancel();
    }

    @Override
    public boolean isCanceled() {
      return delegate.isCanceled();
    }

    @SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone.
    @Override
    public Call<T> clone() {
      return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone());
    }

    @Override
    public Request request() {
      return delegate.request();
    }

    @Override
    public Timeout timeout() {
      return delegate.timeout();
    }
  }
}

也就是說我們示例中的mApiService?.listRepos("brycecui")?.enqueue()最終到了這里。delegate他是 Call<T>接口磕洪。它的實現(xiàn)類是OkHttpCall吭练。怎么知道不是別的實現(xiàn)類。上面我們通過代碼追蹤知道了loadserviceMethod.invoke.實際是調(diào)用了 抽象類ServiceMethod的實現(xiàn)類HttpServiceMethod的invoke方法析显。也就是下面這個方法鲫咽。下面調(diào)用了adapt方法并把OkHttpCall傳了過去。最終也就是通過DefaultCallAdapterFactory的get方法的adapt創(chuàng)建一個ExecutorCallbackCall傳遞的谷异。

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

看下 callbackExecutor.execute分尸。線程切換關(guān)鍵所在。

static final class MainThreadExecutor implements Executor {
  private final Handler handler = new Handler(Looper.getMainLooper());
  @Override
  public void execute(Runnable r) {
    handler.post(r);
  }
}

也就是使用handler.post(r);切換到主線程歹嘹。

總結(jié)

  • 如何切換線程

    • kotlin 使用協(xié)程
    • 默認使用handler
  • 用到反射如何優(yōu)化性能的

    • 使用map緩存避免多次反射浪費性能
  • 如何解析參數(shù)注解

    • 使用反射解析箩绍、正則匹配
  • 針對kotlin做了哪些處理

    • 判斷是否使用kotlin并且針對返回值是否只需要body
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市尺上,隨后出現(xiàn)的幾起案子伶选,更是在濱河造成了極大的恐慌,老刑警劉巖尖昏,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仰税,死亡現(xiàn)場離奇詭異,居然都是意外死亡抽诉,警方通過查閱死者的電腦和手機陨簇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迹淌,“玉大人河绽,你說我怎么就攤上這事“η裕” “怎么了耙饰?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵外邓,是天一觀的道長数苫。 經(jīng)常有香客問我粥血,道長宝磨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任耸峭,我火速辦了婚禮峡继,結(jié)果婚禮上岸梨,老公的妹妹穿的比我還像新娘元暴。我一直安慰自己篷扩,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布茉盏。 她就那樣靜靜地躺著鉴未,像睡著了一般枢冤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铜秆,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天掏导,我揣著相機與錄音,去河邊找鬼羽峰。 笑死,一個胖子當(dāng)著我的面吹牛添瓷,可吹牛的內(nèi)容都是我干的梅屉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鳞贷,長吁一口氣:“原來是場噩夢啊……” “哼坯汤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起搀愧,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤惰聂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后咱筛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搓幌,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年迅箩,在試婚紗的時候發(fā)現(xiàn)自己被綠了溉愁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡饲趋,死狀恐怖拐揭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奕塑,我是刑警寧澤堂污,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站龄砰,受9級特大地震影響盟猖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜换棚,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一扒披、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧圃泡,春花似錦碟案、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辆亏。三九已至,卻和暖如春鳖目,著一層夾襖步出監(jiān)牢的瞬間扮叨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工领迈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留彻磁,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓狸捅,卻偏偏與公主長得像衷蜓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子尘喝,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

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