Acerca del paquete simple mvvm (2)

En el último artículo, encapsulamos la BaseActivity básica, BaseFragment y el BaseViewmodel más básico. Entonces, aquí viene la pregunta: BaseViewModel no ha visto ningún uso por el momento, entonces, ¿para qué podemos usarlo? Así que esta publicación de blog responderá esa pregunta.

prefacio

Retrofit es una encapsulación de un marco de solicitud de red RESTful HTTP. El trabajo de las solicitudes de red lo completa esencialmente OkHttp, y Retrofit solo es responsable de la encapsulación de las interfaces de solicitud de red.

Rxjava2+Actualización

Si queremos usar ViewModel en profundidad, primero debemos acceder a la red y tener solicitudes de red disponibles.Primero, agregue permisos de acceso a la red.

    <uses-permission android:name="android.permission.INTERNET" /> 

Importar la biblioteca de recursos que necesitamos

    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'

Primero definimos una clase

public class ApiRetrofit {
    
    

}

Añadir algunos parámetros constantes

    private final String BASE_URL = "http://v.juhe.cn/";
    private final String BASE_DEBUG_URL = "http://v.juhe.cn/";

Primero creamos un objeto Retrofit

Retrofit.Builder();

Luego agregamos request baseUrl a Retrofit

        retrofit = new Retrofit.Builder()
                .baseUrl(BuildConfig.DEBUG ? BASE_DEBUG_URL : BASE_URL)
                .build();

Por supuesto, pensará en este momento, necesito configurar el tiempo de espera de la solicitud, ¿qué pasa con el interceptor? ¿Cómo lo hago? Debido a que Retrofit se completa en base a okhttp, entonces su configuración relacionada es, por supuesto, okhttp para completar la configuración.

    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();

