优雅的使用MVP + RxJava +改造框架

image.png

最近看了不少MVP的文章和项目,整合了一个关于MVP+retrofit2+RxJava的项目,话不多说直接上代码

前言

首先,阅读本篇文章前,建议你先去了解一下MVP这个设计模式。当然,也可以先看看我前面的文章_ 传送门

另外,还用到了RxJava、Retrofit、Okhttp。如果你已经了解了他们的基本用法,请直接忽略这段,接着往下看~

不想看长长的文章的,可以直接看代码。代码地址已经上传到Github,https://github.com/jjxs/MvpPackage

工程结构

按照惯例,先看看工程的主要结构:

image.png

简单说一下几个主要包下的功能。首先是api包,这是存放对Retrofit进行包装的类。Base包当然是放各种Base类啦~ mvp包是将契约类Contract、Model的实现类和Presenter的实现类放一起,方便管理。其实你也可以按功能分包,个人喜好吧。ui包放一些界面的类,如Activity和Fragment。

下面正式开始~

契约类

同样也是从Contract契约类开始说起:

public interface MainContract {

    interface View extends BaseView {

        void showDialog();

        void onSucceed(Gank data);

        void onFail(String err);

        void hideDialog();

    }

    interface Model extends BaseModel {
        Observable<Gank> getGank();
    }

    abstract class Presenter extends BasePresenter<View, Model> {
        public abstract void getGank();
    }
}

我们可以看到,整体上和Google的Demo差不多,都是把View和Presenter放到Contract类里面统一管理,我这里多加了个Model接口,我不推荐在Presenter进行Model操作,本来很优雅的一件事,在Presenter进行Model操作的话,感觉就差了很多,要做一个优雅的程序员。不同的地方是Model和View接口继承了BaseModel接口和BaseView接口,Presenter变成了一个抽象类,继承于BasePresenter抽象类,传入两个泛型View、Model。为啥呢?我们接着看Base包下的三个Base类。

Base类

BaseView:

public interface BaseView {
}

BaseModel;

public interface BaseModel {
}

BasePresenter:

public class BasePresenter<V extends BaseView,M extends BaseModel> {
    protected V mView;
    protected M mModel;

    private CompositeSubscription mCompositeSubscription;

    protected void addSubscribe(Subscription subscription) {
        if (mCompositeSubscription == null) {
            mCompositeSubscription = new CompositeSubscription();
        }
        mCompositeSubscription.add(subscription);
    }

    public void unSubscribe() {
        if (mView != null) {
            mView = null;
        }
        if (mCompositeSubscription != null && mCompositeSubscription.hasSubscriptions()) {
            mCompositeSubscription.clear();
        }
    }

BaseModel和BaseView接口里面是空的,在这里我只是为了在BasePresenter中提供一个约束。当然,如果你有其它全局的需求,可以在里面添加一些方法。重点是BasePresenter这个抽象类,传入一个View和Model,并将其用protected关键字修饰,这样,在它的子类中就可以直接对其赋值和使用了。加入CompositeSubscription变量,是为了对RxJava进行管理。unSubscribe方法对View进行null赋值和清除Rx的Subscription(订阅),防止内存泄漏。

Presnter桥梁:

接下来看看这个很重要的类,作为连接Model和View的桥梁,这里又是怎么做的呢?

public class MainPresenter extends MainContract.Presenter {
    public MainPresenter(MainContract.View view) {
        mView = view;
        mModel = new MainModel();
    }

    @Override
    public void getGank() {

        Subscription subscribe = mModel.getGank()
                .subscribe(new Subscriber<Gank>() {

                    @Override
                    public void onStart() {
                        mView.showDialog();
                    }

                    @Override
                    public void onCompleted() {
                        mView.hideDialog();
                    }

                    @Override
                    public void onError(Throwable e) {
                        mView.onFail(e.getMessage());
                        onCompleted();
                    }

                    @Override
                    public void onNext(Gank gank) {
                        mView.onSucceed(gank);
                    }
                });


        addSubscribe(subscribe);
    }
}

构造方法传进一个View,并且new了一个Model对象,直接赋值给父类中的View和Model。然后下面复写的方法中调用Model中的方法,再将结果通过View中的方法传出去,这是很原始的MVP方式。最后addSubscribe添加到订阅队列中。

Model处理数据

Model分出来,而不在Presenter处理,其实也是为了简洁,当你要处理很多数据的时候,Presenter就会变得很乱了。

public class MainModel implements MainContract.Model {

