小白能看懂的MVP+RXjava+Retrofit2详细讲解

在开始讲正文之前,我首先认为你已经大概分别了解了MVP、RXjava、Retrofit的知识;如果对这些知识还很不熟的话,建议先了解完这些知识在说。

像小白一样学习MVP

给 Android 开发者的 RxJava 详解

Retrofit 2.0:有史以来最大的改进

本文也是简单讲MVP+rxjava+retrofit进行结合,便于初学者上手,大神勿喷,并请指正,谢谢

我知道这些文章想一下全部吸收太难(特别是RXJAVA,扔物线大神的RXjava我至少看了4遍,然后再写这边文章的时候也会对照文章不懂的地方马上翻阅),在分别了解了这些知识之后才打算将这三者结合一起,同时在github上下载了很大代码去学习,发现有的讲的太深奥初学者看不懂,有的写的太随意虽然将三者进行结合了但是结合之后的优点却没有展示出来。

在看到浅谈Android MVP设计模式(简单结合RxJava+Retrofit) 这篇文章才开始觉悟,并参考其代码也做了简单修改,谢谢这位贡献者。

效果图

先看效果图,一个简单的获取天气信息的功能。


效果图1

效果图2

接口数据结构

接口数据结构

可通过数据源查看完整json数据。

项目结构图

项目结构图
  • 1:Retrofit的接口service定义。
  • 2:定义了一些基类,处理公共的方法(个人觉得这样很nice,真是学到了)。
  • 3:MVP中model。
  • 4:MVP中的presenter。
  • 5:MVP中的view。

开发步骤

一:添加依赖

在build.gradle中添加rxjava、retrofit等依赖

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
compile 'com.jakewharton:butterknife:7.0.0'

/** rxjava **/
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'

/** retrofit **/
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'

/**  okhttp  **/
compile 'com.squareup.okhttp3:okhttp:3.3.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'

}

注意:rxjava|rxandroid版本需要一致、retrofit|rxjava版本一致;如果不一致会报错。

二:新建mvp中的v

我们知道从视图view的角度上看一次完整的请求过程包括:显示加载框–>加载数据成功(加载失败)–>更新UI(提示用户)–>关闭正在加载的框。

而这几个步骤我们可以认为大部分请求都是这样的,那么我们就可以将他封装成一个基类视图IBaseView

  • 定义基类视图IBaseView

    package bule.souv.com.mvp_rxjava_retrofittest_master.base;
    
    /**
    * 描述:视图基类
    * 作者:dc on 2017/2/16 10:59
    * 邮箱:597210600@qq.com
    */
    public interface IBaseView<T> {
    /**
    * @descriptoin  请求前加载progress
    * @author   dc
    * @date 2017/2/16 11:00
    */
    void showProgress();
    
    /**
    * @descriptoin  请求结束之后隐藏progress
    * @author   dc
    * @date 2017/2/16 11:01
    */
    void disimissProgress();
    
    /**
    * @descriptoin  请求数据成功
    * @author   dc
    * @param tData 数据类型
    * @date 2017/2/16 11:01
    */
    void loadDataSuccess(T tData);
    
    /**
    * @descriptoin  请求数据错误
    * @author   dc
    * @param throwable 异常类型
    * @date 2017/2/16 11:01
    */
    void loadDataError(Throwable throwable);
    }
    

将这几个请求步骤也成公共方法,然后子类继承这个基类就能获取到这几个公共方法,避免了每个请求的子view都重复定义

  • 定义WeatherView子类视图

      package bule.souv.com.mvp_rxjava_retrofittest_master.view;
    
      import bule.souv.com.mvp_rxjava_retrofittest_master.base.IBaseView;
      import bule.souv.com.mvp_rxjava_retrofittest_master.bean.WeatherInfoBean;
    
      /**
    * 描述:天气信息的mvp中的视图V
    * 作者:dc on 2017/2/16 10:59
    * 邮箱:597210600@qq.com
    */
      public interface WeatherView extends IBaseView<WeatherInfoBean> {
      }
    

有的好事者就问了,你这个子类怎么啥都没做;对,就是啥都没做,任性。因为基类视图已经全部帮我实现了呀,但是有的需求可能只针对某一个功能,不能写在基类上,那么我们就在子类上实现这个方法,比如获取明天的天气,这个只属于天气功能,所以我们可以写在WeatherView这个子类里面。但是这个demo只有一个天气获取的功能,所以......