Agregue la impresión del registro de solicitud de registro y la configuración del encabezado de solicitud, en el que podemos agregar el token, el número de versión del protocolo, el signo del algoritmo de cifrado, etc.

   /**
     * 请求访问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();
    }

Luego agregamos la configuración relacionada con okhttp a Retrofit

        retrofit = new Retrofit.Builder()
                .baseUrl(BuildConfig.DEBUG ? BASE_DEBUG_URL : BASE_URL)
                .client(client)
                .build();

Ahora todo lo que tenemos que hacer es exponer Retrofit para llamadas de interfaz. En este momento, necesitamos usar su método de creación, pero a primera vista puede estar un poco confundido, puede intentar ingresar el código fuente de creación para ver su fuente código y lo entenderás lo tengo

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

Desde su código fuente, ¿lo entiendes todo de una vez? Devuelve una interfaz de proxy.
Luego creamos una nueva interfaz.

public interface ApiServiceHelper {
    
    

}

Ahora exponemos el objeto Retrofit a la interfaz ApiServiceHelper

   apiServiceHelper = retrofit.create(ApiServiceHelper.class);

Pero para evitar la creación repetida cuando llamamos externamente, debemos configurar ApiRetrofit en modo singleton

    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;
    }

En este momento, nuestra solicitud de red más básica está encapsulada, primero intentemos hacer referencia a ella en ViewModel

    protected ApiServiceHelper apiServiceHelper = ApiRetrofit.getInstance().getApiService();

Comencemos con la solicitud de red más simple, primero escriba una interfaz en ApiServiceHelper; uso la api gratuita de datos agregados como prueba

    @Headers("x-url:sub")
    @GET("toutiao/index")
    Observable<PageBean<NewsBean>> getNews(@Query("key") String key, @Query("type") String type);

Luego cargue la solicitud de red en ViewModel y cree un nuevo TestViewModel

   apiServiceHelper
            .getNews("d9ae666a0ff02c0486c0879570e56d6c", "top")
            .subscribeOn(Schedulers.io())               //在IO线程进行网络请求
            .observeOn(AndroidSchedulers.mainThread())  //回到主线程去处理请求结果
            .subscribe(consumer);

Pero, ¿parece que a esa interfaz le falta algo?
1. Mostrar y ocultar el cuadro de carga
2. Vuelva a intentar la configuración, vuelva a intentarlo cuando la solicitud falle, por ejemplo, el token caduca, podemos llamar al método de actualización del token en reintento, para que el usuario pueda actualizar el token sin percepción 3,
cuadro de carga de procesamiento de excepciones
en
este momento Necesitamos usar dos métodos, doOnSubscribe y doFinally. Estos dos métodos se ejecutan antes y después de que comience y finalice la solicitud de red. Podemos mostrar el cuadro de carga en doOnSubscribe y ocultar el cuadro de carga en doFinally. En esta vez, podemos llamar a la encapsulación en el método 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);

El mecanismo de reintento de Retrofit retryWhen
haga clic en el código fuente retryWhen, puede ver que el método de construcción de retryWhen es muy simple, es un parámetro de función, pero no subestime este método, su funcionalidad es muy poderosa. El primer parámetro de Funtion es Throwable, es decir, la excepción lanzada por la interfaz actual, y el segundo parámetro es ObservableSource, es decir, podemos continuar con la solicitud anterior procesando la excepción y devolviendo un nuevo objeto ObservableSource, y realizar operaciones tales como refrescando la ficha sin percepción

.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);
                        }))

Hablaremos sobre la encapsulación unificada de excepciones más adelante. Después de escribir esto, encontrará que una interfaz de solicitud en TestViewModel ya es demasiado larga. Si hay varias interfaces, ¿no sería necesario escribir una gran cantidad de código repetitivo? Entonces podemos volver a encapsular la solicitud de red en 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;
    }

Ahora llamamos a la interfaz en TestViewModel

    //获取首页文章
    public LiveData<PageBean<NewsBean>> getNews() {
    
    
        return observe(apiServiceHelper.getNews("d9ae666a0ff02c0486c0879570e56d6c", "top"), mutableLiveData);
    }

¿Es mucho más conveniente?Los estudiantes cuidadosos encontrarán que hemos agregado algunas cosas nuevas arriba.

  1. Datos en tiempo real
  2. Manejo unificado de excepciones de ConsumerError

Datos en tiempo real

LiveData es una clase de titular de datos observable. A diferencia de los observadores ordinarios (como Observable en RxJava), LiveData es consciente del ciclo de vida, es decir, puede percibir el ciclo de vida de otros componentes de la aplicación (Actividad, Fragmento, Servicio). Esta conciencia garantiza que solo los componentes activos reciban actualizaciones de LiveData. Consulte Ciclo de vida para obtener más detalles.

Esta es la definición dada por el funcionario. No quiero explicar demasiado. Los estudiantes que no entiendan pueden verificar y escribir el uso de LiveData del Manejo de excepciones unificado de errores. Los amigos cuidadosos
encontrarán
que hay muchos parámetros de construcción de la suscripción de suscripción. método.
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
No los explicaré uno por uno. Hay muchos parámetros de construcción, necesitamos personalizar nuestra propia excepción porque usamos el segundo método de construcción,

  1. onNext es el método de devolución de llamada en caso de éxito
  2. onError es el método de devolución de llamada en caso de falla y excepción

Encapsulación BaseException

Primero definimos la clase base BaseException y definimos algunos códigos de excepción de uso común

    /**
     * 解析数据失败
     */
    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 = "登录超时";

Exponemos dos parámetros a BaseException para llamadas externas


    private String errorMsg;//异常信息描述
    private String errorCode;//异常code

pega el codigo completo

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;
    }
}

Ahora todo lo que tenemos que hacer es anular la excepción de error en el método de suscripción.

new Consumer<Throwable>() {
    
    
                    @Override
                    public void accept(Throwable throwable) throws Exception {
    
    
                        
                    }
                })

La pregunta es cómo implementamos este manejo de excepciones
1. Al ingresar una excepción, primero debemos cerrar el cuadro de carga

        if (baseView != null && isShowDialog) {
    
    
            baseView.hideLoading();
        }

