retrofit 封裝,Lifecycle綁定Activty和Fragment生命周期授瘦,優(yōu)雅的管理Http請求

retrofit-helper

Retrofit是很多android開發(fā)者都在使用的Http請求庫醋界!他負責(zé)網(wǎng)絡(luò)請求接口的封裝,底層實現(xiàn)是OkHttp,它的一個特點是包含了特別多注解竟宋,通過動態(tài)代理的方式使得開發(fā)者在使用訪問網(wǎng)絡(luò)的時候更加方便簡單高效。

  • 1. Retrofit-helper擴展了那些功能

    描述 相關(guān)類和方法
    回調(diào)函數(shù)中直接處理請求結(jié)果形纺,無需再次判斷是否成功 Callback.onSuccess(Call<T> call, T response)
    請求開始和結(jié)束監(jiān)聽 Callback.onStart(Call<T> call)Callback.onCompleted(Call<T> call, @Nullable Throwable t)
    全局維護多個Retrofit實例 RetrofitFactory.DEFAULTRetrofitFactory.OTHERS
    統(tǒng)一解析異常信息 Callback.parseThrowable(Call<T> call, Throwable t)
    綁定Activity或者Fragment生命周期 LifeCall<T> bindToLifecycle(LifecycleProvider provider, Lifecycle.Event event)
    攔截器監(jiān)聽下載和上傳進度 ProgressInterceptor 丘侠、ProgressListener
    單獨指定某個請求的日志級別 HttpLoggingInterceptor
  • 2. 封裝邏輯解析

    • 2.1 RetrofitFactory全局管理retrofit實例

      全局管理retrofit 實例,通用的做法有單利模式或者靜態(tài)類成員屬性的方式逐样,并且在Application中初始化蜗字,在這里只是管理全局retrofit對象,故采用靜態(tài)的成員屬性即可滿足需求

      DEFAULT 靜態(tài)變量管理默認常用的的retrofit對象脂新,OTHERS 管理其他多個不同配置的retrofit

      /**
       * 創(chuàng)建時間:2018/4/3
       * 編寫人: chengxin
       * 功能描述:管理全局的Retrofit實例
       */
      public final class RetrofitFactory {
          /**
           * 緩存不同配置的retrofit集合挪捕,如不同的url ,converter等
           */
          public static final Map<String, Retrofit> OTHERS = new ConcurrentHashMap<>(2);
          /**
           * 全局的Retrofit對象
           */
          public static volatile Retrofit DEFAULT;
          /**
           * A {@code null} value is permitted
           */
          @Nullable
          public static volatile OnEventListener LISTENER;
      
          private RetrofitFactory() {
          }
      
          public static <T> T create(Class<T> service) {
              //確保多線程的情況下retrofit不為空或者被修改了
              Retrofit retrofit = DEFAULT;
              Utils.checkState(retrofit != null, "DEFAULT == null");
              return retrofit.create(service);
          }
      
          /**
           * @param name 獲取 OTHERS 中指定名字的retrofit
           */
          public static <T> T create(String name, Class<T> service) {
              Utils.checkNotNull(name, "name == null");
              Retrofit retrofit = OTHERS.get(name);
              Utils.checkState(retrofit != null,
                      String.format("retrofit named with '%s' was not found , have you put it in OTHERS ?", name));
              return retrofit.create(service);
          }
      }
      
    • 2.2 自定義Call,支持綁定生命周期。Call接口實現(xiàn) enqueue(Callback<T> callback)方法 enqueue(Callback<T> callback) 争便,支持綁定Activity或者Fragment生命周期bindToLifecycle(LifecycleProvider provider, Lifecycle.Event event)

      /**
       * 創(chuàng)建時間:2018/4/8
       * 編寫人: chengxin
       * 功能描述:支持生命周期綁定的Call{@link retrofit2.Call}
       */
      public interface Call<T> extends Callable<T>, Cloneable {
      
          String TAG = Call.class.getSimpleName();
      
          boolean isExecuted();
      
          void cancel();
      
          boolean isCanceled();
      
          Call<T> clone();
      
          Request request();
      
          /**
           * 綁定生命周期
           *
           * @param provider LifecycleProvider
           * @param event    {@link Lifecycle.Event}
           * @return
           */
          LifeCall<T> bindToLifecycle(LifecycleProvider provider, Lifecycle.Event event);
      
         /**
           * default event is {@link Lifecycle.Event#ON_DESTROY}
           *
           * @param provider LifecycleProvider
           * @return LifeCall
           * @see Call#bindToLifecycle(LifecycleProvider, Lifecycle.Event)
           */
          LifeCall<T> bindUntilDestroy(LifecycleProvider provider);
      }
      
    • 2.3Callback 統(tǒng)一處理回調(diào)和異常

      retrofit 默認的callback只有支持成功和失敗的回調(diào)级零,而一般我們的場景需要在請求開始和結(jié)束的地方做一些UI的展示處理,如顯示加載動畫等滞乙,故添加了onStartonCompleted 監(jiān)聽奏纪。parseThrowable方法可以處理各種請求過程中拋出的throwable,轉(zhuǎn)換成統(tǒng)一的格式方便我們處理展示等

      
      /**
       * if {@link LifeCall#isDisposed()} return true, all methods will not call
       *
       * @param <T> Successful response body type.
       */
      @UiThread
      public interface Callback<T> {
          /**
           * @param call The {@code Call} that was started
           */
          void onStart(Call<T> call);
      
          /**
           * @param call The {@code Call} that has thrown exception
           * @param t    統(tǒng)一解析throwable對象轉(zhuǎn)換為HttpError對象,如果throwable為{@link HttpError}
           *             <li>則為{@link retrofit2.Converter#convert(Object)}內(nèi)拋出的異常</li>
           *             如果為{@link retrofit2.HttpException}
           *             <li>則為{@link Response#body()}為null的時候拋出的</li>
           */
          @NonNull
          HttpError parseThrowable(Call<T> call, Throwable t);
      
          /**
           * 過濾一次數(shù)據(jù),如剔除List中的null等,默認可以返回t
           */
          @NonNull
          T transform(Call<T> call, T t);
      
          void onError(Call<T> call, HttpError error);
      
          void onSuccess(Call<T> call, T t);
      
          /**
           * @param t 請求失敗的錯誤信息
           */
          void onCompleted(Call<T> call, @Nullable Throwable t);
      }
      
  • 2.4 HttpError 統(tǒng)一包裝異常錯誤酷宵,由Callback parseThrowable 方法統(tǒng)一返回

    ? HttpError類中有兩個成員屬性msg 被body亥贸,msg是保存錯誤的描述信息等,body可以保存異常的具體信息或者原始的json等浇垦,onError(Call<T> call, HttpError error)回調(diào)方法可以根據(jù)body的具體信息做二次處理炕置。

    /**
     * 通用的錯誤信息,一般請求是失敗只需要彈出一些錯誤信息即可,like{@link retrofit2.HttpException}
     * Created by chengxin on 2017/6/22.
     */
    public final class HttpError extends RuntimeException {
        private static final long serialVersionUID = -134024482758434333L;
        /**
         * 展示在前端的錯誤描述信息
         */
        public String msg;
    
        /**
         * <p>
         * 請求失敗保存失敗信息,for example:
         * <li>BusiModel: {code:xxx,msg:xxx} 業(yè)務(wù)錯誤信息</li>
         * <li>original json:  原始的json</li>
         * <li>{@link retrofit2.Response}:錯誤響應(yīng)體->Response<?></li>
         * <li>Throwable: 拋出的異常信息</li>
         * </p>
         */
        @Nullable
        public final transient Object body;
    
        public HttpError(String msg) {
            this(msg, null);
        }
    
        public HttpError(String msg, @Nullable Object body) {
            super(msg);
            if (body instanceof Throwable) {
                initCause((Throwable) body);
            }
            //FastPrintWriter#print(String str)
            this.msg = msg != null ? msg : "null";
            this.body = body;
        }
    
        /**
         * 保證和msg一致
         */
        @Override
        public String getMessage() {
            return msg;
        }
    
        @Override
        public String toString() {
            return "HttpError {msg="
                    + msg
                    + ", body="
                    + body
                    + '}';
        }
    }
    
  • 2.5 CallAdapterFactory返回Call請求適配器

    處理請求接口方法返回為Call的請求適配器工廠類

    public final class CallAdapterFactory extends CallAdapter.Factory {
        private static final String RETURN_TYPE = Call.class.getSimpleName();
    
        public static final CallAdapter.Factory INSTANCE = new CallAdapterFactory();
    
        private CallAdapterFactory() {
        }
    
        /**
         * Extract the raw class type from {@code type}. For example, the type representing
         * {@code List<? extends Runnable>} returns {@code List.class}.
         */
        public static Class<?> getRawType(Type type) {
            return CallAdapter.Factory.getRawType(type);
        }
    
        @Override
        public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
            if (getRawType(returnType) != Call.class) {
                return null;
            }
            if (!(returnType instanceof ParameterizedType)) {
                throw new IllegalArgumentException(
                        String.format("%s return type must be parameterized as %s<Foo> or %s<? extends Foo>", RETURN_TYPE, RETURN_TYPE, RETURN_TYPE));
            }
            final Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);
    
            return new CallAdapter<Object, Call<?>>() {
                @Override
                public Type responseType() {
                    return responseType;
                }
    
                @Override
                public Call<Object> adapt(retrofit2.Call<Object> call) {
                    return new RealCall<>(call);
                }
            };
        }
    }
    
  • 2.6 RealLifeCall 繼承LifeCall實現(xiàn)Activity或Fragment生命周期綁定男韧,自動管理生命周期

    負責(zé)生命周期的監(jiān)聽朴摊,在onChanged(@NonNull Lifecycle.Event event)方法中如果匹配到指定的event,標記為disposed 并取消請求此虑,這時Callback中所有的回調(diào)函數(shù)將不會在執(zhí)行甚纲,保證回調(diào)處的安全性。

    
    final class RealLifeCall<T> implements LifeCall<T> {
        private final Call<T> delegate;
        private final Lifecycle.Event event;
        private final LifecycleProvider provider;
        /**
         * LifeCall是否被釋放了
         * like rxAndroid MainThreadDisposable or rxJava ObservableUnsubscribeOn, IoScheduler
         */
        private final AtomicBoolean once = new AtomicBoolean();
    
        RealLifeCall(Call<T> delegate, Lifecycle.Event event, LifecycleProvider provider) {
            this.delegate = delegate;
            this.event = event;
            this.provider = provider;
            provider.observe(this);
        }
    
        @Override
        public void enqueue(final Callback<T> callback) {
            Utils.checkNotNull(callback, "callback==null");
            delegate.enqueue(new Callback<T>() {
                @Override
                public void onStart(Call<T> call) {
                    if (!isDisposed()) {
                        callback.onStart(call);
                    }
                }
    
                @NonNull
                @Override
                public HttpError parseThrowable(Call<T> call, Throwable t) {
                    if (!isDisposed()) {
                        return callback.parseThrowable(call, t);
                    }
                    return new HttpError("Already disposed.", t);
                }
    
                @NonNull
                @Override
                public T transform(Call<T> call, T t) {
                    if (!isDisposed()) {
                        return callback.transform(call, t);
                    }
                    return t;
                }
    
                @Override
                public void onSuccess(Call<T> call, T t) {
                    if (!isDisposed()) {
                        callback.onSuccess(call, t);
                    }
                }
    
                @Override
                public void onError(Call<T> call, HttpError error) {
                    if (!isDisposed()) {
                        callback.onError(call, error);
                    }
                }
    
                @Override
                public void onCompleted(Call<T> call, @Nullable Throwable t) {
                    if (!isDisposed()) {
                        callback.onCompleted(call, t);
                        provider.removeObserver(RealLifeCall.this);
                    }
                }
            });
        }
    
        @NonNull
        @Override
        public T execute() throws Throwable {
            try {
                if (isDisposed()) {
                    throw new DisposedException("Already disposed.");
                }
                T body = delegate.execute();
                if (isDisposed()) {
                    throw new DisposedException("Already disposed.");
                }
                return body;
            } catch (Throwable t) {
                if (isDisposed() && !(t instanceof DisposedException)) {
                    throw new DisposedException("Already disposed.", t);
                }
                throw t;
            } finally {
                if (!isDisposed()) {
                    provider.removeObserver(this);
                }
            }
        }
    
        @Override
        public void onChanged(@NonNull Lifecycle.Event event) {
            if (this.event == event
                    || event == Lifecycle.Event.ON_DESTROY
                    //Activity和Fragment的生命周期是不會傳入 {@code Lifecycle.Event.ON_ANY},
                    //可以手動調(diào)用此方法傳入 {@code Lifecycle.Event.ON_ANY},用于區(qū)分是否為手動調(diào)  用
                    || event == Lifecycle.Event.ON_ANY) {
                if (once.compareAndSet(false, true)/*保證原子性*/) {
                    delegate.cancel();
                    RetrofitFactory.getOnEventListener().onDisposed(delegate, event);
                    provider.removeObserver(this);
                }
            }
        }
    
        @Override
        public boolean isDisposed() {
            return once.get();
        }
    }
    
  • 2.7 AndroidLifecycle觀察者模式統(tǒng)一分發(fā)生命周期事件

    繼承LifecycleObserver 接口監(jiān)聽當(dāng)前的Activity或者Fragment的生命周期朦前,分發(fā)生命周期事件到Observer的onChanged方法介杆。這個類是線程安全的,保證多線程環(huán)境的正確性

    
    /**
     * 實現(xiàn)LifecycleObserver監(jiān)聽Activity和Fragment的生命周期
     * It is thread safe.
     *
     * @see android.database.Observable
     */
    public final class AndroidLifecycle implements LifecycleProvider, LifecycleObserver {
        private final Object mLock = new Object();
    
        @GuardedBy("mLock")
        private final ArrayList<Observer> mObservers = new ArrayList<>();
        /**
         * 緩存當(dāng)前的Event事件
         */
        @GuardedBy("mLock")
        @Nullable
        private Lifecycle.Event mEvent;
    
        @MainThread
        public static LifecycleProvider createLifecycleProvider(LifecycleOwner owner) {
            return new AndroidLifecycle(owner);
        }
    
        private AndroidLifecycle(LifecycleOwner owner) {
            owner.getLifecycle().addObserver(this);
        }
    
        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
        void onEvent(LifecycleOwner owner, Lifecycle.Event event) {
            synchronized (mLock) {
                //保證線程的可見性
                mEvent = event;
                // since onChanged() is implemented by the app, it could do anything, including
                // removing itself from {@link mObservers} - and that could cause problems if
                // an iterator is used on the ArrayList {@link mObservers}.
                // to avoid such problems, just march thru the list in the reverse order.
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onChanged(event);
                }
            }
            if (event == Lifecycle.Event.ON_DESTROY) {
                owner.getLifecycle().removeObserver(this);
            }
        }
    
        @Override
        public void observe(Observer observer) {
            if (observer == null) {
                throw new IllegalArgumentException("The observer is null.");
            }
            synchronized (mLock) {
                if (mObservers.contains(observer)) {
                    return;
                }
                mObservers.add(observer);
                RetrofitFactory.getOnEventListener().onObserverCountChanged(this, mObservers.size() - 1, mObservers.size());
                // since onChanged() is implemented by the app, it could do anything, including
                // removing itself from {@link mObservers} - and that could cause problems if
                // an iterator is used on the ArrayList {@link mObservers}.
                // to avoid such problems, just call onChanged() after {@code mObservers.add(observer)}
                if (mEvent != null) {
                    observer.onChanged(mEvent);
                }
            }
        }
    
        @Override
        public void removeObserver(Observer observer) {
            if (observer == null) {
                throw new IllegalArgumentException("The observer is null.");
            }
            synchronized (mLock) {
                int index = mObservers.indexOf(observer);
                if (index == -1) {
                    return;
                }
                mObservers.remove(index);
                RetrofitFactory.getOnEventListener().onObserverCountChanged(this, mObservers.size() + 1, mObservers.size());
            }
        }
    
        @Override
        public String toString() {
            return "AndroidLifecycle@" + Integer.toHexString(hashCode());
        }
    }
    
  • 2.8 ProgressInterceptor 攔截器監(jiān)聽下載和上傳進度

    繼承okhttp3.Interceptor 韭寸,構(gòu)造方法中傳入ProgressListener監(jiān)聽進度

    /**
     * 創(chuàng)建時間:2018/8/2
     * 編寫人: chengxin
     * 功能描述:上傳或下載進度監(jiān)聽攔截器
     */
    public class ProgressInterceptor implements Interceptor {
    
        private final ProgressListener mProgressListener;
    
        public ProgressInterceptor(ProgressListener progressListener) {
            Utils.checkNotNull(progressListener, "progressListener==null");
            this.mProgressListener = progressListener;
        }
    
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            RequestBody requestBody = request.body();
            //判斷是否有上傳需求
            if (requestBody != null && requestBody.contentLength() > 0) {
                Request.Builder builder = request.newBuilder();
                RequestBody newRequestBody = new ProgressRequestBody(requestBody, mProgressListener, request);
                request = builder.method(request.method(), newRequestBody).build();
            }
    
            Response response = chain.proceed(request);
            ResponseBody responseBody = response.body();
            if (responseBody != null && responseBody.contentLength() > 0) {
                Response.Builder builder = response.newBuilder();
                ResponseBody newResponseBody = new ProgressResponseBody(responseBody, mProgressListener, request);
                response = builder.body(newResponseBody).build();
            }
            return response;
        }
    }
    
  • 2.9 HttpLoggingInterceptor 可以單獨指定某個請求的日志級別

    構(gòu)造OkhttpClient時添加此攔截器春哨,在請求的服務(wù)方法中添加注解

    @Headers("LogLevel:NONE") 或 @Headers("LogLevel:BASIC") 或 @Headers("LogLevel:HEADERS") 或@Headers("LogLevel:BODY")

    @FormUrlEncoded
    @Headers("LogLevel:HEADERS")
    @POST("user/login")
    Call<LoginInfo> getLogin(@Field("username") String username, @Field("password") String password);
    
  • 3.實戰(zhàn)

    • 3.1 初始化全局Retrofit對象

      Retrofit retrofit = new Retrofit.Builder()
                      .baseUrl("http://wanandroid.com/")
                      .callFactory(new OkHttpClient.Builder()
                              .addNetworkInterceptor(httpLoggingInterceptor)
                              .build())
                      //必須添加此adapter 用于構(gòu)建Call
                      .addCallAdapterFactory(CallAdapterFactory.INSTANCE)
                      //添加自定義json解析器 
                      .addConverterFactory(GsonConverterFactory.create())
                      .build();
       RetrofitFactory.DEFAULT = retrofit;
       
       //可以添加多個,如:
       RetrofitFactory.OTHERS.put("other",otherRetrofit);
       
      
    • 3.2 添加請求服務(wù)接口

      下面為登錄的 post請求

      @FormUrlEncoded
      @POST("user/login")
      Call<LoginInfo> getLogin(@Field("username") String username, @Field("password") String password);
      
    • 3.3 發(fā)起請求

      是不是很簡單恩伺,媽媽再也不用擔(dān)心Activity銷毀后資源回收導(dǎo)致的NullPointException等問題了

     
    
    public class MainActivity extends AppCompatActivity {
    
        LifecycleProvider provider = AndroidLifecycle.createLifecycleProvider(this);
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            final Context context = this;
    
            RetrofitFactory.create(ApiService.class)
                  .getLogin("loginName", "password")
                    //.bindUntilDestroy(provider) Activity銷毀時取消請求
                  .bindToLifecycle(provider, Lifecycle.Event.ON_STOP)
                  .enqueue(new DefaultCallback<LoginInfo>() {
                      @Override
                        public void onStart(Call<LoginInfo> call) {
                          showLoading();
                        }
    
                        @Override
                        public void onError(Call<LoginInfo> call, HttpError error) {
                            Toast.makeText(context, error.msg, Toast.LENGTH_SHORT).show();
                        }
    
                        @Override
                        public void onSuccess(Call<LoginInfo> call, LoginInfo loginInfo) {
                            Toast.makeText(context, "登錄成功赴背!", Toast.LENGTH_SHORT).show();
                            //do...
                        }
    
                        @Override
                        public void onCompleted(Call<LoginInfo> call, @Nullable Throwable t){
                            hideLoading();
                        }
                    });
        }
    }
    
  • 4.注意事項

    • 4.1 構(gòu)建retrofit是需要CallAdapterFactory實例,否則無法處理返回為Call的服務(wù)接口

    • 4.2 Callback的回調(diào)函數(shù)均在主線程執(zhí)行,如果Call綁定了生命周期觸發(fā)了cancel()方法

      UI回調(diào)方法均不會執(zhí)行凰荚,如果要監(jiān)聽那些請求被取消了燃观,可以設(shè)置RetrofitFactory.LISTENER屬性,其為一個全局的監(jiān)聽器OnEventListener便瑟。

  • 5.下載

    dependencies {
         implementation 'com.xcheng:retrofit-helper:1.5.5'
    }
    

