关于mvvm简易封装(二)
上篇文章我们封装了基础的BaseActivity、BaseFragment和最最最基础的BaseViewmodel。那么疑问来了BaseViewModel暂时没有看到任何用处,那么我们可以用来干嘛呢?那么这篇博文就来解答这个问题
前言
Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装,网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装
Rxjava2+Retrofit
想要深层次用到ViewModel那么我们最基础的需要先可以访问网络有可用的网络请求,首先添加网络访问权限
<uses-permission android:name="android.permission.INTERNET" />
引入我们需要的资源库
implementation 'com.squareup.picasso:picasso:2.4.0'
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
//ConverterFactory的String依赖包
implementation 'com.squareup.retrofit2:converter-scalars:2.5.0'
//ConverterFactory的Gson依赖包
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
//CallAdapterFactory的Rx依赖包
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
implementation 'com.google.code.gson:gson:2.8.6'
首先我们定义一个类
public class ApiRetrofit {
}
添加部分常量参数
private final String BASE_URL = "http://v.juhe.cn/";
private final String BASE_DEBUG_URL = "http://v.juhe.cn/";
首先我们创建一个Retrofit对象
Retrofit.Builder();
然后我们给Retrofit添加请求baseUrl
retrofit = new Retrofit.Builder()
.baseUrl(BuildConfig.DEBUG ? BASE_DEBUG_URL : BASE_URL)
.build();
当然这时你就会想,我需要设置请求超时时间,拦截器呢?我该如何去做。因为Retrofit是基于okhttp的去完成,那么他的相关配置当然是有okhttp去完成配置了
private OkHttpClient client;
client = new OkHttpClient.Builder()
//添加log拦截器
// .addInterceptor(urlInterceptor)
.addInterceptor(logInterceptor)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build();
添加日志请求日志打印,和请求头设置,在请求头中我们可以添加token、协议版本号、加密算法sign等等
/**
* 请求访问quest
* response拦截器
* 日志拦截器
*/
private Interceptor logInterceptor = chain -> {
String timeStamp = DateUtil.getTimestamp();
Request request = chain.request().newBuilder()
.addHeader("x-token", UserAccountHelper.getToken() == null ? "" : UserAccountHelper.getToken())
.addHeader("x-timestamp", timeStamp)
.addHeader("x-uuid", UUID.randomUUID().toString())
.addHeader("x-appid", ApiAccountHelper.APPID)
.addHeader("x-phoneidentity", ApiAccountHelper.getDeviceId())
.addHeader("x-protocolversion", ApiAccountHelper.PROTOCOL_VERSION)
.addHeader("x-sign", encodeSign(bodyToString(chain.request().body()), timeStamp))
.build();
Response response = chain.proceed(request);
if (BuildConfig.DEBUG) {
printLog(request, response);
}
return response;
};
private void printLog(final Request request, final Response response) {
LogUtil.show("--------------------Request Start--------------------");
LogUtil.show("Method:" + request.method());
LogUtil.show("Url:" + request.url());
LogUtil.show("HttpHeader:" + request.headers().toString());
try {
LogUtil.show("请求参数:" + bodyToString(request.body()));
} catch (IOException e) {
LogUtil.show("请求参数解析失败");
}
try {
ResponseBody responseBody = response.peekBody(1024 * 1024);
LogUtil.show("返回结果:" + responseBody.string());
} catch (Exception e) {
LogUtil.show("返回结果解析失败");
}
LogUtil.show("--------------------Request End--------------------");
}
private String bodyToString(final RequestBody request) throws IOException {
final Buffer buffer = new Buffer();
if (request != null)
request.writeTo(buffer);
else
return "";
return buffer.readUtf8();
}
然后我们把okhttp相关配置添加给Retrofit
retrofit = new Retrofit.Builder()
.baseUrl(BuildConfig.DEBUG ? BASE_DEBUG_URL : BASE_URL)
.client(client)
.build();
现在我们需要做的就是把Retrofit暴露出去供接口调用,这时就要使用它的create方法了,但是乍一看你可能有点懵,你可以尝试着进入create源码中去看看它的源码就明白了了
public <T> T create(final Class<T> service) {
//1、验证服务接口。 检验文件是否是interface类型。 如果不是抛出异常。
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
//返回类型是通过动态代理生成的对象。T就是传入的接口类。
//动态代理的invoke方法,会在每个方法调用的时候执行。也就是xxxxservice.doAction()的时候;(例子,假设doAction是接口中的一个方法);
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
//获取平台类型。检查是Android还是Java。我们可以忽略,因为开发Android,平台类型一般都是Android。这里会得到一个Android类型的Platform对象。
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
//检查方法是否来自Object。如果是就正常调用。我们传入的是接口类,所以这里不会执行,直接跳过。
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
//这里我们向isDefaultMethod方法里面看,可以看到android平台的这个方法永远返回false。所以此处也是跳过。
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
//这里是重点!!!,从这里开始去解析目前正在执行的方法。 就是去我们传入的接口类中,找到当前调用的doAction方法。同时去解析注解和各种参数。得到一个ServiceMethod对象。
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
//用serviceMethod对象生成一个OkHttpCall对象.serviceMethod中有解析到的各种配置。
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
//返回一个对象,如果我们没有使用rxjava 那么拿到的是call对象。如果使用了rxjava。那么拿到的是Observable对象。
//此处拿到的返回对象,是在xxxxservice.doAction()时得到的对象。决定返回对象的类型。是在retrofit.builder.addCallAdapterFactory的时候.
return serviceMethod.adapt(okHttpCall);
}
});
}
从他的源码看来是不是一下子就明白了呢,它是返回了一个代理接口
那么我们新建一个接口
public interface ApiServiceHelper {
}
我们现在把Retrofit对象暴露给ApiServiceHelper接口
apiServiceHelper = retrofit.create(ApiServiceHelper.class);
但是我们在外部调用的时候为了避免重复创建,需要把ApiRetrofit设置为单例模式
private static ApiRetrofit apiRetrofit;
private Retrofit retrofit;
public static ApiRetrofit getInstance() {
if (apiRetrofit == null) {
synchronized (Object.class) {
if (apiRetrofit == null) {
apiRetrofit = new ApiRetrofit();
}
}
}
return apiRetrofit;
}
这时我们最基础的网络请求就封装好了,我们先尝试在ViewModel中去引用它
protected ApiServiceHelper apiServiceHelper = ApiRetrofit.getInstance().getApiService();
我们开始最简易的网络请求,在ApiServiceHelper先编写一个接口;我使用聚合数据的免费api作为测试
@Headers("x-url:sub")
@GET("toutiao/index")
Observable<PageBean<NewsBean>> getNews(@Query("key") String key, @Query("type") String type);
然后在ViewModel中去加载网络请求,新建一个TestViewModel
apiServiceHelper
.getNews("d9ae666a0ff02c0486c0879570e56d6c", "top")
.subscribeOn(Schedulers.io()) //在IO线程进行网络请求
.observeOn(AndroidSchedulers.mainThread()) //回到主线程去处理请求结果
.subscribe(consumer);
但是这样的接口是不是似乎还少了什么?
1、加载框显示与隐藏
2、retry配置,当请求失败后进行重试,比如,token过期,我们可以retry中调用刷新token的方法,让用户无感知的刷新token
3、exception处理
加载框
这时我们需要用到两个方法doOnSubscribe、doFinally,这两个方法分别执行与网络请求开始前和结束后,我们可以在doOnSubscribe中显示加载框,在doFinally隐藏加载框,这时我们可以调用baseView中的封装方法
apiServiceHelper
.getNews("d9ae666a0ff02c0486c0879570e56d6c", "top")
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
addDisposable(disposable);
if (baseView != null && isShowDialog) {
baseView.showLoading(dialogMessage);
}
}
})
.doFinally(() -> {
if (baseView != null && isShowDialog) {
baseView.hideLoading();
}
})
.subscribeOn(Schedulers.io()) //在IO线程进行网络请求
.observeOn(AndroidSchedulers.mainThread()) //回到主线程去处理请求结果
.subscribe(consumer);
Retrofit的重试机制retryWhen
点进retryWhen源码可以看到retryWhen的构造方法很简单是一个Function参数,但是别小看这个方法,他的功能性很强大。Funtion第一个参数为Throwable,即当前接口抛出的异常,第二个参数为ObservableSource,即我们可以通过处理异常然后返回一个新的ObservableSource对象继续上一个请求,进行无感知刷新token等操作
.retryWhen((Function<Observable<Throwable>, ObservableSource<?>>) throwableObservable ->
throwableObservable.flatMap((Function<Throwable, ObservableSource<?>>) throwable -> {
if (throwable instanceof BaseException) {
BaseException baseException = (BaseException) throwable;
if (UserAccountHelper.isLoginPast(baseException.getErrorCode())) {
// 如果上面检测到token过期就会进入到这里
//抛出一个新的接口,刷新token接口
return apiServiceHelper.getNewToken()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.doOnNext(loginResultBean -> {
UserAccountHelper.setToken(loginResultBean.getToken());//存储新的token
});// 这里更新完成后就会进行重订阅,从Observable.just(null)重新开始走。
}
}
return Observable.error(throwable);
}))
统一异常封装我们等会再说,写到这里了,你会发现TestViewModel中一个请求接口就已经这么长了,那如果好几个接口那岂不是得写很多重复代码?于是我们可以把网络请求再次封装到BaseViewModel中去
//把统一操作全部放在这
protected <T> MutableLiveData<T> observe(Observable observable, final MutableLiveData<T> liveData) {
observable.subscribeOn(Schedulers.io())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
addDisposable(disposable);
if (baseView != null && isShowDialog) {
baseView.showLoading(dialogMessage);
}
}
})
.doFinally(() -> {
if (baseView != null && isShowDialog) {
baseView.hideLoading();
}
})
.observeOn(AndroidSchedulers.mainThread())
// .compose(objectLifecycleTransformer)
.retryWhen((Function<Observable<Throwable>, ObservableSource<?>>) throwableObservable ->
throwableObservable.flatMap((Function<Throwable, ObservableSource<?>>) throwable -> {
if (throwable instanceof BaseException) {
BaseException baseException = (BaseException) throwable;
if (UserAccountHelper.isLoginPast(baseException.getErrorCode())) {
// 如果上面检测到token过期就会进入到这里
// 然后下面的方法就是更新token
return apiServiceHelper.getNewToken()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.doOnNext(loginResultBean -> { UserAccountHelper.setToken(loginResultBean.getToken());//存储新的token
});// 这里更新完成后就会进行重订阅,从Observable.just(null)重新开始走。
}
}
return Observable.error(throwable);
}))
.subscribe(o -> {
liveData.postValue((T) o);//通知数据更新
}, consumerError);
return liveData;
}
现在我们在TestViewModel中调用接口
//获取首页文章
public LiveData<PageBean<NewsBean>> getNews() {
return observe(apiServiceHelper.getNews("d9ae666a0ff02c0486c0879570e56d6c", "top"), mutableLiveData);
}
是不是方便很多了呢,细心的同学会发现上面我们多了几个新的东西
- LiveData
- consumerError统一异常处理
LiveData
LiveData 是一个可被观察的数据持有类。与普通的被观察者(如 RxJava 中的 Observable)不同的是,LiveData 是生命周期感知的,也就是说,它能感知其它应用组件(Activity,Fragment,Service)的生命周期。这种感知能力可以确保只有处于 active 状态的组件才能收到 LiveData 的更新。详情可查看 Lifecycle。
这是官方给予的定义,不做过多解释,不明白的同学可以去查看写LiveData的使用
Error统一异常处理
细心的小伙伴会发现subscribe订阅方法的构造参数有很多
我就不一一解读了,他的构造参数还有很多,我们需要自定义自己的异常因为我们使用第二种构造方法,
- onNext即成功的情况下的回调方法
- onError即失败和异常的情况下回调方法
BaseException封装
我们首先定义BaseException基类,定义一些常用的异常code
/**
* 解析数据失败
*/
static final String PARSE_ERROR = "1001";
public static final String PARSE_ERROR_MSG = "解析数据失败";
/**
* 网络问题
*/
static final String BAD_NETWORK = "1002";
static final String BAD_NETWORK_MSG = "服务器或网络异常";
/**
* 连接错误
*/
static final String CONNECT_ERROR = "1003";
static final String CONNECT_ERROR_MSG = "连接错误";
/**
* 连接超时
*/
static final String CONNECT_TIMEOUT = "1004";
static final String CONNECT_TIMEOUT_MSG = "连接超时";
/**
* 未知错误
*/
static final String OTHER = "1005";
static final String OTHER_MSG = "未知错误";
/**
* 其他问题,即服务器返回的请求失败
*/
public static final String REQUEST_ERROR = "1006";
/**
* 登录超时
*/
public static final String TOKEN_ERROR = "1007";
public static final String TOKEN_ERROR_MSG = "登录超时";
我们给BaseExcepition暴露两个参数共外部调用
private String errorMsg;//异常信息描述
private String errorCode;//异常code
贴上完整代码
public class BaseException extends IOException {
private static final long serialVersionUID = 602780230218501625L;
/**
* 解析数据失败
*/
static final String PARSE_ERROR = "1001";
public static final String PARSE_ERROR_MSG = "解析数据失败";
/**
* 网络问题
*/
static final String BAD_NETWORK = "1002";
static final String BAD_NETWORK_MSG = "服务器或网络异常";
/**
* 连接错误
*/
static final String CONNECT_ERROR = "1003";
static final String CONNECT_ERROR_MSG = "连接错误";
/**
* 连接超时
*/
static final String CONNECT_TIMEOUT = "1004";
static final String CONNECT_TIMEOUT_MSG = "连接超时";
/**
* 未知错误
*/
static final String OTHER = "1005";
static final String OTHER_MSG = "未知错误";
/**
* 其他问题,即服务器返回的请求失败
*/
public static final String REQUEST_ERROR = "1006";
/**
* 登录超时
*/
public static final String TOKEN_ERROR = "1007";
public static final String TOKEN_ERROR_MSG = "登录超时";
private String errorMsg;
private String errorCode;
String getErrorMsg() {
return errorMsg;
}
String getErrorCode() {
return errorCode;
}
public BaseException(String errorMsg, Throwable cause) {
super(errorMsg, cause);
this.errorMsg = errorMsg;
}
BaseException(String message, Throwable cause, String errorCode) {
super(message, cause);
this.errorCode = errorCode;
this.errorMsg = message;
}
BaseException(String message, String errorCode) {
this.errorCode = errorCode;
this.errorMsg = message;
}
}
现在我们需要做的就是在subscribe订阅方法中重写错误异常
new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
}
})
问题就是我们如何去实现这个异常的处理
1、当进入异常时我们需要先关闭加载框
if (baseView != null && isShowDialog) {
baseView.hideLoading();
}
2、我们获取exception的code和message
BaseException be = new BaseException(BaseException.OTHER_MSG, e, BaseException.OTHER);
3、把我们的异常回调给activity和fragment
if (baseView != null) {
baseView.onErrorCode(new BaseModelEntity(be.getErrorCode(), be.getErrorMsg()));
baseView.showToast(be.getErrorMsg());
}
这样我们简易的异常处理就做好了,后续我们给他在完善一下,这里你们会发现多了一个BaseModelEntity类,这个类是做什么的呢?是用来处理接口返回数据统一处理的,那么我们继续讲解下一个问题
接口数据统一处理
大部分同学的接口定义都是类似于这样
接口返回格式是统一的我们不需要对其进行每个接口都处理一遍,我们可以利用Retrofit的特性进行统一处理
我们新建一个实体类
public class BaseModelEntity<T> implements Serializable {
public BaseModelEntity() {
}
public BaseModelEntity(String code, String msg) {
this.error_code = code;
this.reason = msg;
}
private String error_code; //类型:String 必有字段 备注:错误标识,根据该字段判断服务器操作是否成功
private String reason; //类型:String 必有字段 备注:错误信息
private T result;
public String getCode() {
return error_code;
}
public void setCode(String code) {
this.error_code = code;
}
public String getMsg() {
return reason;
}
public void setMsg(String msg) {
this.reason = msg;
}
public T getData() {
return result;
}
public void setData(T data) {
this.result = data;
}
}
如果有列表的话还可以定义一个列表的统一实体类
public class PageBean<T> implements Serializable {
private List<T> data;
private String nextPageToken;
private String prevPageToken;
private int requestCount;
private int responseCount;
private int rowCount;
public List<T> getList() {
return data;
}
public void setList(List<T> list) {
this.data = list;
}
public int getRowCount() {
return rowCount;
}
public void setRowCount(int rowCount) {
this.rowCount = rowCount;
}
public String getNextPageToken() {
return nextPageToken;
}
public void setNextPageToken(String nextPageToken) {
this.nextPageToken = nextPageToken;
}
public String getPrevPageToken() {
return prevPageToken;
}
public void setPrevPageToken(String prevPageToken) {
this.prevPageToken = prevPageToken;
}
public int getRequestCount() {
return requestCount;
}
public void setRequestCount(int requestCount) {
this.requestCount = requestCount;
}
public int getResponseCount() {
return responseCount;
}
public void setResponseCount(int responseCount) {
this.responseCount = responseCount;
}
public static class PageInfo implements Serializable {
private int totalResults;
private int resultsPerPage;
public int getTotalResults() {
return totalResults;
}
public void setTotalResults(int totalResults) {
this.totalResults = totalResults;
}
public int getResultsPerPage() {
return resultsPerPage;
}
public void setResultsPerPage(int resultsPerPage) {
this.resultsPerPage = resultsPerPage;
}
}
}
Retrofit提供了封装方法供我们去实现数据统一处理
/** Add converter factory for serialization and deserialization of objects. */
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
然后我们重新只需要重写Converter.Factory类即可
public final class BaseConverterFactory extends Converter.Factory {
public static BaseConverterFactory create() {
return create(new Gson());
}
//工厂方法,用于创建实例
@SuppressWarnings("ConstantConditions")
public static BaseConverterFactory create(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
return new BaseConverterFactory(gson);
}
private final Gson gson;
private BaseConverterFactory(Gson gson) {
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new BaseResponseBodyConverter<>(gson, adapter);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new BaseRequestBodyConverter<>(gson, adapter);
}
}
Converter.Factory工厂类有几个方法需要重写
responseBodyConverter,返回结果处理
requestBodyConverter,请求参数处理
接下来我们只需要重写这两个接口的实现类即可
新建一个类,重写requestBody数据的处理方法
class BaseRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
BaseRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}
同样的我们新建一个类,去重写responseBody数据处理方法
public class BaseResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final TypeAdapter<T> adapter;
BaseResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
String jsonString = value.string();
try {
BaseModelEntity baseModel = new GsonBuilder().disableHtmlEscaping().create().fromJson(jsonString,
new TypeToken<BaseModelEntity<T>>() {
}.getType());//利用统一实体类对象进行json解析
if (!"0".equals(baseModel.getCode())) {//判断接口是否成功,如果不成功直接抛出异常
throw new BaseException(baseModel.getMsg(), baseModel.getCode());
}
//如果返回code是成功的话,则去除data对象直接返回,注意这里data对象如果为null,并且你接口观察者用的是Consumer的话,会抛出异常,
//你可以尝试把null转为为一个字符串抛出
return adapter.fromJson(new Gson().toJson(baseModel.getData() == null ? "操作完成" : baseModel.getData()));
} catch (Exception e) {
e.printStackTrace();
//数据解析异常
throw e;
} finally {
value.close();
}
}
}
然后重写Converter.Factory,把刚才重写的数据方法设置上去
retrofit = new Retrofit.Builder()
.baseUrl(BuildConfig.DEBUG ? BASE_DEBUG_URL : BASE_URL)
.addConverterFactory(BaseConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//支持RxJava2
.client(client)
.build();
在返回结果的统一处理中,我们抛出了几个异常
- json解析异常
- 接口处理异常
那么我们可以丰富一下我们的异常处理了
首先判断返回的异常是不是服务器返回的
if (e instanceof BaseException) {
be = (BaseException) e;
//回调到view层 处理 或者根据项目情况处理
if (baseView != null) {
baseView.onErrorCode(new BaseModelEntity(be.getErrorCode(), be.getErrorMsg()));
}
}
如果是直接回调给activity和fragment处理,如果不是的服务器返回的,那么可能是接口请求超时了或者请求被拦截了等等
if (e instanceof HttpException) {
// HTTP错误
be = new BaseException(BaseException.BAD_NETWORK_MSG, e, BaseException.BAD_NETWORK);
} else if (e instanceof ConnectException
|| e instanceof UnknownHostException) {
// 连接错误
be = new BaseException(BaseException.CONNECT_ERROR_MSG, e, BaseException.CONNECT_ERROR);
} else if (e instanceof InterruptedIOException) {
// 连接超时
be = new BaseException(BaseException.CONNECT_TIMEOUT_MSG, e, BaseException.CONNECT_TIMEOUT);
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) {
// 解析错误
be = new BaseException(BaseException.PARSE_ERROR_MSG, e, BaseException.PARSE_ERROR);
} else {
be = new BaseException(BaseException.OTHER_MSG, e, BaseException.OTHER);
}
}
这样我们activity和fragment中可以根据不同的情况对不同的code进行相应的处理
到此网络请求封装基本完成啦,我们只需要在页面中,把返回值对应的实体类绑定到xml中即可
mViewModel.getNews().observe(this,news -> {
binding.SetNews(news);
});
贴上完善后的BaseViewModel完整代码
public class BaseViewModel<V extends BaseView> extends AndroidViewModel {
//离开页面,是否取消网络
private CompositeDisposable compositeDisposable;
//如果开启,同一url还在请求网络时,不会
public ArrayList<String> onNetTags;
private String dialogMessage = "正在加载,请稍后...";
private LifecycleTransformer objectLifecycleTransformer;
protected V baseView;
private boolean isShowDialog;
protected ApiServiceHelper apiServiceHelper = ApiRetrofit.getInstance().getApiService();
public BaseViewModel(@NonNull Application application) {
super(application);
this.isShowDialog = true;
}
protected void setBaseView(V baseView) {
this.baseView = baseView;
}
public void setShowDialog(boolean showDialog) {
isShowDialog = showDialog;
}
public V getBaseView() {
return baseView;
}
public boolean isShowDialog() {
return isShowDialog;
}
@Override
protected void onCleared() {
super.onCleared();
}
public void setDialogMessage(String dialogMessage) {
this.dialogMessage = dialogMessage;
}
private void addDisposable(Disposable disposable) {
if (compositeDisposable == null) {
compositeDisposable = new CompositeDisposable();
}
compositeDisposable.add(disposable);
}
private void removeDisposable() {
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
}
public void setObjectLifecycleTransformer(LifecycleTransformer objectLifecycleTransformer) {
this.objectLifecycleTransformer = objectLifecycleTransformer;
}
//把统一操作全部放在这,不会重连
@SuppressLint("CheckResult")
protected <T> MutableLiveData<T> observe(Observable observable, boolean isShowDialog,final MutableLiveData<T> liveData) {
this.isShowDialog = isShowDialog;
return observe(observable,liveData);
}
//把统一操作全部放在这,不会重连
@SuppressLint("CheckResult")
protected <T> MutableLiveData<T> observe(Observable observable, final MutableLiveData<T> liveData) {
observable.subscribeOn(Schedulers.io())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
addDisposable(disposable);
if (baseView != null && isShowDialog) {
baseView.showLoading(dialogMessage);
}
}
})
.doFinally(() -> {
if (baseView != null && isShowDialog) {
baseView.hideLoading();
}
})
.observeOn(AndroidSchedulers.mainThread())
// .compose(objectLifecycleTransformer)
.retryWhen((Function<Observable<Throwable>, ObservableSource<?>>) throwableObservable ->
throwableObservable.flatMap((Function<Throwable, ObservableSource<?>>) throwable -> {
if (throwable instanceof BaseException) {
BaseException baseException = (BaseException) throwable;
if (UserAccountHelper.isLoginPast(baseException.getErrorCode())) {
// 如果上面检测到token过期就会进入到这里
// 然后下面的方法就是更新token
return apiServiceHelper.getNewToken()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.doOnNext(loginResultBean -> {
UserAccountHelper.setToken(loginResultBean.getToken());//存储新的token
});// 这里更新完成后就会进行重订阅,从Observable.just(null)重新开始走。
}
}
return Observable.error(throwable);
}))
.subscribe(o -> {
liveData.postValue((T) o);
}, consumerError);
return liveData;
}
protected Action finallyAction = new Action() {
@Override
public void run() throws Exception {
if (baseView != null && isShowDialog) {
baseView.hideLoading();
}
}
};
protected Consumer consumerError = (Consumer<Throwable>) e -> {
LogUtil.show("BaseViewModel|系统异常: " + e);
if (baseView != null && isShowDialog) {
baseView.hideLoading();
}
BaseException be = null;
if (e != null) {
if (e instanceof BaseException) {
be = (BaseException) e;
//回调到view层 处理 或者根据项目情况处理
if (baseView != null) {
baseView.onErrorCode(new BaseModelEntity(be.getErrorCode(), be.getErrorMsg()));
}
} else {
if (e instanceof HttpException) {
// HTTP错误
be = new BaseException(BaseException.BAD_NETWORK_MSG, e, BaseException.BAD_NETWORK);
} else if (e instanceof ConnectException
|| e instanceof UnknownHostException) {
// 连接错误
be = new BaseException(BaseException.CONNECT_ERROR_MSG, e, BaseException.CONNECT_ERROR);
} else if (e instanceof InterruptedIOException) {
// 连接超时
be = new BaseException(BaseException.CONNECT_TIMEOUT_MSG, e, BaseException.CONNECT_TIMEOUT);
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) {
// 解析错误
be = new BaseException(BaseException.PARSE_ERROR_MSG, e, BaseException.PARSE_ERROR);
} else {
be = new BaseException(BaseException.OTHER_MSG, e, BaseException.OTHER);
}
}
} else {
be = new BaseException(BaseException.OTHER_MSG, e, BaseException.OTHER);
}
LogUtil.show("BaseViewModel|异常消息: " + be.getErrorMsg());
if (baseView != null) {
baseView.onErrorCode(new BaseModelEntity(be.getErrorCode(), be.getErrorMsg()));
baseView.showToast(be.getErrorMsg());
}
};
}
ApiRetrofit完整代码
public class ApiRetrofit {
private final String BASE_URL = "http://v.juhe.cn/";
private final String BASE_DEBUG_URL = "http://v.juhe.cn/";
private static ApiRetrofit apiRetrofit;
private Retrofit retrofit;
private OkHttpClient client;
private ApiServiceHelper apiServiceHelper;
/**
* 动态修改url
*/
private Interceptor urlInterceptor = chain -> {
// 获取request
Request request = chain.request();
// 从request中获取原有的HttpUrl实例oldHttpUrl
HttpUrl oldHttpUrl = request.url();
// 获取request的创建者builder
Request.Builder builder = request.newBuilder();
// 从request中获取headers,通过给定的键url_name
List<String> headerValues = request.headers("x-url");
if (headerValues.size() > 0) {
// 如果有这个header,先将配置的header删除,因此header仅用作app和okhttp之间使用
builder.removeHeader("x-url");
// 匹配获得新的BaseUrl
String headerValue = headerValues.get(0);
HttpUrl newBaseUrl = null;
if ("sub".equals(headerValue)) {
newBaseUrl = HttpUrl.parse(UserAccountHelper.getBaseUrl());
} else if ("admin".equals(headerValue)) {
newBaseUrl = HttpUrl.parse(BuildConfig.DEBUG ? BASE_DEBUG_URL : BASE_URL);
} else {
newBaseUrl = oldHttpUrl;
}
// 重建新的HttpUrl,修改需要修改的url部分
HttpUrl newFullUrl = oldHttpUrl
.newBuilder()
// 更换网络协议
.scheme(newBaseUrl.scheme())
// 更换主机名
.host(newBaseUrl.host())
// 更换端口
.port(newBaseUrl.port())
.build();
// 重建这个request,通过builder.url(newFullUrl).build();
// 然后返回一个response至此结束修改
return chain.proceed(builder.url(newFullUrl).build());
}
return chain.proceed(request);
};
/**
* 请求访问quest
* response拦截器
* 日志拦截器
*/
private Interceptor logInterceptor = chain -> {
String timeStamp = DateUtil.getTimestamp();
Request request = chain.request().newBuilder()
.addHeader("x-token", UserAccountHelper.getToken() == null ? "" : UserAccountHelper.getToken())
.addHeader("x-timestamp", timeStamp)
.addHeader("x-uuid", UUID.randomUUID().toString())
.addHeader("x-phoneidentity", ApiAccountHelper.getDeviceId())
.addHeader("x-phoneinfo", ApiAccountHelper.getPhoneInfo())
.build();
Response response = chain.proceed(request);
if (BuildConfig.DEBUG) {
printLog(request, response);
}
return response;
};
private void printLog(final Request request, final Response response) {
LogUtil.show("--------------------Request Start--------------------");
LogUtil.show("Method:" + request.method());
LogUtil.show("Url:" + request.url());
LogUtil.show("HttpHeader:" + request.headers().toString());
try {
LogUtil.show("请求参数:" + bodyToString(request.body()));
} catch (IOException e) {
LogUtil.show("请求参数解析失败");
}
try {
ResponseBody responseBody = response.peekBody(1024 * 1024);
LogUtil.show("返回结果:" + responseBody.string());
} catch (Exception e) {
LogUtil.show("返回结果解析失败");
}
LogUtil.show("--------------------Request End--------------------");
}
private String bodyToString(final RequestBody request) throws IOException {
final Buffer buffer = new Buffer();
if (request != null)
request.writeTo(buffer);
else
return "";
return buffer.readUtf8();
}
private ApiRetrofit() {
client = new OkHttpClient.Builder()
//添加log拦截器
// .addInterceptor(urlInterceptor)
.addInterceptor(logInterceptor)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BuildConfig.DEBUG ? BASE_DEBUG_URL : BASE_URL)
.addConverterFactory(BaseConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//支持RxJava2
.client(client)
.build();
apiServiceHelper = retrofit.create(ApiServiceHelper.class);
}
public static ApiRetrofit getInstance() {
if (apiRetrofit == null) {
synchronized (Object.class) {
if (apiRetrofit == null) {
apiRetrofit = new ApiRetrofit();
}
}
}
return apiRetrofit;
}
public ApiServiceHelper getApiService() {
return apiServiceHelper;
}
}
后续会上传demo,如果有讲解不清晰的可以留言