关于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);
    }

是不是方便很多了呢,细心的同学会发现上面我们多了几个新的东西

  1. LiveData
  2. consumerError统一异常处理

LiveData

LiveData 是一个可被观察的数据持有类。与普通的被观察者(如 RxJava 中的 Observable)不同的是,LiveData 是生命周期感知的,也就是说,它能感知其它应用组件(Activity,Fragment,Service)的生命周期。这种感知能力可以确保只有处于 active 状态的组件才能收到 LiveData 的更新。详情可查看 Lifecycle。

这是官方给予的定义,不做过多解释,不明白的同学可以去查看写LiveData的使用
Error统一异常处理
细心的小伙伴会发现subscribe订阅方法的构造参数有很多
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我就不一一解读了,他的构造参数还有很多,我们需要自定义自己的异常因为我们使用第二种构造方法,

  1. onNext即成功的情况下的回调方法
  2. 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,如果有讲解不清晰的可以留言

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青丶穗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值