欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

关于mvvm简易封装(二)

程序员文章站 2022-07-01 20:50:43
...

上篇文章我们封装了基础的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订阅方法的构造参数有很多
关于mvvm简易封装(二)
关于mvvm简易封装(二)
关于mvvm简易封装(二)
我就不一一解读了,他的构造参数还有很多,我们需要自定义自己的异常因为我们使用第二种构造方法,

  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类,这个类是做什么的呢?是用来处理接口返回数据统一处理的,那么我们继续讲解下一个问题

接口数据统一处理

大部分同学的接口定义都是类似于这样
关于mvvm简易封装(二)
接口返回格式是统一的我们不需要对其进行每个接口都处理一遍,我们可以利用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,如果有讲解不清晰的可以留言

相关标签: MVVM android