MVP+Retrofit2.0+RxJava2.0

最近把MVP+Retrofit2.0+RxJava2.0封装了一下,在项目里用了之后,发现代码确实简洁了很多,看上去简直不要太爽!哈哈,大圣写代码,自己写自己夸!
关于MVP的定义之类的内容我就不再赘述了,无非就是Model,View,Presenter。网上千篇文章千篇样,所以我理解着就是根据自己的风格写就行了,没有一个统一的定义非要怎么样怎么样去写。Retrofit2.0和RxJava2.0的基础知识各位看官也就移步别苑自己去看吧。这里就直接说我写的这个封装了。
首先我先定义了一个Retrofit的管理类:RetrofitManager。

public class RetrofitManager {

    private static int DEFAULT_TIMEOUT = 5;

    private RetrofitManager() {

    }

    public static RetrofitManager getInstance() {

        return RetrofitManagerHolder.INSTANCE;
    }

    private static class RetrofitManagerHolder {

        private static RetrofitManager INSTANCE = new RetrofitManager();
    }

    public <S> S create(Class<S> service, String baseUrl) {
        Retrofit retrofit = new Retrofit.Builder()
                .client(getOkHttpClient())
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
        return retrofit.create(service);
    }

    private OkHttpClient getOkHttpClient() {
        return new OkHttpClient.Builder()
                .proxy(Proxy.NO_PROXY)
                .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .retryOnConnectionFailure(false)
                .build();
    }
}

这个类里面定义了Retrofit的获取方法create(),其中涉及到了OkHttp的同步封装。单例模式用的是静态内部类的方式(《设计模式》里最推荐的定义单例模式的方式)。
其次是定义了一个接口:RetrofitService。

public interface RetrofitService {

    @GET(HttpUtil.GET_COUPON_INFO_URL)
    Observable<CouponInfoEntity> getCouponInfo(@QueryMap Map<String, String> params);

    @FormUrlEncoded
    @POST(HttpUtil.CONSUME_COUPON_URL)
    Observable<ConsumeCouponEntity> consumeCoupon(@Query("randomseed") String randomSeed,
                                                  @Query("token") String token,
                                                  @FieldMap Map<String, String> params);
}

这里面放的就是Retrofit请求接口的方式,注意:返回类型为Observable<T>,这是因为集成了RxJava。
然后就是我们的MVP了,首先是Presenter。我这里定义了一个BasePresenter。

public interface BasePresenter {

    void add(Disposable disposable);

    void clear();
}

两个方法:add()和clear(),这两个方法就是用来管理RxJava的生命周期的。
其次是View。我同样定义了一个BaseView。

public interface BaseView {

    void showResult(String result);
}

这里面就是根据各位自己的喜好自行定义了。
最后是Model。同样的,BaseMode。

public class BaseModel {

    public <T> Observable<T> observable(Observable<T> observable) {
        return observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
    }
}

这个方法实现的是返回一个切换线程的Observable,这样就不用在后面每次写一遍了。
然后我还定义了一个RxPresenter,用来实现BasePresenter。

public class RxPresenter implements BasePresenter {

    private CompositeDisposable disposables;

    @Override
    public void add(Disposable disposable) {
        if (null == disposables) {
            disposables = new CompositeDisposable();
        }
        disposables.add(disposable);
    }

    @Override
    public void clear() {
        if (null != disposables && disposables.isDisposed()) {
            disposables.clear();
        }
    }
}

CompositeDisposable是一个管理Disposable的集合,具体怎么实现的大家可以去看下源码,很好理解。
还有一个BaseObserver,用来简洁化请求接口时的操作。

public abstract class BaseObserver<T> implements Observer<T> {

    private Context context;

    public BaseObserver(Context context) {
        this.context = context;
    }

    @Override
    public void onSubscribe(Disposable d) {
        DialogFactory.showLoadingDialog(context);
        onDisposable(d);
    }

