(三)安卓框架搭建之MVP+Retrofit+RxJava基础
2017-11-26 13:18
495 查看
上一篇,算是完成了准备工作,那么这篇就来说说MVP和RxJava的封装了。首先看看接口返回数据的格式:
code、message、data标准的三大门神。一般是以这种格式返回数据。数据格式的统一利于封装,以此数据格式为准的实体基类如下
在dataframework内新建包model和BaseResponseBean类。
由于后面的demo用到豆瓣的API,很遗憾它的格式并非上述标准的格式。在项目中返回的数据用了继承父类方式,而非上面的泛型方式,为了区分。新的父类名字我改为
当然,项目中要根据实际数据为准来调整调整字段、结构等。没有必要过于纠结数据格式问题,换汤不换药,道理是一样的。
下面进行MVP相关内容的讲解!
dataframework的build.gradle增加如下
写之前需要在
在view下新建接口
在presenter下新建
因为几乎每个Presenter实现类里都要处理绑定和解绑事件,所以我们要把这个处理过程提取出来,此处写一个基类BasePresenter统一管理,在presenter下新建
稍后会写一个测试类TestActivty结合豆瓣的API。来详解mvp的使用。在此之前先来封装一下BaseActivity,因为一般都是在BaseActivity中进行Presenter和View的初始化绑定
类中都是基本的要素,且注释较为详细,容易理解。其中有个别方法是为了和后面内容对接,直接贴代码:
这里的showError(),是BasePresenter中的回调方法,用来统一处理错误情况,若页面有RecycalView并正在刷新的情况,也可在此处结束刷新。因为每个页面都会showError(),所以我们需要在BaseActivity里增加统一处理的方法handleError(resultBean),内容如下:
文中用到的豆瓣电影TOP250的URL为:http://api.douban.com/v2/movie/top250?start=1&count=15
其他几个主要辅助的类或资源分别为如下:
其中AppTheme.PopupOver,AppTheme.AppBarOverlay等是toolbar相关的样式资源,请到demo中查看,此处不一一列出
点击请求数据按钮。获取到返回的数据如下:
![列表
当然,TestPresenterImpl中的内容是重点,其中getMovieListData()方法里的内容是retrofit和rxjava最基本的用法!想必大家多少都见过。我们再来看下rxjava相关的代码,其实它主要做了三个事情。统一管理主线程、工作线程、请求返回后的回调处理!引入rxjava之前,三者都是自己管理。所以说,它的引入极大的简化了我们的工作。
下一篇将讲述优化封装
github源码地址
{ "code" : 1, "message" : "请求成功!" , "data" : { "name": "张三", "age": 3 } }
code、message、data标准的三大门神。一般是以这种格式返回数据。数据格式的统一利于封装,以此数据格式为准的实体基类如下
在dataframework内新建包model和BaseResponseBean类。
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
写之前需要在
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/TestPresenterImpl1.包内新建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; } } } }
点击请求数据按钮。获取到返回的数据如下:
![列表
当然,TestPresenterImpl中的内容是重点,其中getMovieListData()方法里的内容是retrofit和rxjava最基本的用法!想必大家多少都见过。我们再来看下rxjava相关的代码,其实它主要做了三个事情。统一管理主线程、工作线程、请求返回后的回调处理!引入rxjava之前,三者都是自己管理。所以说,它的引入极大的简化了我们的工作。
下一篇将讲述优化封装
相关链接
(四)安卓框架搭建之MVP+Retrofit+RxJava优化github源码地址
相关文章推荐
- Rxjava + retrofit + dagger2 + mvp搭建Android框架
- Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架
- Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架
- 一步步搭建Retrofit+RxJava+MVP网络请求框架(一)
- Retrofit+RXJava+MVP的框架搭建
- 安卓开发框架(MVP+主流框架+基类+工具类)--- Retrofit+RxJava
- 搭建MVP+Retrofit+RxJava框架详解
- 一步步搭建Retrofit+RxJava+MVP网络请求框架(一)
- Android 搭建MVP+Retrofit+RxJava网络请求框架
- Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架
- (安卓) MVP 框架 (Rxjava2+Retrofit)结合 实现网络请求
- Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架
- 安卓项目快速开发框架, MVP + Retrofit + RxJava,Activity 和 Fragment 结合 MVP 模式的完整封装,大大减少代码量
- Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架
- [置顶] Android 一步步搭建MVP+Retrofit+RxJava网络请求框架
- Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架
- Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架
- Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架
- 使用Retrofit+RxJava搭建简单的MVP网络请求框架
- Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架