三:新建MVP中的model

M在MVP中主要处理业务数据逻辑;如:网络请求数据、数据库获取数据等等。

我们在此就就是通过网络数据天气数据

  • WeatherModel的接口定义
    package bule.souv.com.mvp_rxjava_retrofittest_master.model;

    import bule.souv.com.mvp_rxjava_retrofittest_master.base.IBaseRequestCallBack;

    /**
    * 描述:MVP中的M;处理获取网络天气数据
    * 作者:dc on 2017/2/16 11:03
    * 邮箱:597210600@qq.com
    */
    public interface WeatherModel<T> {

    /**
    * @descriptoin  获取网络数据
    * @author   dc
    * @param city 城市
     *@param key key
     * @param iBaseRequestCallBack 数据的回调接口
     * @date 2017/2/17 19:01
     */
    void loadWeather(String city,String key, IBaseRequestCallBack<T> iBaseRequestCallBack);

     /**
     * @descriptoin 注销subscribe
     * @author
     * @param
     * @date 2017/2/17 19:02
     * @return
     */
    void onUnsubscribe();
    }

主要提供两个方法,一个通过city、key来获取数据,IBaseRequestCallBack为数据回调接口,这个在下面会讲到。

  • 定义数据的回调接口IBaseRequestCallBack

在请求数据之后不管成功or失败,我们都需要通知用户(显示UI提示用户),所以在请求数据之后需要提供一个接口反馈到视图V上,在我们做项目的过程中会发现很多数据请求返回的类型都差不多,可能只是返回的bean对象不同而已(天气Bean、班级Bean、新闻Bean),那么我们可不可以也将这些方法写成基类用作回调,然后使用泛型来包装不同的对象就可以了

    package bule.souv.com.mvp_rxjava_retrofittest_master.base;

/**
 * 描述:请求数据的回调接口
 * Presenter用于接受model获取(加载)数据后的回调
 * 作者:dc on 2017/2/16 11:22
 * 邮箱:597210600@qq.com
 */
public interface IBaseRequestCallBack<T> {

/**
 * @descriptoin 请求之前的操作
 * @author  dc
 * @date 2017/2/16 11:34
 */
void beforeRequest();

/**
 * @descriptoin 请求异常
 * @author  dc
 * @param throwable 异常类型
 * @date 2017/2/16 11:34
 */
void requestError(Throwable throwable);

/**
 * @descriptoin 请求完成
 * @author  dc
 * @date 2017/2/16 11:35
 */
void requestComplete();

/**
 * @descriptoin 请求成功
 * @author  dc
 * @param callBack 根据业务返回相应的数据
 * @date 2017/2/16 11:35
 */
void requestSuccess(T callBack);
}

请求之前的操作一样、用Throwable来处理请求异常信息、请求完成之后的操作一样、只有请求成功返回的Object不一样,在这里使用T泛型来指定不同的object对象。

  • 定义retrofit中service的Api
    package bule.souv.com.mvp_rxjava_retrofittest_master.api;

    import bule.souv.com.mvp_rxjava_retrofittest_master.bean.WeatherInfoBean;
    import retrofit2.http.GET;
    import retrofit2.http.Query;
    import rx.Observable;
    
    /**
     * 描述:retrofit的接口service定义
     * 作者:dc on 2017/2/16 11:10
     * 邮箱:597210600@qq.com
     */
    public interface WeatherServiceApi {

    //请求的接口地址:http://api.avatardata.cn/Weather/Query?key=75bfe88f27a34311a41591291b7191ce&cityname=%E9%95%BF%E6%B2%99
    @GET("Weather/Query?")
    Observable<WeatherInfoBean> loadWeatherInfo(@Query("key") String key,@Query("cityname") String cityname);

    }