2. Obtenemos el código y mensaje de la excepción

          BaseException  be = new BaseException(BaseException.OTHER_MSG, e, BaseException.OTHER);

3. Vuelva a llamar nuestra excepción a la actividad y fragmente

        if (baseView != null) {
    
    
            baseView.onErrorCode(new BaseModelEntity(be.getErrorCode(), be.getErrorMsg()));
            baseView.showToast(be.getErrorMsg());
        }

De esta manera, nuestro manejo de excepciones simple está hecho, y lo mejoraremos más adelante. Aquí encontrará que hay una clase BaseModelEntity adicional. ¿Qué hace esta clase? Se utiliza para procesar los datos devueltos por la interfaz de manera unificada, así que sigamos explicando la siguiente pregunta.

Procesamiento unificado de datos de interfaz

La mayoría de las definiciones de interfaz de los estudiantes son similares a esta.
inserte la descripción de la imagen aquí
El formato de retorno de la interfaz está unificado. No necesitamos procesar cada interfaz. Podemos usar las características de Retrofit para un procesamiento unificado.
Creamos una nueva clase de entidad.

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;
    }
}

Si hay una lista, también puede definir una clase de entidad unificada para la lista

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 proporciona un método de encapsulación para que logremos un procesamiento de datos unificado

    /** Add converter factory for serialization and deserialization of objects. */
    public Builder addConverterFactory(Converter.Factory factory) {
    
    
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

Entonces solo necesitamos reescribir la clase Converter.Factory nuevamente

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

La clase de fábrica Converter.Factory tiene varios métodos que necesitan reescribir
responseBodyConverter, devolver el procesamiento de resultados
requestBodyConverter y solicitar el procesamiento de parámetros.
A continuación, solo necesitamos reescribir las clases de implementación de estas dos interfaces
. Cree una nueva clase y reescriba el método de procesamiento de solicitud de datos del cuerpo

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());
    }
}

De manera similar, creamos una nueva clase para reescribir el método de procesamiento de datos de 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();
        }
    }
}

Luego reescriba Converter.Factory y configure el método de datos recién reescrito

        retrofit = new Retrofit.Builder()
                .baseUrl(BuildConfig.DEBUG ? BASE_DEBUG_URL : BASE_URL)
                .addConverterFactory(BaseConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//支持RxJava2
                .client(client)
                .build();

En el procesamiento unificado de los resultados devueltos, lanzamos varias excepciones

  • Excepción de análisis JSON
  • La interfaz maneja las excepciones
    , por lo que podemos enriquecer nuestro manejo de excepciones.Primero
    , determine si el servidor devuelve la excepción devuelta.
if (e instanceof BaseException) {
    
    
                be = (BaseException) e;

                //回调到view层 处理 或者根据项目情况处理
                if (baseView != null) {
    
    
                    baseView.onErrorCode(new BaseModelEntity(be.getErrorCode(), be.getErrorMsg()));
                }
            } 

Si se vuelve a llamar directamente a la actividad y al fragmento para su procesamiento, si el servidor no lo devuelve, es posible que se haya agotado el tiempo de espera de la solicitud de interfaz o que la solicitud haya sido interceptada, etc.

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

De esta manera, podemos procesar diferentes códigos de acuerdo a diferentes situaciones en nuestra actividad y fragmento.
En este punto, la encapsulación de la solicitud de red está básicamente completa. Solo necesitamos vincular la clase de entidad correspondiente al valor de retorno a xml en la página.

        mViewModel.getNews().observe(this,news -> {
    
    
            binding.SetNews(news);
        });

Pegue el código completo del BaseViewModel completado

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());
        }
    };

}

Código completo de 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;
    }
}

La demostración se cargará más tarde, si hay alguna explicación poco clara, deje un mensaje

Supongo que te gusta

Origin blog.csdn.net/fzkf9225/article/details/105200803
Recomendado
Clasificación