Android Retrofit、RxJava与MVP封装

一、前言

说起Retrofit与RxJava的封装就不得不说起MVP,由于自己老是记不住搭建MVP的过程,自己理解的深度不够加上感觉每次写项目都要重复一遍搭建过程,所以记录一次,加深理解。

二、什么是MVP

网络上有很多讲解MVP的文章,各有千秋,其实这只是一个开发的模式,主要是为了简化开发中的层级与代码的耦合。各自有各自的理解,并没有明确的定义。在个人看来,其实就是将以前在活动的做的事进行分层,类似于后端开发中MVC模式划分的层级。MVP模式将活动、碎片等作为V层(View)只作为展示数据的层级,而P层(Presenter)作为V层与P层中间层级,负责收集V层的数据进行处理及从M层(Model)获取数据传递给V层。然而目前来看普通的App主要进行网络请求获取数据,或者通过一些工具类就能进行数据存储,所以个人感觉其实M层的作用十分微弱,进而P层就显得尤为重要了。

MVP的优点
  • 层次清晰,解耦
MVP的缺点
  • 造成P层臃肿
  • 代码冗余

接下来进行整个项目的搭建。

三、编写基础接口

1、接口的作用

为什么要使用接口?在MVP中接口是十分重要的东西,最顶层的接口不仅约束了其子接口,还为代码的复用提供了有效的作用,总的来说在MVP中的编程是面向接口的编程。这里编写了IBaseViewIBasePresenter作为V层、P层的顶级接口。

public interface IBaseView {

    void showToastMsg(String msg);

    void showEmptyView();

    void showErrorView();

    void showLoadingView();

    void showNoNetWorkView();

    void showSuccessView();
}
public interface IBasePresenter<T extends IBaseView> {

    void attachView(T view);

    void detachView();
}
2、添加依赖

说起MVP基础的搭建就一定少不了Rx系列与Retrofit了,截至2019年12月22日,Retrofit2的最新版本为 2.7.0,RxJava2的最新版本为2.2.15,RxAndroid的版本为2.1.1,整个项目主要添加的依赖如下:

所有依赖

有很多博客说还要添加OkHttp3的依赖,由于Retrofit2本就是根据OkHttp3来搭建的,所以可以看到依赖里其实有OkHttp3的依赖的,不必再进行二次添加了。


OkHttp3依赖

四、编写BaseActivity与BaseFragment

public abstract class BaseActivity<T extends IBasePresenter> extends AppCompatActivity implements IBaseView {

    protected T mPresenter;
    private Toast mToast;
    private AbstractUiLoader mUiLoader;
    private Unbinder mUnBinder;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (mUiLoader == null) {
            mUiLoader = new AbstractUiLoader(this) {
                @Override
                public View getSuccessView(ViewGroup container) {
                    return LayoutInflater.from(getContext()).inflate(getLayoutId(), container, false);
                }
            };
        }
        mUiLoader.setStatus(AbstractUiLoader.Status.SUCCESS);
        setContentView(mUiLoader);
        mUnBinder = ButterKnife.bind(this);

        initEvent();
    }

    protected abstract int getLayoutId();

    protected abstract void initEvent();

    @Override
    public void showToastMsg(String msg) {
        if (mToast == null) {
            mToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
            mToast.show();
        }else {
            mToast.setText(msg);
            mToast.show();
        }
    }

    @Override
    protected void onDestroy() {
        if (mUnBinder != null) {
            mUnBinder.unbind();
        }
        if (mPresenter != null) {
            mPresenter.detachView();
            mPresenter = null;
        }
        super.onDestroy();
    }
    
    //实现IBaseView中的方法,使用mUiLoader进行视图切换
}

这部分代码其实就是对Activity的一些通用方法的封装,BaseFragment同理。AbstractUiLoader是一个视图切换类继承至FrameLayout主要是对布局视图的统一切换,详情点击 这儿

五、编写Retrofit2基础

这里使用 玩Android 开放API 的首页文章进行举例。由于网站返回的json具有统一的格式,所以需要写一个基础的数据实体类:BaseData,用来接收数据

public class BaseData<T> {

    private T data;
    private int errorCode;
    private String errorMsg;
    
    //get和set方法
}

通过示例可以看出首页的一级bean为下图所示,所以再定义一个PageBean


返回的json
public class PageBean {

    private int curPage;
    private List<ArticleBean> datas;
    private int offset;
    private boolean over;
    private int pageCount;
    private int size;
    private int total;
    
    //get和set方法
}

接着定义文章的bean

public class ArticleBean {

    private String apkLink;
    private int audit;
    private String author;
    private int chapterId;
    private String chapterName;
    private boolean collect;
    private int courseId;
    private String desc;
    private String envelopePic;
    
    //get和set方法和剩余的字段
}

最后写一个Retrofit2的接口

public interface Api {

    /**
     * 获取主页文章
     * @return Observable<BaseData<PageBean>>
     */
    @GET("article/list/{pageNum}/json")
    Observable<BaseData<PageBean>> getHomeArticleList(@Path("pageNum") int pageNum);
}