    @Override
    public void onNext(T t) {
        onSuccess(t);
    }

    @Override
    public void onError(Throwable e) {
        LogUtil.i("onError: " + e.getMessage());
        DialogFactory.dismissLoadingDialog();
    }

    @Override
    public void onComplete() {
        DialogFactory.dismissLoadingDialog();
    }

    protected abstract void onSuccess(T t);

    protected abstract void onDisposable(Disposable d);
}

这是一个抽象类,实现了Observer,其中我定义了一个构造方法,用于获取上下文Context来显示加载对话框。这个类定义出来之后,后面实现请求接口的时候就只需要执行和实现onSuccess()和onDisposable()方法。
最后是BaseActivity类,此处onDestroy()方法里清除所有的disposable。

public abstract class BaseActivity<P extends RxPresenter> extends Activity {

    protected P presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(initContentView());
        ButterKnife.bind(this);
        UIUtil.add(this);
        init();
        presenter = createPresenter();
        initListener();
        showView();
    }

    protected abstract int initContentView();

    protected abstract void init();

    protected abstract P createPresenter();

    protected abstract void initListener();

    protected abstract void showView();

    @Override
    protected void onDestroy() {
        super.onDestroy();
        presenter.clear();
        UIUtil.remove(this);
    }
}

最后看一下具体实现:
比如主界面里我要请求两个接口,定义两个按钮,具体实现layout的xml就不贴了。

public class MainActivity extends BaseActivity<HomePresenter> implements HomeView {

    @BindView(R.id.edit_input_coupon_code)
    EditText inputCouponCode;

    private long exitTime;

    @Override
    protected int initContentView() {
        return R.layout.activity_main;
    }

    @Override
    protected void init() {

    }

    @Override
    protected HomePresenter createPresenter() {
        return new HomePresenter(this, this);
    }

    @Override
    protected void initListener() {

    }

    @Override
    protected void showView() {

    }

    @Override
    public String getCouponCode() {
        return StringUtil.getStringValue(inputCouponCode);
    }

    @Override
    public void onConsumeCouponSuccess(ConsumeCouponEntity consumeCouponEntity) {

    }

    @Override
    public void onGetCouponInfoSuccess(CouponInfoEntity couponInfoEntity) {

    }

    @Override
    public void showResult(String result) {
        LogUtil.i(result);
    }

    @OnClick(R.id.img_setting)
    public void onSetting() {
        UIUtil.openUI(this, SettingActivity.class);
    }

    @OnClick(R.id.btn_get_coupon_info)
    public void onGetCouponInfo() {
        presenter.getCouponInfo();
    }

    @OnClick(R.id.btn_consume_coupon)
    public void onConsumeCoupon() {
        presenter.consumeCoupon();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if ((System.currentTimeMillis() - exitTime) > 2000) {
                ToastUtil.toastShort(this, "再按一次退出程序");
                exitTime = System.currentTimeMillis();
            } else {
                UIUtil.exit();
            }
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
}

这里我们用的是HomePresenter和HomeView。然后我们再来看一下这两个类是怎么实现的。

public class HomePresenter extends RxPresenter {

    private Context context;
    private HomeView homeView;
    private HomeModel homeModel;

    public HomePresenter(Context context, HomeView homeView) {
        this.context = context;
        this.homeView = homeView;
        homeModel = new HomeModel();
    }

    /**
     * 获取优惠券信息
     */
    public void getCouponInfo() {
        String randomSeed = String.valueOf(System.currentTimeMillis() / 1000);
        String token = "token";
        homeModel.getCouponInfo(homeView.getCouponCode(), randomSeed, token)
                .subscribe(new BaseObserver<CouponInfoEntity>(context) {
                    @Override
                    protected void onSuccess(CouponInfoEntity couponInfoEntity) {
                        if (couponInfoEntity.Status != 1) {
                            ToastUtil.toastShort(context, couponInfoEntity.Msg);
                            return;
                        }
                        homeView.onGetCouponInfoSuccess(couponInfoEntity);
                        homeView.showResult(couponInfoEntity.toString());
                    }

                    @Override
                    protected void onDisposable(Disposable d) {
                        add(d);
                    }
                });
    }