? github地址: retrofit-helper

License

Copyright 2019 xchengDroid

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缆毁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子胳徽,更是在濱河造成了極大的恐慌积锅,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件养盗,死亡現(xiàn)場離奇詭異缚陷,居然都是意外死亡,警方通過查閱死者的電腦和手機往核,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門箫爷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人聂儒,你說我怎么就攤上這事虎锚。” “怎么了衩婚?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵窜护,是天一觀的道長。 經(jīng)常有香客問我非春,道長柱徙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任奇昙,我火速辦了婚禮护侮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘储耐。我一直安慰自己羊初,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布什湘。 她就那樣靜靜地躺著长赞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闽撤。 梳的紋絲不亂的頭發(fā)上涧卵,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音腹尖,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛热幔,可吹牛的內(nèi)容都是我干的乐设。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼绎巨,長吁一口氣:“原來是場噩夢啊……” “哼近尚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起场勤,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤戈锻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后和媳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體格遭,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年留瞳,在試婚紗的時候發(fā)現(xiàn)自己被綠了拒迅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡她倘,死狀恐怖璧微,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情硬梁,我是刑警寧澤前硫,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站荧止,受9級特大地震影響屹电,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜罩息,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一嗤详、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瓷炮,春花似錦葱色、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至烘绽,卻和暖如春淋昭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背安接。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工翔忽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓歇式,卻偏偏與公主長得像驶悟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子材失,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

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