再编写一个Retrofit的工具类用于获取Api的实例

public class RetrofitUtil {

    private static Api instance;
    /**
     * 超时时间
     */
    private static final int TIME_OUT = 10;

    public static Api getApi(){
        if (instance == null) {
            String baseUrl = "https://www.wanandroid.com/";
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
                    .readTimeout(TIME_OUT, TimeUnit.SECONDS);
            instance = new Retrofit.Builder().baseUrl(baseUrl)
                    .client(builder.build())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create()).build().create(Api.class);
        }
        return instance;
    }


}

到此为止,我们就将Retrofit的基础部分编写完成,接着完成RxJava部分。

六、编写RxJava的基础

在MVP中,RxJava主要用于P层进行请求,由于P层是一个普通的Java类,所以要防止视图销毁时P层依然还在进行网络请求,后回调View造成的内存泄露。为什么要编写这个基础的observer呐?因为在订阅的时候一般用的是匿名内部类,而主要使用的方法基本上就只有void onNext(@NonNull T t)这一个方法,所以在这里对它进行封装。

public abstract class BaseObserver<T> extends ResourceObserver<BaseData<T>> {

    private IBaseView mView;

    public BaseObserver(IBaseView view){
        this.mView = view;
    }

    @Override
    protected void onStart() {
        mView.showLoadingView();
        super.onStart();
    }


    @Override
    public void onNext(BaseData<T> baseData) {
        if (baseData.getErrorCode() == 0){
            onSuccess(baseData.getData());
            mView.showSuccessView();
        }else {
            mView.showErrorView();
        }
    }

    @Override
    public void onError(Throwable e) {
        onFailed(e);
        mView.showToastMsg(e.getMessage());
    }

    @Override
    public void onComplete() {
    }

    public abstract void onSuccess(T data);

    public void onFailed(Throwable e){

    }
}

这里选择继承了ResourceObserver类,进行封装。为什么在构造方法中传入一个IBaseView,主要在于每次获取json后,都能通过code判断是否成功、失败、错误等,所以直接将这些放入Observer中,减少冗余代码。

七、编写BasePresenter层

有了上面的基础,就可以来编写最重要的P层了,在P层中使用RxJava进行线程切换以及与Retrofit进行网络请求,最重要的就是对网络请求与页面回调之间的控制,有著名的 RxLifecycle 能对其进行管理。这里选择自己手动管理。通过CompositeDisposable类的说明可以看出,这个类是为了管理资源的容器,所以可以在BasePresenter的子类中使用subscribeWith()订阅观察对象返回一个观察者加入到CompositeDisposable容器中,页面销毁时使用该对象销毁正在进行的请求。

类说明

public abstract class BasePresenter<T extends IBaseView> implements IBasePresenter<T> {

    protected T mView;
    private CompositeDisposable mCompositeDisposable;
    protected Api mApi = RetrofitUtil.getApi();

    @Override
    public void attachView(T view) {
        this.mView = view;
    }

    @Override
    public void detachView() {
        if (mView != null) {
            mView = null;
        }
        if (mCompositeDisposable != null) {
            mCompositeDisposable.clear();
        }
        if (mApi != null){
            mApi = null;
        }
    }

    protected void addRequest(Disposable disposable){
        if (mCompositeDisposable == null) {
            mCompositeDisposable = new CompositeDisposable();
        }
        mCompositeDisposable.add(disposable);
    }
}

八、实战

在实际中,由于面向接口编程所以,每个页面都会有ViewPresenter接口为了方便管理就有了Contract接口

public interface MainContract {

    interface View extends IBaseView {
        void showArticleList(List<ArticleBean> articleList);
    }

    interface Presenter extends IBasePresenter<View> {
        void getHomeArticle();
    }
}

活动(V层):

public class MainActivity extends BaseActivity<MainContract.Presenter> implements MainContract.View {

    @BindView(R.id.rv_main)
    RecyclerView mMainRv;

    private MainRvAdapter mAdapter;

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

    @Override
    protected void initEvent() {

        mPresenter = new MainPresenter();
        mPresenter.attachView(this);

        mMainRv.setLayoutManager(new LinearLayoutManager(this));
        mAdapter = new MainRvAdapter();
        mMainRv.setAdapter(mAdapter);
        mMainRv.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));

        mPresenter.getHomeArticle();
    }

    @Override
    public void showArticleList(List<ArticleBean> articleList) {
        mAdapter.setNewData(articleList);
    }
}

P层:

public class MainPresenter extends BasePresenter<MainContract.View> implements MainContract.Presenter{

    @Override
    public void getHomeArticle() {
        addRequest(mApi.getHomeArticleList(1)
                .compose(RxUtil.schedulerTransformer())
                .subscribeWith(new BaseObserver<PageBean>(mView) {
            @Override
            public void onSuccess(PageBean data) {
                mView.showArticleList(data.getDatas());
            }}));
    }
}

整个项目的源码点 这儿

九、参考

WanAndroid
喜马拉雅
Google样例
欢迎关注我的个人小站,不足之处希望大家指出。

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

推荐阅读更多精彩内容