这里首先需要注意请求的接口地址使用retrofit不要拼接错了。提供了两个参数;

  • 定义实现WeathModel的WeathModelImp

      package bule.souv.com.mvp_rxjava_retrofittest_master.model;
    
      import android.content.Context;
    
      import bule.souv.com.mvp_rxjava_retrofittest_master.api.WeatherServiceApi;
      import bule.souv.com.mvp_rxjava_retrofittest_master.base.BaseModel;
      import bule.souv.com.mvp_rxjava_retrofittest_master.base.IBaseRequestCallBack;
      import bule.souv.com.mvp_rxjava_retrofittest_master.bean.WeatherInfoBean;
      import rx.Subscriber;
      import rx.android.schedulers.AndroidSchedulers;
      import rx.schedulers.Schedulers;
      import rx.subscriptions.CompositeSubscription;
    
      /**
     * 描述:MVP中的M实现类,处理业务逻辑数据
     * 作者:dc on 2017/2/16 11:05
     * 邮箱:597210600@qq.com
     */
      public class WeatherModelImp extends BaseModel implements WeatherModel<WeatherInfoBean> {
    
      private Context context = null;
      private WeatherServiceApi weatherServiceApi;
      private CompositeSubscription mCompositeSubscription;
    
      public WeatherModelImp(Context mContext) {
          super();
          context = mContext;
          weatherServiceApi = retrofitManager.getService();
          mCompositeSubscription = new CompositeSubscription();
      }
    
      @Override
      public void loadWeather(String key, String city, final IBaseRequestCallBack iBaseRequestCallBack) {
          mCompositeSubscription.add(weatherServiceApi.loadWeatherInfo(key, city)  //将subscribe添加到subscription,用于注销subscribe
                .observeOn(AndroidSchedulers.mainThread())//指定事件消费线程
                .subscribeOn(Schedulers.io())  //指定 subscribe() 发生在 IO 线程
                .subscribe(new Subscriber<WeatherInfoBean>() {
    
                    @Override
                    public void onStart() {
                        super.onStart();
                        //onStart它总是在 subscribe 所发生的线程被调用 ,如果你的subscribe不是主线程,则会出错,则需要指定线程;
                        iBaseRequestCallBack.beforeRequest();
                    }
    
                    @Override
                    public void onCompleted() {
                        //回调接口:请求已完成,可以隐藏progress
                        iBaseRequestCallBack.requestComplete();
                    }
    
                    @Override
                    public void onError(Throwable e) {
                        //回调接口:请求异常
                        iBaseRequestCallBack.requestError(e);
                    }
    
                    @Override
                    public void onNext(WeatherInfoBean weatherInfoBean) {
                        //回调接口:请求成功,获取实体类对象
                        iBaseRequestCallBack.requestSuccess(weatherInfoBean);
                    }
                }));
      }
    
      @Override
        public void onUnsubscribe() {
        //判断状态
          if(mCompositeSubscription.isUnsubscribed()){
              mCompositeSubscription.clear();  //注销
              mCompositeSubscription.unsubscribe();
          }
        }
      }
    

因为实现了接口WeathModel所以也实现了接口的两个方法。这里有两部分不知道我理解的对不对,还请大神赐教

1:我在loadweather方法第一行使用了CompositeSubscription.add()方法添加subscribe,主要是用来注销subscribe不知道合不合适,或者说怎样处理注销subscribe会更好?

2:onStart,学习rxjava我们知道observe除了提供必须的三个方法onCompleted()、onError()、onNext()之外还提供了一个onStart()方法,主要在 subscribe 刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,所以在这里我用来显示progress;但是需要注意onStart它总是在 subscribe 所发生的线程被调用 ,如果你的subscribe不是主线程,则会出错,则需要指定线程。

3:在onUnsubscribe()方法中,先判断状态在注销subscribe。

新建MVP中的P

P在MVP中主要是视图V和模式M之间的桥梁。

  • 定义WeathPresenter接口

      package bule.souv.com.mvp_rxjava_retrofittest_master.presenter;
    
          /**
           * 描述:MVP中的P接口定义
           * 作者:dc on 2017/2/16 15:17
           * 邮箱:597210600@qq.com
           */
          public interface WeatherPresenter {
    
          /**
           * @descriptoin 请求天气数据
           * @author  dc
           * @param key key
           *            @param city 城市
           * @date 2017/2/17 19:36
           * @return
           */
          void loadWeather(String key, String city);
      
          /**
           * @descriptoin 注销subscribe
           * @author  dc
           * @date 2017/2/17 19:36
           */
          void unSubscribe();
      }
    
  • 定义presenter的基类

