關於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,如果有講解不清晰的可以留言

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章