    @Override
    public Observable<Gank> getGank() {
        return ApiEngine.getInstance().getApiService()
                .getGank("1")
                .compose(RxSchedulers.<Gank>switchThread());
    }
}

我这里很简单,就获取ApiService对象,然后调用API。最后compose传进我自己定义的线程切换器:


public class RxSchedulers {

    public static <T> Observable.Transformer<T, T> switchThread() {
        return new Observable.Transformer<T, T>() {
            @Override
            public Observable<T> call(Observable<T> tObservable) {
                return tObservable
                        .subscribeOn(Schedulers.io())
                        .unsubscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        };
    }
}

将网络请求的IO线程切换回Android的UI主线程,才能继续进行在Presenter中的操作。

BaseActivity中的封装

在前面我们可以看到BasePresenter中有两个方法,一个是添加订阅addSubscribe,另一个是unSubscribe解除订阅。我们只看到了在Presenter中使用了addSubscribe,而没有看到unSubscribe在哪使用了。因为要防止内存泄漏,所以当然要在和生命周期相关的地方进行释放资源,这个地方只有我们所说的View了,也就是Activity和Fragment中。我们先开看一下相关代码:

public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity {

    protected P mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (onCreatePresenter() != null) {
            mPresenter = onCreatePresenter();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.unSubscribe();
        }
    }

    protected abstract P onCreatePresenter();
}

通过泛型确定一个Presenter的类型,然后使用抽象方法onCreatePresenter对其进行赋值,最后在onDestroy方法中进行资源的释放。继承这个BaseActivity类的Activity,就不用每次都在onDestroy进行同样的操作啦~达到简洁的目的。同理,Fragment中也是同样的,只是在生命周期的onResume和onPause中分别进行Presenter的赋值和资源的释放。这里我就不贴代码,可以上我的Github看。\

Retrofit引擎封装

/**
 * Created by Fangzheng on 2017/9/21.
 */

public class ApiEngine {
    private volatile static ApiEngine apiEngine;
    private Retrofit retrofit;

    private ApiEngine() {

        //日志拦截器
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        //缓存
        int size = 1024 * 1024 * 100;
        File cacheFile = new File(App.getContext().getCacheDir(), "OkHttpCache");
        Cache cache = new Cache(cacheFile, size);

        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(12, TimeUnit.SECONDS)
                .writeTimeout(12, TimeUnit.SECONDS)
                .writeTimeout(12, TimeUnit.SECONDS)
                .addNetworkInterceptor(new NetworkInterceptor())
                .addInterceptor(loggingInterceptor)
                .cache(cache)
                .build();

        retrofit = new Retrofit.Builder()
                .baseUrl(ApiService.BASE_URL)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
    }

    public static ApiEngine getInstance() {
        if (apiEngine == null) {
            synchronized (ApiEngine.class) {
                if (apiEngine == null) {
                    apiEngine = new ApiEngine();
                }
            }
        }
        return apiEngine;
    }

    public ApiService getApiService() {
        return retrofit.create(ApiService.class);
    }

}

用了单例模式,在构造方法中只初始化一次Retrofit和Okhttp。双重锁的方式获取单例,然后再根据需要获取ApiService,如果你有很多个不同源的API,那就可以创建多个getXXXXApiService。

结语

看下运行效果

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

推荐阅读更多精彩内容

  • 前言 首先,阅读本篇文章前,建议你先去了解一下MVP这个设计模式。当然,也可以先看看我前面的文章_ 传送门 另外,...
    Fi7z阅读 3,644评论 4 37
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,472评论 25 707
  • 青石板街的邂逅 你一袭青衣,面纱微扬 拔剑,银蛇游动 割裂的颈项,你踩着鲜血而过 虚空颤动,几里外的紫竹林 我翻转...
    苏格拉风掠影阅读 698评论 43 30
  • 1、感恩父母给予我生命。 2、感恩祖辈将我养育。 3、感恩母亲、祖母为家的巨大付出。 4、感恩妻子对我一直的包容与...
    朱晓军阅读 130评论 0 0
  • Hi,你好哇,暖先生。请允许我这么称呼你,在遇见你之前。 今天是个特别的日子,5月20号,上午9点21分,并没有刻...
    赵无稽阅读 254评论 0 2