上面我们的IBaseCallBack,我们定义了几个回调方法,方法里面都是处理请求之前、之后的结果,这里也可以新建个Presenter的基类用来建立不同的M和V之间的桥梁(因为是公共的方法)只是M和V不同而已。也因为不同的M和V对象,同样我们也用泛型来代替

    package bule.souv.com.mvp_rxjava_retrofittest_master.base;

    /**
     * 描述:
     *  * 代理对象的基础实现 :  一些基础的方法
     *
     * @param <V> 视图接口对象(view) 具体业务各自继承自IBaseView
     * @param <T> 业务请求返回的具体对象
     * 作者:dc on 2017/2/16 15:07
     * 邮箱:597210600@qq.com
     */
    public class BasePresenterImp<V extends IBaseView , T> implements IBaseRequestCallBack<T> {

    private IBaseView iBaseView = null;  //基类视图

    /**
     * @descriptoin  构造方法
     * @author  dc
     * @param view 具体业务的视图接口对象
     * @date 2017/2/16 15:12
     */
    public BasePresenterImp(V view) {
        this.iBaseView = view;
    }

    /**
     * @descriptoin 请求之前显示progress
     * @author  dc
     * @date 2017/2/16 15:13
     */
    @Override
    public void beforeRequest() {
        iBaseView.showProgress();
    }

    /**
     * @descriptoin 请求异常显示异常信息
     * @author  dc
     * @param throwable 异常信息
     * @date 2017/2/16 15:13
     */
    @Override
    public void requestError(Throwable throwable) {
        iBaseView.loadDataError(throwable);
        iBaseView.disimissProgress(); //请求错误,提示错误信息之后隐藏progress
    }

    /**
     * @descriptoin 请求完成之后隐藏progress
     * @author  dc
     * @date 2017/2/16 15:14
     */
    @Override
    public void requestComplete() {
        iBaseView.disimissProgress();
    }

    /**
     * @descriptoin 请求成功获取成功之后的数据信息
     * @author  dc
     * @param callBack 回调的数据
     * @date 2017/2/16 15:14
     */
    @Override
    public void requestSuccess(T callBack) {
        iBaseView.loadDataSuccess(callBack);
    }

}

在代码中我们可以看到,加载前的弹框,加载成功回调给UI,加载失败通知UI错误信息,加载完成关闭弹框等都已经在这里做了一个基础的实现(这就是为什么定义一个基类的原因)。

这里我们通过两个泛型V and T,V表示不同的视图、T表示不同的对象。这样只要在不同的presenter传入不同的V和T就能通过基类来建立不同的视图V和不同的模型M之间的桥梁

  • 实现WeatherPresenter接口的WeathPresenterImp
        package bule.souv.com.mvp_rxjava_retrofittest_master.presenter;

        import android.content.Context;
        
        import bule.souv.com.mvp_rxjava_retrofittest_master.base.BasePresenterImp;
        import bule.souv.com.mvp_rxjava_retrofittest_master.bean.WeatherInfoBean;
        import bule.souv.com.mvp_rxjava_retrofittest_master.model.WeatherModelImp;
        import bule.souv.com.mvp_rxjava_retrofittest_master.view.WeatherView;
        
        /**
         * 描述:MVP中的P实现类
         * 作者:dc on 2017/2/16 15:17
         * 邮箱:597210600@qq.com
         */
        public class WeatherPresenterImp extends BasePresenterImp<WeatherView,WeatherInfoBean> implements WeatherPresenter { //传入泛型V和T分别为WeatherView、WeatherInfoBean表示建立这两者之间的桥梁
        private Context context = null;
        private WeatherModelImp weatherModelImp = null;
    
        /**
         * @descriptoin 构造方法
         * @param view 具体业务的视图接口对象
         * @author dc
         * @date 2017/2/16 15:12
         */
        public WeatherPresenterImp(WeatherView view, Context context) {
            super(view);
            this.context = context;
            this.weatherModelImp = new WeatherModelImp(context);
        }
    
        @Override
        public void loadWeather(String key, String city) {
            weatherModelImp.loadWeather(key, city, this);
        }
    
        @Override
        public void unSubscribe() {
            weatherModelImp.onUnsubscribe();
        }
    }

在类的定义代码中我们继承了基类BasePresenterImp并传入泛型V、T分别为WeatherView、WeatherInfoBean则表示建立这两者之间的桥梁

