(三)安卓框架搭建之MVP+Retrofit+RxJava基础

来源:互联网 发布:用相对坐标编程图例 编辑:程序博客网 时间:2024/05/18 02:58

上一篇,算是完成了准备工作,那么这篇就来说说MVP和RxJava的封装了。首先看看接口返回数据的格式:

{"code" : 1,"message" : "请求成功!" ,"data" : {    "name": "张三",    "age": 3  }}

code、message、data标准的三大门神。一般是以这种格式返回数据。数据格式的统一利于封装,以此数据格式为准的实体基类如下
在dataframework内新建包model和BaseResponseBean类。

Paste_Image.png

package com.example.burro.demo.dataframework.model;/**基类 泛型T为实体数据 * Created by ex.zhong on 2017/9/23. */public class BaseResponseBean<T> {    private int code;    private String message;    private T data;    public int getCode() {        return code;    }    public void setCode(int code) {        this.code = code;    }    public String getMessage() {        return message;    }    public void setMessage(String message) {        this.message = message;    }    public T getData() {        return data;    }    public void setData(T data) {        this.data = data;    }

由于后面的demo用到豆瓣的API,很遗憾它的格式并非上述标准的格式。在项目中返回的数据用了继承父类方式,而非上面的泛型方式,为了区分。新的父类名字我改为BaseResultBean,上面的标准格式基类我仍旧保存到demo中,如果更换的话,那也是分分钟的事情。后面案例和讲解也将使用BaseResultBean,其内容如下:

package com.example.burro.demo.dataframework.model;/**返回数据父类。子类可继承 * Created by ex.zhong on 2017/9/23. */public class BaseResultBean {    protected int code;    protected String msg;    public BaseResultBean() {    }    public BaseResultBean(int code, String msg) {        this.code = code;        this.msg = msg;    }    public int getCode() {        return code;    }    public void setCode(int code) {        this.code = code;    }    public String getMsg() {        return msg;    }    public void setMsg(String msg) {        this.msg = msg;    }}

当然,项目中要根据实际数据为准来调整调整字段、结构等。没有必要过于纠结数据格式问题,换汤不换药,道理是一样的。

下面进行MVP相关内容的讲解!

依赖包引入:

项目的build.gradle增加如下appframework

        /*rx-android-java*/        rxjava                : 'io.reactivex:rxjava:1.1.0',        rxandroid             : 'io.reactivex:rxandroid:1.1.0',        retrofit              : 'com.squareup.retrofit2:retrofit:2.0.2',        converter_gson        : 'com.squareup.retrofit2:converter-gson:2.0.2',        adapter_rxjava        : 'com.squareup.retrofit2:adapter-rxjava:2.0.2',        //compile 'com.google.code.gson:gson:2.6.2'        logging_interceptor   : 'com.squareup.okhttp3:logging-interceptor:3.3.0',        spots_dialog          : 'com.github.d-max:spots-dialog:0.7@aar',

dataframework的build.gradle增加如下

    compile deps.rxjava    compile deps.rxandroid    compile deps.retrofit    compile deps.converter_gson    compile deps.adapter_rxjava    compile deps.logging_interceptor    compile deps.spots_dialog    compile deps.annotation

BaseView

Paste_Image.png
写之前需要在appframework下新建包mvp,mvp下新建三个包contract,presenter,view
在view下新建接口BaseView,Baseview接口内的方法是页面内【Activity或者Fragment】需要执行的通用方法。这里先定义一个 showError(BaseResultBean resultBean); 返回正确情况有很多种,在实现类中增加,若错误,我们要统一处理。所以showError(BaseResultBean resultBean)方法是全局共有的。

package com.example.burro.demo.appframework.mvp.view;import com.example.burro.demo.dataframework.model.BaseResultBean;/**View接口 * Created by ex.zhong on 2017/9/23. */public interface BaseView {    void showError(BaseResultBean  resultBean);}

BasePresenter

Presenter和View创建类似,
在presenter下新建IPresenter,IPresenter attachView(T view); void detachView();两个方法是全局共有的

package com.example.burro.demo.appframework.mvp.presenter;import com.example.burro.demo.appframework.mvp.view.BaseView;/**Presenter接口 * 注:在创建presenter时绑定,在页面destroy()时解绑。 * Created by ex.zhong on 2017/9/23. */public interface IPresenter<T extends BaseView> {    void attachView(T view);    void detachView();}

因为几乎每个Presenter实现类里都要处理绑定和解绑事件,所以我们要把这个处理过程提取出来,此处写一个基类BasePresenter统一管理,在presenter下新建BasePresenter实现IPresenter

package com.example.burro.demo.appframework.mvp.presenter;import com.example.burro.demo.appframework.mvp.view.BaseView;/** * Presenter基类。目的是统一处理绑定和解绑 * Created by ex.zhong on 2017/9/23. */public class BasePresenter<T extends BaseView> implements IPresenter<T> {    protected T mView;    @Override    public void attachView(T mView) {        mView = mView;    }    @Override    public void detachView() {        mView = null;    }  //   public boolean isViewAttached() {  //      return mView != null;  //    }  //    public void checkViewAttached() {  //        if (!isViewAttached()) throw new    // MvpViewNotAttachedException();  //  }![Uploading 04_766476.png . . .]  //   public static class  //MvpViewNotAttachedException extends //RuntimeException {  //        public MvpViewNotAttachedException() {  //           super("Please call   //Presenter.attachView(MvpView) before" +  //                    " requesting data to the   //Presenter");  //        }  //    }}

【备注:这里的checkViewAttached(),在rxJava未引入之前使用。目的是判断页面是否还存在,若不存在则不执行。rxJava中对此作了处理。只需调用解绑方法即可。在此处稍作提及,后面我会直接删掉此内容】

稍后会写一个测试类TestActivty结合豆瓣的API。来详解mvp的使用。在此之前先来封装一下BaseActivity,因为一般都是在BaseActivity中进行Presenter和View的初始化绑定

BaseActivity

在appframework新建ui包,包内新建BaseActivity抽象类

类中都是基本的要素,且注释较为详细,容易理解。其中有个别方法是为了和后面内容对接,直接贴代码:

package com.example.burro.demo.appframework.ui;import android.app.Activity;import android.os.Bundle;import android.support.annotation.Nullable;import android.support.v7.app.AppCompatActivity;import android.support.v7.widget.Toolbar;import android.view.MenuItem;import android.view.View;import com.example.burro.demo.appframework.BaseApplication;import com.example.burro.demo.appframework.mvp.presenter.BasePresenter;import com.example.burro.demo.appframework.mvp.view.BaseView;import butterknife.ButterKnife;import butterknife.Unbinder;/** * BaseActivity Activity基类 * butterKnife的绑定 初始方法的设定 presentet和view的绑定 * Created by ex.zhong on 2017/9/23. */public abstract class BaseActivity<T extends BasePresenter> extends AppCompatActivity implements BaseView,Toolbar.OnMenuItemClickListener {    protected T mPresenter;    protected Activity mContext;    private Unbinder mUnbinder;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(initLayoutInflater());        mUnbinder = ButterKnife.bind(this);        mContext = this;        createPresenter();        if (mPresenter != null) mPresenter.attachView(this);        BaseApplication.getInstance().addActivity(this);        initParams();        initViews();    }    protected abstract int initLayoutInflater(); //初始化布局    protected abstract void initParams(); //初始化参数    protected abstract void initViews();  //初始化控件    protected abstract void createPresenter(); //创建presenter    /**     * @param toolbar toolbar 控件     * @param title   标题     */    protected void setToolBar(Toolbar toolbar, String title) {        if (toolbar != null) {            if (title != null) toolbar.setTitle(title);            setSupportActionBar(toolbar);            toolbar.setOnMenuItemClickListener(this);            getSupportActionBar().setDisplayHomeAsUpEnabled(true);            getSupportActionBar().setDisplayShowHomeEnabled(true);            toolbar.setNavigationOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View view) {                    onBackPressed();                }            });        }    }    //toolbar右侧menu点击事件    @Override    public boolean onMenuItemClick(MenuItem item) {        return false;    }      //统一处理错误信息    public void handleError(BaseResultBean errResult) {        if (errResult == null) return;        if (this == null) return;        //可以分门别类的处理 错误消息,如session过期,跳转到登录页面。其他情况提示即可        ToastUtils.showToast(mContext, errResult.getMsg());    }    @Override    protected void onDestroy() {        if (mPresenter != null) mPresenter.detachView();        if (mUnbinder != null) mUnbinder.unbind();        super.onDestroy();    }}

在biz新建测试类

新建内容如下 biz/test/view/TestActivity、biz/test/TestContract、biz/test/TestPresenterImpl

1.包内新建TestActivity继承BaseActivity.TestActivity

package com.example.burro.demo.appbiz.test.view;import com.example.burro.demo.appbiz.R;import com.example.burro.demo.appbiz.R2;import com.example.burro.demo.appbiz.test.TestContract;import com.example.burro.demo.appbiz.test.TestPresenterImpl;import com.example.burro.demo.appframework.ui.BaseActivity;import com.example.burro.demo.appframework.util.LogUtils;import com.example.burro.demo.databiz.model.test.MovieListBean;import com.example.burro.demo.dataframework.model.BaseResultBean;import butterknife.OnClick;/**测试页面 * Created by ex.zhong on 2017/9/23. */public class TestActivity extends BaseActivity<TestPresenterImpl> implements TestContract.View{    @Override    protected int initLayoutInflater() {        return R.layout.activity_test;    }    @Override    protected void initParams() {    }    @Override    protected void initViews() {    }    @Override    protected void createPresenter() {        mPresenter = new TestPresenterImpl();    }    @Override    public void showError(BaseResultBean resultBean) {         //错误处理        handleError(resultBean);    }    @Override    public void setMovieListData(MovieListBean bean) {        LogUtils.i("TAG",bean==null?"":bean.toString());    }    @OnClick(R2.id.btnTest)    public void getMovieListData(){        mPresenter.getMovieListData(1,15);    }}

这里的showError(),是BasePresenter中的回调方法,用来统一处理错误情况,若页面有RecycalView并正在刷新的情况,也可在此处结束刷新。因为每个页面都会showError(),所以我们需要在BaseActivity里增加统一处理的方法handleError(resultBean),内容如下:

    //统一处理错误信息    public void handleError(BaseResultBean errResult) {        if (errResult == null) return;        if (this == null) return;        //可以分门别类的处理 错误消息,如session过期,跳转到登录页面。其他情况提示即可        ToastUtils.showToast(mContext, errResult.getMsg());    }

值得强调的是,增加错误结果统一处理很有必要,也很少有人注意这点,我们后面网络请求错误结果的返回也会与此对接。此处默认是给出Toast提示信息,当然还有很多其他操作,正如注释所说:如果errResult的code是session过期的标识,那么我们给出提示的同时也会跳转至登录页面等等。

2.TestContract:

Contract:d单词意思为契约、协议。TestContract即协议类,定制mvp各层接口和实现方法。说白了,就是把v层和p层需要实现的方法统一在一块,方便管理,也起到了解耦作用。

package com.example.burro.demo.appbiz.test;import com.example.burro.demo.appframework.mvp.presenter.IPresenter;import com.example.burro.demo.appframework.mvp.view.BaseView;import com.example.burro.demo.databiz.model.test.MovieListBean;/**协议类,定制mvp各层接口和实现方法 * Contract:d单词意思为契约 协议 * 接口View内 定义实现view内所需方法 * 接口Presenter 定义实现presenter内所需的方法 * Created by ex.zhong on 2017/9/23. */public class TestContract {    public interface View extends BaseView {        void setMovieListData(MovieListBean bean);    }    public interface Presenter extends IPresenter<View> {        void getMovieListData(int start,int count);    }}

3.TestPresenterImpl:

TestPresenterImpl继承自BasePresenter,初级版本如下:

package com.example.burro.demo.appbiz.test;import com.example.burro.demo.appframework.mvp.presenter.BasePresenter;import com.example.burro.demo.appbiz.test.TestContract.*;import com.example.burro.demo.appframework.util.LogUtils;import com.example.burro.demo.appframework.util.StringUtils;import com.example.burro.demo.databiz.model.test.MovieListBean;import com.example.burro.demo.databiz.service.ApiService;import com.example.burro.demo.dataframework.http.HttpConfig;import java.util.HashMap;import retrofit2.Retrofit;import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;import retrofit2.converter.gson.GsonConverterFactory;import rx.Subscriber;import rx.android.schedulers.AndroidSchedulers;import rx.schedulers.Schedulers;/**测试presenter * Created by ex.zhong on 2017/9/23. */public class TestPresenterImpl extends BasePresenter<View> implements Presenter {    @Override    public void getMovieListData(int start, int count) {        Retrofit retrofit = new Retrofit.Builder()                .baseUrl(HttpConfig.BASE_URL)                .addConverterFactory(GsonConverterFactory.create())                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())                .build();        ApiService movieService = retrofit.create(ApiService.class);        HashMap<String,String> map=new HashMap<>();        map.put("start", StringUtils.getString(start));        map.put("count",StringUtils.getString(count));        movieService.getMovieListData(map)                .subscribeOn(Schedulers.io())                .observeOn(AndroidSchedulers.mainThread())                .subscribe(new Subscriber<MovieListBean>() {                    @Override                    public void onStart() {                        //请求开始                        LogUtils.i("TestPresenterImpl","onStart()");                    }                    @Override                    public void onCompleted() {                       // //请求完成                        LogUtils.i("TestPresenterImpl","onCompleted()");                    }                    @Override                    public void onError(Throwable e) {                        // //请求异常                        LogUtils.i("TestPresenterImpl","onError()");                    }                    @Override                    public void onNext(MovieListBean movieListBean) {                        // //请求OK,执行                        LogUtils.i("TestPresenterImpl","onNext()");                        mView.setMovieListData(movieListBean);                    }                });    }}

文中用到的豆瓣电影TOP250的URL为:http://api.douban.com/v2/movie/top250?start=1&count=15

其他几个主要辅助的类或资源分别为如下:

布局文件activity_test.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.example.burro.demo.appbiz.test.view.TestActivity">    <android.support.design.widget.AppBarLayout        android:id="@+id/appBarLayout"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:theme="@style/AppTheme.AppBarOverlay">        <android.support.v7.widget.Toolbar            android:id="@+id/toolbar"            android:layout_width="match_parent"            android:layout_height="?attr/actionBarSize"            android:background="?attr/colorPrimary"            app:popupTheme="@style/AppTheme.PopupOverlay" />    </android.support.design.widget.AppBarLayout>    <Button        android:id="@+id/btnTest"        android:layout_below="@id/appBarLayout"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="请求数据"        /></RelativeLayout>

其中AppTheme.PopupOver,AppTheme.AppBarOverlay等是toolbar相关的样式资源,请到demo中查看,此处不一一列出

ApiService接口类 databiz/service/ApiService:

package com.example.burro.demo.databiz.service;import com.example.burro.demo.databiz.model.test.MovieListBean;import java.util.HashMap;import java.util.Map;import retrofit2.http.GET;import retrofit2.http.Path;import retrofit2.http.Query;import retrofit2.http.QueryMap;import rx.Observable;/** *  存放访问网络的方法 * Created by ex.zhong on 2017/9/24. */public interface ApiService {    public static final String URL_MOVIELIST="/v2/movie/top250"; //豆瓣电影top250    @GET(URL_MOVIELIST)    Observable<MovieListBean> getMovieListData(@QueryMap HashMap<String,String> count);}

网络配置类 dataframework/http/HttpConfig :

package com.example.burro.demo.dataframework.http;/** * Created by ex.zhong on 2017/9/24. *放置网络相关配置数据,如IP/端口等 */public class HttpConfig {    public final static String BASE_URL="http://api.douban.com";}

电影列表实体类 databiz/model/test/MovieListBean :

package com.example.burro.demo.databiz.model.test;import com.example.burro.demo.dataframework.model.BaseResultBean;import java.util.List;/**豆瓣电影列表 * Created by ex.zhong on 2017/9/24. */public class MovieListBean extends BaseResultBean{    public List<SubjectsBean> subjects;    public static class SubjectsBean {        /**         * rating : {"max":10,"average":9.6,"stars":"50","min":0}         * genres : ["犯罪","剧情"]         * title : 肖申克的救赎         * casts : [{"alt":"https://movie.douban.com/celebrity/1054521/","avatars":{"small":"https://img3.doubanio.com/img/celebrity/small/17525.jpg","large":"https://img3.doubanio.com/img/celebrity/large/17525.jpg","medium":"https://img3.doubanio.com/img/celebrity/medium/17525.jpg"},"name":"蒂姆·罗宾斯","id":"1054521"},{"alt":"https://movie.douban.com/celebrity/1054534/","avatars":{"small":"https://img3.doubanio.com/img/celebrity/small/34642.jpg","large":"https://img3.doubanio.com/img/celebrity/large/34642.jpg","medium":"https://img3.doubanio.com/img/celebrity/medium/34642.jpg"},"name":"摩根·弗里曼","id":"1054534"},{"alt":"https://movie.douban.com/celebrity/1041179/","avatars":{"small":"https://img1.doubanio.com/img/celebrity/small/5837.jpg","large":"https://img1.doubanio.com/img/celebrity/large/5837.jpg","medium":"https://img1.doubanio.com/img/celebrity/medium/5837.jpg"},"name":"鲍勃·冈顿","id":"1041179"}]         * collect_count : 1107705         * original_title : The Shawshank Redemption         * subtype : movie         * directors : [{"alt":"https://movie.douban.com/celebrity/1047973/","avatars":{"small":"https://img3.doubanio.com/img/celebrity/small/230.jpg","large":"https://img3.doubanio.com/img/celebrity/large/230.jpg","medium":"https://img3.doubanio.com/img/celebrity/medium/230.jpg"},"name":"弗兰克·德拉邦特","id":"1047973"}]         * year : 1994         * images : {"small":"https://img3.doubanio.com/view/movie_poster_cover/ipst/public/p480747492.webp","large":"https://img3.doubanio.com/view/movie_poster_cover/lpst/public/p480747492.webp","medium":"https://img3.doubanio.com/view/movie_poster_cover/spst/public/p480747492.webp"}         * alt : https://movie.douban.com/subject/1292052/         * id : 1292052         */        public RatingBean rating;        public String title;        public int collect_count;        public String original_title;        public String subtype;        public String year;        public ImagesBean images;        public String alt;        public String id;        public List<String> genres;        public List<CastsBean> casts;        public List<DirectorsBean> directors;        public static class RatingBean {            /**             * max : 10             * average : 9.6             * stars : 50             * min : 0             */            public int max;            public double average;            public String stars;            public int min;        }        public static class ImagesBean {            /**             * small : https://img3.doubanio.com/view/movie_poster_cover/ipst/public/p480747492.webp             * large : https://img3.doubanio.com/view/movie_poster_cover/lpst/public/p480747492.webp             * medium : https://img3.doubanio.com/view/movie_poster_cover/spst/public/p480747492.webp             */            public String small;            public String large;            public String medium;        }        public static class CastsBean {            /**             * alt : https://movie.douban.com/celebrity/1054521/             * avatars : {"small":"https://img3.doubanio.com/img/celebrity/small/17525.jpg","large":"https://img3.doubanio.com/img/celebrity/large/17525.jpg","medium":"https://img3.doubanio.com/img/celebrity/medium/17525.jpg"}             * name : 蒂姆·罗宾斯             * id : 1054521             */            public String alt;            public AvatarsBean avatars;            public String name;            public String id;            public static class AvatarsBean {                /**                 * small : https://img3.doubanio.com/img/celebrity/small/17525.jpg                 * large : https://img3.doubanio.com/img/celebrity/large/17525.jpg                 * medium : https://img3.doubanio.com/img/celebrity/medium/17525.jpg                 */                public String small;                public String large;                public String medium;            }        }        public static class DirectorsBean {            /**             * alt : https://movie.douban.com/celebrity/1047973/             * avatars : {"small":"https://img3.doubanio.com/img/celebrity/small/230.jpg","large":"https://img3.doubanio.com/img/celebrity/large/230.jpg","medium":"https://img3.doubanio.com/img/celebrity/medium/230.jpg"}             * name : 弗兰克·德拉邦特             * id : 1047973             */            public String alt;            public AvatarsBeanX avatars;            public String name;            public String id;            public static class AvatarsBeanX {                /**                 * small : https://img3.doubanio.com/img/celebrity/small/230.jpg                 * large : https://img3.doubanio.com/img/celebrity/large/230.jpg                 * medium : https://img3.doubanio.com/img/celebrity/medium/230.jpg                 */                public String small;                public String large;                public String medium;            }        }    }}

点击请求数据按钮。获取到返回的数据如下:

![列表
Uploading 08_178546.png ...]数据
当然,TestPresenterImpl中的内容是重点,其中getMovieListData()方法里的内容是retrofit和rxjava最基本的用法!想必大家多少都见过。我们再来看下rxjava相关的代码,其实它主要做了三个事情。统一管理主线程、工作线程、请求返回后的回调处理!引入rxjava之前,三者都是自己管理。所以说,它的引入极大的简化了我们的工作。

08.png

下一篇将讲述优化封装

相关链接

(四)安卓框架搭建之MVP+Retrofit+RxJava优化

github源码地址

阅读全文
0 0
原创粉丝点击