    /**
     * 核销优惠券
     */
    public void consumeCoupon() {
        String shopId = (String) SPUtil.getInstance().getData(SPUtil.SHOP_ID, "");
        if (null == shopId || shopId.equals("")) {
            ToastUtil.toastShort(context, "请先设置店铺Id");
            return;
        }
        String randomSeed = String.valueOf(System.currentTimeMillis() / 1000);
        String token = "token";
        homeModel.consumeCoupon(homeView.getCouponCode(), shopId, randomSeed, token)
                .subscribe(new BaseObserver<ConsumeCouponEntity>(context) {
                    @Override
                    protected void onSuccess(ConsumeCouponEntity consumeCouponEntity) {
                        if (consumeCouponEntity.Status != 1) {
                            ToastUtil.toastShort(context, consumeCouponEntity.Msg);
                            return;
                        }
                        homeView.onConsumeCouponSuccess(consumeCouponEntity);
                        homeView.showResult(consumeCouponEntity.toString());
                    }

                    @Override
                    protected void onDisposable(Disposable d) {
                        add(d);
                    }
                });
    }
}

看,请求接口部分多么简洁,数据解析已经通过RetrofitManager里设置的.addCallAdapterFactory(RxJava2CallAdapterFactory.create())实现了,妈妈再也不用担心我手动解析的麻烦了。在onSuccess()方法里接收就行了。漂亮!
再看HomeView。

public interface HomeView extends BaseView {

    String getCouponCode();

    void onConsumeCouponSuccess(ConsumeCouponEntity consumeCouponEntity);

    void onGetCouponInfoSuccess(CouponInfoEntity couponInfoEntity);
}

其中getCouponCode()方法用来获取界面上输入框里的值,用于HomePresenter里请求接口传参,而onConsumeCouponSuccess()和onGetCouponInfoSuccess()方法用来接收返回数据的实体类对象。
别忘了,还有一个HomeModel呢,那么它是什么样的呢?到底长的是鞋拔子脸还是猪腰子脸呢?

public class HomeModel extends BaseModel {

    public Observable<CouponInfoEntity> getCouponInfo(String couponCode, String randomSeed, String token) {
        Map<String, String> params = new HashMap<>();
        params.put("couponCode", couponCode);
        params.put("randomseed", randomSeed);
        params.put("token", token);
        return observable(RetrofitManager.getInstance().create(RetrofitService.class, HttpUtil.BASE_URL).getCouponInfo(params));
    }

    public Observable<ConsumeCouponEntity> consumeCoupon(String couponCode, String storeId, String randomSeed, String token) {
        Map<String, String> params = new HashMap<>();
        params.put("CouponCode", couponCode);
        params.put("StoreId", storeId);
        return observable(RetrofitManager.getInstance().create(RetrofitService.class, HttpUtil.BASE_URL).consumeCoupon(randomSeed, token, params));
    }
}

哇!原来长得跟本山大叔一样,太神奇了。(开个玩笑,如有侵权,本人概不承认!)
这里就是实现接口请求了,用了BaseMode()里的observable()。
大体就是这样一个风格,这是我自己适合的风格,权且抛砖引玉,语言描述上有不完善之处,代码上应该也有不尽人意之处,还请各位大神帮忙指点指点,本人期待进步!
其实之前写了一个Retrofit初接触,当时还没用到MVP和RxJava2.0,就已经觉得自己很牛13了。现在看到这个封装之后,感觉代码果然好简洁,水平高了很多。再看看那个Retrofit初接触,感觉好low啊!哈哈,说明我还是进步了。
行了,就这些吧。记在这儿,抛砖引玉,敬请指教。也方便自己以后使用的时候查看。

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

推荐阅读更多精彩内容