也因为我们在BasePresenterImp中实现了IBaseCallBack的回调方法,所以程序Model请求之后通过回调接口调用BasePresenterImp中不同的方法,而我们在不同的方法中处理了视图V的界面,从而P就建立了M的数据处理和V视图的显示处理之间的桥梁

Activity或Fragment的实现

这里面的代码处理都很简单,因为MVP的好处讲activity的M和V分离开了的好处。

    package bule.souv.com.mvp_rxjava_retrofittest_master;

    import android.app.ProgressDialog;
    import android.os.Bundle;
    import android.support.design.widget.FloatingActionButton;
    import android.support.v7.app.AppCompatActivity;
    import android.support.v7.widget.Toolbar;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    import bule.souv.com.mvp_rxjava_retrofittest_master.bean.WeatherInfoBean;
    import bule.souv.com.mvp_rxjava_retrofittest_master.presenter.WeatherPresenterImp;
    import bule.souv.com.mvp_rxjava_retrofittest_master.view.WeatherView;
    import butterknife.Bind;
    import butterknife.ButterKnife;
    import butterknife.OnClick;
    
    public class MainActivity extends AppCompatActivity implements WeatherView{

    @Bind(R.id.toolbar)
    Toolbar toolbar;
    @Bind(R.id.main_getweather_temp_btn)
    Button mainGetweatherTempBtn;
    @Bind(R.id.main_showweather_temp_tv)
    TextView mainShowweatherTempTv;
    @Bind(R.id.fab)
    FloatingActionButton fab;

    /**  对象定义  **/
    private WeatherPresenterImp weatherPresenterImp = null;
    private ProgressDialog progressDialog = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        setSupportActionBar(toolbar);

        init();

    }

    /**
     * @descriptoin 点击获取温度
     * @author
     * @param
     * @date 2017/2/16 15:52
     * @return
     */
    @OnClick(R.id.main_getweather_temp_btn)
    public void setOnClickRequestWeatherBtn(){
        weatherPresenterImp.loadWeather("c5bb749112664353af44bc99ed263857", "长沙");
    }

    /**
     * @descriptoin 初始化
     * @author  dc
     * @date 2017/2/16 15:44
     */
    private void init(){
        weatherPresenterImp = new WeatherPresenterImp(this,this);
        progressDialog = new ProgressDialog(MainActivity.this);
        //        progressDialog.setTitle();
        progressDialog.setMessage("正在请求获取数据,请稍等!!!");
    }


    @Override
    public void showProgress() {
        if(progressDialog != null && !progressDialog.isShowing()){
            progressDialog.show();
        }
    }

    @Override
    public void disimissProgress() {
        if(progressDialog != null && progressDialog.isShowing()){
            progressDialog.dismiss();
        }
    }


    @Override
    public void loadDataSuccess(WeatherInfoBean tData) {
        String temperature = tData.getResult().getRealtime().getWeather().getTemperature();
        mainShowweatherTempTv.setVisibility(View.VISIBLE);
        mainShowweatherTempTv.setText("当前的气温为:" + temperature + "℃");
    }

    @Override
    public void loadDataError(Throwable throwable) {
        String errorMsg = throwable.getMessage();
        mainShowweatherTempTv.setVisibility(View.VISIBLE);
        mainShowweatherTempTv.setText(errorMsg);
    }

    @Override
    protected void onStop() {
        super.onStop();
        //在退出之后注销subscribe
        weatherPresenterImp.unSubscribe();
    }
}

总结

通过上面的案例我想对mvp+rxjava+retrofit的结合使用一些项目应该有一些了解了吧。

  • MVP的好处将M和V分离,使我们的activity或fragment更简洁,整个的功能逻辑处理很明了。

  • 一些公共的功能的方法,我们可以通过定义基类的方法实现,用我们的子类继承基类(获取公共方法)、定义本身方法。会使我们少些很多重复的代码。

  • RXjava + retrofit响应式使我们处理功能的代码逻辑很简洁,你会发现这个功能连Handler都没用到。

  • 当然Rxjava的强大远不如此,后期会根据实际开发情况来更深入的了解rxjava。

  • retrofit中的网络请求部分是可以在继续封装的,这个我们看一些github项目就能发现,后期会模仿这项开源项目强大我们的Okhttp的网络封装。

github下载地址

项目github下载地址

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

推荐阅读更多精彩内容