一.介绍
目前使用较为广泛的网络请求框架 MVP+Retrofit2+okhttp3+Rxjava2,我于2017年也加入了使用行列,在网上找了许多案例,实际项目开发中解决了一些所谓的坑,总结了些内容与大家共享一下。
1.什么是MVP?
在图中有三个模块view(界面),presenter(控制层),model(数据源)。他们在这个项目中中担任什么角色呢?
2. MVP运行的过程
- Model: 数据层,负责与网络层和数据库层的逻辑交互。
- View: UI层,显示数据, 并向Presenter报告用户行为。
- Presenter: 从Model拿数据,应用到UI层,管理UI的状态,响应用户的行为。
- 用户在view层告诉presenter我要数据
- presenter告诉model我要数据
- model访问网络得到了数据再通知presenter给你我取到的数据
- presenter 处理好数据 再把数据传递给view
- 最后view将拿到的数据显示出来给用户观看
3.MVP和MVC的区别
MVC首先就是理解比较容易,技术含量不高,这对开发和维护来说成本较低也易于维护与修改。表现层与业务层分离各司其职,对开发来说很有利,但是MVC的每个构件在使用之前都需要经过彻底的测试,代码难以复用。
在MVP里,Presenter完全把Model和View进行了分离,主要的程序逻辑在Presenter里实现,而且Presenter与具体的view是没有一点关联的,而是通过定义好的接口进行交互,从而使得在变更View的同时可以保持Presenter不变,可以复用。
在MVP模式里,View只应该有简单的Set/Get方法,用户输入和设置显示的内容,除此不应该有更多的内容,绝不允许直接访问Model,这就是与MVC最大的不同之处。
二.框架的搭建
1.搭建框架的依赖
采用Retrofit2+Rxjava2+Rxandroid+okhttp3 搭建网络请求框架
implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
2.创建工具类:RetrofitUtils、OkHttp3Utils
- RetrofitUtils和OkHttp3Utils的特性:
- 使用okhttp3作为请求接口;
- 以观察者模式创建实例;
- 使用gson作为数据转换器;
- 添加各种拦截器,如日志拦截,请求头拦截,请求参数拦截等等
- 开启数据缓存,无网络时可从缓存读取数据;
- 辅助类静态方法获取OkHttp3Utils实例。
详细代码如下:
RetrofitUtils工具类封装
封装可以设置多个BaseUrl,应对项目对接多业务方的需求
public abstract class RetrofitUtils {
private Retrofit mRetrofit = null;
private Retrofit mRetrofit2 = null;
private OkHttpClient mOkHttpClient;
/**
* 获取Retrofit对象
*
* @return
*/
public Retrofit getRetrofit() {
if (null == mRetrofit) {
if (null == mOkHttpClient) {
OkHttp3Utils okHttp3Utils = new OkHttp3Utils();
mOkHttpClient = okHttp3Utils.getOkHttpClient();
}
mRetrofit = new Retrofit.Builder()
.baseUrl(BaseUrlUtil.BaseServiceUrl)
.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(mOkHttpClient)
.build();
}
return mRetrofit;
}
/**
* 获取Retrofit对象
*这个主要是为了应对多个BaseUrl而准备的
* @return
*/
public Retrofit getRetrofit2() {
if (null == mRetrofit2) {
if (null == mOkHttpClient) {
OkHttp3Utils okHttp3Utils = new OkHttp3Utils();
mOkHttpClient = okHttp3Utils.getOkHttpClient();
}
mRetrofit2 = new Retrofit.Builder()
.baseUrl(BaseUrlUtil.BaseServiceUrl2)
.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(mOkHttpClient)
.build();
}
return mRetrofit2;
}
public class NullOnEmptyConverterFactory extends Converter.Factory {
@Override
public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
final Converter delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
return new Converter() {
@Override
public Object convert(ResponseBody body) throws IOException {
if (body.contentLength() == 0) return null;
return delegate.convert(body);
}
};
}
}
}
OkHttp3Utils工具类封装
自定义拦截器,可以按照自己的需求设置请求头的参数,同时对cookies做了自动化管理,对cookers管理更方便
public class OkHttp3Utils {
private OkHttpClient mOkHttpClient;
Activity activity = AppManager.topActivity();
private Handler updateHandler = new Handler() {
@Override
public void dispatchMessage(Message msg) {
super.dispatchMessage(msg);
if (msg.what == 401) {
//401 token失效
if (activity != null && !activity.isDestroyed()) {
try {
PreferenceHelper.write(PreferenceHelper.DEFAULT_FILE_NAME, AppConfig.PREFER_TOKEN_TAG, "");
DialogView dialogView = new DialogView(activity, 180, 180, R.layout.my_dialog, R.style.dialog) {
@Override
public void isdismiss(int tag) {
if (tag == DialogView.CANCEL_BUTTON_CLICK) {
}
}
};
dialogView.showdialog2("温馨提示", "登录失效,请重新登录", "去登录", "");
} catch (Exception es) {
es.printStackTrace();
}
}
} else if (msg.what == 300) {
Toast.makeText(activity, "暂无网络", Toast.LENGTH_SHORT).show();
}
}
};
//设置缓存目录
private File cacheDirectory = new File(MyApplication.getInstance().getApplicationContext().getCacheDir().getAbsolutePath(), "MyCache");
private Cache cache = new Cache(cacheDirectory, 10 * 1024 * 1024);
/**
* 获取OkHttpClient对象
*
* @return
*/
public OkHttpClient getOkHttpClient() {
if (null == mOkHttpClient) {
//同样okhttp3后也使用build设计模式
mOkHttpClient = new OkHttpClient.Builder()
//添加拦截器
.addInterceptor(new MyIntercepter())
//设置一个自动管理cookies的管理器
.cookieJar(new CookiesManager())
//添加网络连接器
// .addNetworkInterceptor(new CookiesInterceptor(MyApplication.getInstance().getApplicationContext()))
//设置请求读写的超时时间
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.cache(cache)//设置缓存
.retryOnConnectionFailure(true)//自动重试
.build();
}
return mOkHttpClient;
}
/**
* 拦截器
*/
private class MyIntercepter implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!isNetworkReachable(MyApplication.instance.getApplicationContext())) {
updateHandler.sendEmptyMessage(300);
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)//无网络时只从缓存中读取
.build();
}
Request.Builder RequestBuilder = request.newBuilder();
Request build;
build = RequestBuilder
.removeHeader("User-Agent")
.addHeader("User-Agent", getUserAgent())
.addHeader("Authorization", "")
.build();
Response response = chain.proceed(build);
int code = response.code();
//对个别链接地址做处理
HttpUrl url = response.request().url();
System.out.println("我的网址"+url);
updateHandler.sendEmptyMessage(code);
if (code == 401) {
//跳转到登录页面
updateHandler.sendEmptyMessage(401);
} else if (code == 402) {
//跳转到开户审核中界面
updateHandler.sendEmptyMessage(402);
} else if (code == 403) {
//跳转到开户界面
updateHandler.sendEmptyMessage(403);
}
return response;
}
}
private static String getUserAgent() {
String userAgent = "";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
try {
userAgent = WebSettings.getDefaultUserAgent(MyApplication.getInstance().getApplicationContext());
} catch (Exception e) {
userAgent = System.getProperty("http.agent");
}
} else {
userAgent = System.getProperty("http.agent");
}
StringBuffer sb = new StringBuffer();
for (int i = 0, length = userAgent.length(); i < length; i++) {
char c = userAgent.charAt(i);
if (c <= '\u001f' || c >= '\u007f') {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* 自动管理Cookies
*/
private class CookiesManager implements CookieJar {
private final PersistentCookieStore cookieStore = new PersistentCookieStore(MyApplication.getInstance().getApplicationContext());
@Override
public void saveFromResponse(HttpUrl url, List cookies) {
if (cookies != null && cookies.size() > 0) {
for (Cookie item : cookies) {
cookieStore.add(url, item);
}
}
}
@Override
public List loadForRequest(HttpUrl url) {
List cookies = cookieStore.get(url);
return cookies;
}
}
/**
* 判断网络是否可用
*
* @param context Context对象
*/
@RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
public Boolean isNetworkReachable(Context context) {
ConnectivityManager cm = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo current = cm.getActiveNetworkInfo();
if (current == null) {
return false;
}
return (current.isAvailable());
}
}
线程切换操作的封装
public class BaseNetWork extends RetrofitUtils{
/**https://github.com/r17171709/Retrofit2Demo
* 插入观察者
* @param observable
* @param observer
* @param
*/
public void setSubscribe(Observable observable, Observer observer) {
observable.subscribeOn(Schedulers.io())
.subscribeOn(Schedulers.newThread())//子线程访问网络
.observeOn(AndroidSchedulers.mainThread())//回调到主线程
.subscribe(observer);
}
}
下面就是实体接口的调用
public class UserNetWork extends BaseNetWork {
protected final NetService service = getRetrofit().create(NetService.class);
private interface NetService {
//获取首页轮播图
@GET("api/AppPubilc/get_lunbotu")
Observable toGetLunBoTuEntity();
}
//首页轮播图
public void toGetLunBoTuEntity(Observer observer) {
setSubscribe(service.toGetLunBoTuEntity(), observer);
}
}
以上就是Retrofit2+Rxjava2+Rxandroid+okhttp3的高度封装的网络框架,自定义拦截器可以拦截请求地址,动态添加请求头里的参数,同时对网络请求响应code码做相应的操作。面对一个项目对接多个业务方,存在多个BaseUrl,该网络框架封装了可以设置多个BaseUrl的。
那么Retrofit2网络框架的网络框架搭建完了,下面来看一下MVP架构的设计吧,不要走开!
下面是项目的整体架构图
创建BaseView基类,用于添加自定义回调,根据需求可做扩展,此处只封装了些最为常用的方法
public interface BaseView {
void showLoadingDialog(String msg);
void dismissLoadingDialog();
/**
* 显示错误信息
*
* @param msg
*/
void showError(String msg);
/**
* 错误码
*/
void onErrorCode(BaseModel model);
}
创建Presenter基类,提供M层和V层通讯桥梁
public interface BasePresenter {
//默认初始化
void start();
//Activity关闭把view对象置为空
void detach();
//将网络请求的每一个disposable添加进入CompositeDisposable,再退出时候一并注销
void addDisposable(Disposable subscription);
//注销所有请求
void unDisposable();
}
创建一个PresenterImpl,用于统一处理网络请求的生命周期,在activity退出时统一注销观察者模式,解绑观察者的情况下调用unDisposable()统一解绑,防止Rx造成的内存泄漏。
/**
* 总控制层
* @param
*/
public abstract class BasePresenterImpl implements BasePresenter {
protected V view;
public BasePresenterImpl(V view) {
this.view = view;
start();
}
@Override
public void detach() {
this.view = null;
unDisposable();
}
@Override
public void start() {
}
//将所有正在处理的Subscription都添加到CompositeSubscription中。统一退出的时候注销观察
private CompositeDisposable mCompositeDisposable;
/**
* 将Disposable添加
*
* @param subscription
*/
@Override
public void addDisposable(Disposable subscription) {
//csb 如果解绑了的话添加 sb 需要新的实例否则绑定时无效的
if (mCompositeDisposable == null || mCompositeDisposable.isDisposed()) {
mCompositeDisposable = new CompositeDisposable();
}
mCompositeDisposable.add(subscription);
}
/**
* 在界面退出等需要解绑观察者的情况下调用此方法统一解绑,防止Rx造成的内存泄漏
*/
@Override
public void unDisposable() {
if (mCompositeDisposable != null) {
mCompositeDisposable.dispose();
}
}
}
创建实体类基类,统一处理后台接口返回的数据,做统一处理
public class TradeSimpleResult implements Serializable{
/**
* Success : false
* StatusCode : 500
* Message : 处理失败
* ErrorInfo : {"ErrorMessage":"请输入真实的身份证姓名信息","ErrorCode":"-1"}
*/
private boolean Success;
private int StatusCode;
private String Message;
private ErrorInfoBean ErrorInfo;
public boolean isSuccess() {
return Success;
}
public void setSuccess(boolean Success) {
this.Success = Success;
}
public int getStatusCode() {
return StatusCode;
}
public void setStatusCode(int StatusCode) {
this.StatusCode = StatusCode;
}
public String getMessage() {
return Message;
}
public void setMessage(String Message) {
this.Message = Message;
}
public ErrorInfoBean getErrorInfo() {
return ErrorInfo;
}
public void setErrorInfo(ErrorInfoBean ErrorInfo) {
this.ErrorInfo = ErrorInfo;
}
public static class ErrorInfoBean {
/**
* ErrorMessage : 请输入真实的身份证姓名信息
* ErrorCode : -1
*/
private String ErrorMessage;
private String ErrorCode;
public String getErrorMessage() {
return ErrorMessage;
}
public void setErrorMessage(String ErrorMessage) {
this.ErrorMessage = ErrorMessage;
}
public String getErrorCode() {
return ErrorCode;
}
public void setErrorCode(String ErrorCode) {
this.ErrorCode = ErrorCode;
}
}
}
好了,以上就是MVP架构的搭建以及接口返回数据的统一处理。代码比较多,但是前后逻辑是很连贯的。那么下面就来做一个实际的接口请求看看效果。
public class MainActivity extends LifecycleBaseActivity implements TestContact.view {
private TextView textView;
private HashMap
以上的LifecycleBaseActivity 和 LifecycleBaseFragment大家可以在下面的链接地址里面去看,这个是Goolge官方架构AAC(Android Architecture Component)的生命周期管理框架,Lifecycle类持有Activity 或 Fragment等组件的生命周期信息,并且允许其他对象观察这些信息。Lifecycle内部使用了两个枚举来跟踪其关联组件的生命周期状态:Event和State。祥见下面分析。可以通过调用Lifecycle类的 addObserver() 方法来添加观察者,如下:
getLifecycle().addObserver(new TestLifeCycle());
我在LifecycleBaseActivity做了部分处理,使用起来更加的便捷、易懂。
现在Retrofit2+Rxjava2+Rxandroid+okhttp3+Lifecycle 的MVP网络框架,结合了Google官方AAC框架,实现APP生命周期的管理整体架构就做好了,文章里涉及带网络框架、MVP架构和生命周期的管理,那么一套完整的App框架就搭建好了。
2019.05.09功能新增:
1.新增token过期自动刷新token,刷新后再请求一次接口的功能
github地址:https://github.com/zengweitao/Treasure
CSDN地址:https://blog.csdn.net/weitao_666/article/details/89235135
源码下载地址:https://download.csdn.net/download/weitao_666/11110313