上篇文章我們封裝了基礎的BaseActivity、BaseFragment和最最最基礎的BaseViewmodel。那麼疑問來了BaseViewModel暫時沒有看到任何用處,那麼我們可以用來幹嘛呢?那麼這篇博文就來解答這個問題
前言
Retrofit 是一個 RESTful 的 HTTP 網絡請求框架的封裝,網絡請求的工作本質上是 OkHttp 完成,而 Retrofit 僅負責 網絡請求接口的封裝
Rxjava2+Retrofit
想要深層次用到ViewModel那麼我們最基礎的需要先可以訪問網絡有可用的網絡請求,首先添加網絡訪問權限
<uses-permission android:name="android.permission.INTERNET" />
引入我們需要的資源庫
implementation 'com.squareup.picasso:picasso:2.4.0'
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
//ConverterFactory的String依賴包
implementation 'com.squareup.retrofit2:converter-scalars:2.5.0'
//ConverterFactory的Gson依賴包
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
//CallAdapterFactory的Rx依賴包
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
implementation 'com.google.code.gson:gson:2.8.6'
首先我們定義一個類
public class ApiRetrofit {
}
添加部分常量參數
private final String BASE_URL = "http://v.juhe.cn/";
private final String BASE_DEBUG_URL = "http://v.juhe.cn/";
首先我們創建一個Retrofit對象
Retrofit.Builder();
然後我們給Retrofit添加請求baseUrl
retrofit = new Retrofit.Builder()
.baseUrl(BuildConfig.DEBUG ? BASE_DEBUG_URL : BASE_URL)
.build();
當然這時你就會想,我需要設置請求超時時間,攔截器呢?我該如何去做。因爲Retrofit是基於okhttp的去完成,那麼他的相關配置當然是有okhttp去完成配置了
private OkHttpClient client;
client = new OkHttpClient.Builder()
//添加log攔截器
// .addInterceptor(urlInterceptor)
.addInterceptor(logInterceptor)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build();
添加日誌請求日誌打印,和請求頭設置,在請求頭中我們可以添加token、協議版本號、加密算法sign等等
/**
* 請求訪問quest
* response攔截器
* 日誌攔截器
*/
private Interceptor logInterceptor = chain -> {
String timeStamp = DateUtil.getTimestamp();
Request request = chain.request().newBuilder()
.addHeader("x-token", UserAccountHelper.getToken() == null ? "" : UserAccountHelper.getToken())
.addHeader("x-timestamp", timeStamp)
.addHeader("x-uuid", UUID.randomUUID().toString())
.addHeader("x-appid", ApiAccountHelper.APPID)
.addHeader("x-phoneidentity", ApiAccountHelper.getDeviceId())
.addHeader("x-protocolversion", ApiAccountHelper.PROTOCOL_VERSION)
.addHeader("x-sign", encodeSign(bodyToString(chain.request().body()), timeStamp))
.build();
Response response = chain.proceed(request);
if (BuildConfig.DEBUG) {
printLog(request, response);
}
return response;
};
private void printLog(final Request request, final Response response) {
LogUtil.show("--------------------Request Start--------------------");
LogUtil.show("Method:" + request.method());
LogUtil.show("Url:" + request.url());
LogUtil.show("HttpHeader:" + request.headers().toString());
try {
LogUtil.show("請求參數:" + bodyToString(request.body()));
} catch (IOException e) {
LogUtil.show("請求參數解析失敗");
}
try {
ResponseBody responseBody = response.peekBody(1024 * 1024);
LogUtil.show("返回結果:" + responseBody.string());
} catch (Exception e) {
LogUtil.show("返回結果解析失敗");
}
LogUtil.show("--------------------Request End--------------------");
}
private String bodyToString(final RequestBody request) throws IOException {
final Buffer buffer = new Buffer();
if (request != null)
request.writeTo(buffer);
else
return "";
return buffer.readUtf8();
}
然後我們把okhttp相關配置添加給Retrofit
retrofit = new Retrofit.Builder()
.baseUrl(BuildConfig.DEBUG ? BASE_DEBUG_URL : BASE_URL)
.client(client)
.build();
現在我們需要做的就是把Retrofit暴露出去供接口調用,這時就要使用它的create方法了,但是乍一看你可能有點懵,你可以嘗試着進入create源碼中去看看它的源碼就明白了了
public <T> T create(final Class<T> service) {
//1、驗證服務接口。 檢驗文件是否是interface類型。 如果不是拋出異常。
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
//返回類型是通過動態代理生成的對象。T就是傳入的接口類。
//動態代理的invoke方法,會在每個方法調用的時候執行。也就是xxxxservice.doAction()的時候;(例子,假設doAction是接口中的一個方法);
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
//獲取平臺類型。檢查是Android還是Java。我們可以忽略,因爲開發Android,平臺類型一般都是Android。這裏會得到一個Android類型的Platform對象。
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
//檢查方法是否來自Object。如果是就正常調用。我們傳入的是接口類,所以這裏不會執行,直接跳過。
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
//這裏我們向isDefaultMethod方法裏面看,可以看到android平臺的這個方法永遠返回false。所以此處也是跳過。
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
//這裏是重點!!!,從這裏開始去解析目前正在執行的方法。 就是去我們傳入的接口類中,找到當前調用的doAction方法。同時去解析註解和各種參數。得到一個ServiceMethod對象。
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
//用serviceMethod對象生成一個OkHttpCall對象.serviceMethod中有解析到的各種配置。
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
//返回一個對象,如果我們沒有使用rxjava 那麼拿到的是call對象。如果使用了rxjava。那麼拿到的是Observable對象。
//此處拿到的返回對象,是在xxxxservice.doAction()時得到的對象。決定返回對象的類型。是在retrofit.builder.addCallAdapterFactory的時候.
return serviceMethod.adapt(okHttpCall);
}
});
}
從他的源碼看來是不是一下子就明白了呢,它是返回了一個代理接口
那麼我們新建一個接口
public interface ApiServiceHelper {
}
我們現在把Retrofit對象暴露給ApiServiceHelper接口
apiServiceHelper = retrofit.create(ApiServiceHelper.class);
但是我們在外部調用的時候爲了避免重複創建,需要把ApiRetrofit設置爲單例模式
private static ApiRetrofit apiRetrofit;
private Retrofit retrofit;
public static ApiRetrofit getInstance() {
if (apiRetrofit == null) {
synchronized (Object.class) {
if (apiRetrofit == null) {
apiRetrofit = new ApiRetrofit();
}
}
}
return apiRetrofit;
}
這時我們最基礎的網絡請求就封裝好了,我們先嚐試在ViewModel中去引用它
protected ApiServiceHelper apiServiceHelper = ApiRetrofit.getInstance().getApiService();
我們開始最簡易的網絡請求,在ApiServiceHelper先編寫一個接口;我使用聚合數據的免費api作爲測試
@Headers("x-url:sub")
@GET("toutiao/index")
Observable<PageBean<NewsBean>> getNews(@Query("key") String key, @Query("type") String type);
然後在ViewModel中去加載網絡請求,新建一個TestViewModel
apiServiceHelper
.getNews("d9ae666a0ff02c0486c0879570e56d6c", "top")
.subscribeOn(Schedulers.io()) //在IO線程進行網絡請求
.observeOn(AndroidSchedulers.mainThread()) //回到主線程去處理請求結果
.subscribe(consumer);
但是這樣的接口是不是似乎還少了什麼?
1、加載框顯示與隱藏
2、retry配置,當請求失敗後進行重試,比如,token過期,我們可以retry中調用刷新token的方法,讓用戶無感知的刷新token
3、exception處理
加載框
這時我們需要用到兩個方法doOnSubscribe、doFinally,這兩個方法分別執行與網絡請求開始前和結束後,我們可以在doOnSubscribe中顯示加載框,在doFinally隱藏加載框,這時我們可以調用baseView中的封裝方法
apiServiceHelper
.getNews("d9ae666a0ff02c0486c0879570e56d6c", "top")
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
addDisposable(disposable);
if (baseView != null && isShowDialog) {
baseView.showLoading(dialogMessage);
}
}
})
.doFinally(() -> {
if (baseView != null && isShowDialog) {
baseView.hideLoading();
}
})
.subscribeOn(Schedulers.io()) //在IO線程進行網絡請求
.observeOn(AndroidSchedulers.mainThread()) //回到主線程去處理請求結果
.subscribe(consumer);
Retrofit的重試機制retryWhen
點進retryWhen源碼可以看到retryWhen的構造方法很簡單是一個Function參數,但是別小看這個方法,他的功能性很強大。Funtion第一個參數爲Throwable,即當前接口拋出的異常,第二個參數爲ObservableSource,即我們可以通過處理異常然後返回一個新的ObservableSource對象繼續上一個請求,進行無感知刷新token等操作
.retryWhen((Function<Observable<Throwable>, ObservableSource<?>>) throwableObservable ->
throwableObservable.flatMap((Function<Throwable, ObservableSource<?>>) throwable -> {
if (throwable instanceof BaseException) {
BaseException baseException = (BaseException) throwable;
if (UserAccountHelper.isLoginPast(baseException.getErrorCode())) {
// 如果上面檢測到token過期就會進入到這裏
//拋出一個新的接口,刷新token接口
return apiServiceHelper.getNewToken()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.doOnNext(loginResultBean -> {
UserAccountHelper.setToken(loginResultBean.getToken());//存儲新的token
});// 這裏更新完成後就會進行重訂閱,從Observable.just(null)重新開始走。
}
}
return Observable.error(throwable);
}))
統一異常封裝我們等會再說,寫到這裏了,你會發現TestViewModel中一個請求接口就已經這麼長了,那如果好幾個接口那豈不是得寫很多重複代碼?於是我們可以把網絡請求再次封裝到BaseViewModel中去
//把統一操作全部放在這
protected <T> MutableLiveData<T> observe(Observable observable, final MutableLiveData<T> liveData) {
observable.subscribeOn(Schedulers.io())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
addDisposable(disposable);
if (baseView != null && isShowDialog) {
baseView.showLoading(dialogMessage);
}
}
})
.doFinally(() -> {
if (baseView != null && isShowDialog) {
baseView.hideLoading();
}
})
.observeOn(AndroidSchedulers.mainThread())
// .compose(objectLifecycleTransformer)
.retryWhen((Function<Observable<Throwable>, ObservableSource<?>>) throwableObservable ->
throwableObservable.flatMap((Function<Throwable, ObservableSource<?>>) throwable -> {
if (throwable instanceof BaseException) {
BaseException baseException = (BaseException) throwable;
if (UserAccountHelper.isLoginPast(baseException.getErrorCode())) {
// 如果上面檢測到token過期就會進入到這裏
// 然後下面的方法就是更新token
return apiServiceHelper.getNewToken()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.doOnNext(loginResultBean -> { UserAccountHelper.setToken(loginResultBean.getToken());//存儲新的token
});// 這裏更新完成後就會進行重訂閱,從Observable.just(null)重新開始走。
}
}
return Observable.error(throwable);
}))
.subscribe(o -> {
liveData.postValue((T) o);//通知數據更新
}, consumerError);
return liveData;
}
現在我們在TestViewModel中調用接口
//獲取首頁文章
public LiveData<PageBean<NewsBean>> getNews() {
return observe(apiServiceHelper.getNews("d9ae666a0ff02c0486c0879570e56d6c", "top"), mutableLiveData);
}
是不是方便很多了呢,細心的同學會發現上面我們多了幾個新的東西
- LiveData
- consumerError統一異常處理
LiveData
LiveData 是一個可被觀察的數據持有類。與普通的被觀察者(如 RxJava 中的 Observable)不同的是,LiveData 是生命週期感知的,也就是說,它能感知其它應用組件(Activity,Fragment,Service)的生命週期。這種感知能力可以確保只有處於 active 狀態的組件才能收到 LiveData 的更新。詳情可查看 Lifecycle。
這是官方給予的定義,不做過多解釋,不明白的同學可以去查看寫LiveData的使用
Error統一異常處理
細心的小夥伴會發現subscribe訂閱方法的構造參數有很多
我就不一一解讀了,他的構造參數還有很多,我們需要自定義自己的異常因爲我們使用第二種構造方法,
- onNext即成功的情況下的回調方法
- onError即失敗和異常的情況下回調方法
BaseException封裝
我們首先定義BaseException基類,定義一些常用的異常code
/**
* 解析數據失敗
*/
static final String PARSE_ERROR = "1001";
public static final String PARSE_ERROR_MSG = "解析數據失敗";
/**
* 網絡問題
*/
static final String BAD_NETWORK = "1002";
static final String BAD_NETWORK_MSG = "服務器或網絡異常";
/**
* 連接錯誤
*/
static final String CONNECT_ERROR = "1003";
static final String CONNECT_ERROR_MSG = "連接錯誤";
/**
* 連接超時
*/
static final String CONNECT_TIMEOUT = "1004";
static final String CONNECT_TIMEOUT_MSG = "連接超時";
/**
* 未知錯誤
*/
static final String OTHER = "1005";
static final String OTHER_MSG = "未知錯誤";
/**
* 其他問題,即服務器返回的請求失敗
*/
public static final String REQUEST_ERROR = "1006";
/**
* 登錄超時
*/
public static final String TOKEN_ERROR = "1007";
public static final String TOKEN_ERROR_MSG = "登錄超時";
我們給BaseExcepition暴露兩個參數共外部調用
private String errorMsg;//異常信息描述
private String errorCode;//異常code
貼上完整代碼
public class BaseException extends IOException {
private static final long serialVersionUID = 602780230218501625L;
/**
* 解析數據失敗
*/
static final String PARSE_ERROR = "1001";
public static final String PARSE_ERROR_MSG = "解析數據失敗";
/**
* 網絡問題
*/
static final String BAD_NETWORK = "1002";
static final String BAD_NETWORK_MSG = "服務器或網絡異常";
/**
* 連接錯誤
*/
static final String CONNECT_ERROR = "1003";
static final String CONNECT_ERROR_MSG = "連接錯誤";
/**
* 連接超時
*/
static final String CONNECT_TIMEOUT = "1004";
static final String CONNECT_TIMEOUT_MSG = "連接超時";
/**
* 未知錯誤
*/
static final String OTHER = "1005";
static final String OTHER_MSG = "未知錯誤";
/**
* 其他問題,即服務器返回的請求失敗
*/
public static final String REQUEST_ERROR = "1006";
/**
* 登錄超時
*/
public static final String TOKEN_ERROR = "1007";
public static final String TOKEN_ERROR_MSG = "登錄超時";
private String errorMsg;
private String errorCode;
String getErrorMsg() {
return errorMsg;
}
String getErrorCode() {
return errorCode;
}
public BaseException(String errorMsg, Throwable cause) {
super(errorMsg, cause);
this.errorMsg = errorMsg;
}
BaseException(String message, Throwable cause, String errorCode) {
super(message, cause);
this.errorCode = errorCode;
this.errorMsg = message;
}
BaseException(String message, String errorCode) {
this.errorCode = errorCode;
this.errorMsg = message;
}
}
現在我們需要做的就是在subscribe訂閱方法中重寫錯誤異常
new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
}
})
問題就是我們如何去實現這個異常的處理
1、當進入異常時我們需要先關閉加載框
if (baseView != null && isShowDialog) {
baseView.hideLoading();
}
2、我們獲取exception的code和message
BaseException be = new BaseException(BaseException.OTHER_MSG, e, BaseException.OTHER);
3、把我們的異常回調給activity和fragment
if (baseView != null) {
baseView.onErrorCode(new BaseModelEntity(be.getErrorCode(), be.getErrorMsg()));
baseView.showToast(be.getErrorMsg());
}
這樣我們簡易的異常處理就做好了,後續我們給他在完善一下,這裏你們會發現多了一個BaseModelEntity類,這個類是做什麼的呢?是用來處理接口返回數據統一處理的,那麼我們繼續講解下一個問題
接口數據統一處理
大部分同學的接口定義都是類似於這樣
接口返回格式是統一的我們不需要對其進行每個接口都處理一遍,我們可以利用Retrofit的特性進行統一處理
我們新建一個實體類
public class BaseModelEntity<T> implements Serializable {
public BaseModelEntity() {
}
public BaseModelEntity(String code, String msg) {
this.error_code = code;
this.reason = msg;
}
private String error_code; //類型:String 必有字段 備註:錯誤標識,根據該字段判斷服務器操作是否成功
private String reason; //類型:String 必有字段 備註:錯誤信息
private T result;
public String getCode() {
return error_code;
}
public void setCode(String code) {
this.error_code = code;
}
public String getMsg() {
return reason;
}
public void setMsg(String msg) {
this.reason = msg;
}
public T getData() {
return result;
}
public void setData(T data) {
this.result = data;
}
}
如果有列表的話還可以定義一個列表的統一實體類
public class PageBean<T> implements Serializable {
private List<T> data;
private String nextPageToken;
private String prevPageToken;
private int requestCount;
private int responseCount;
private int rowCount;
public List<T> getList() {
return data;
}
public void setList(List<T> list) {
this.data = list;
}
public int getRowCount() {
return rowCount;
}
public void setRowCount(int rowCount) {
this.rowCount = rowCount;
}
public String getNextPageToken() {
return nextPageToken;
}
public void setNextPageToken(String nextPageToken) {
this.nextPageToken = nextPageToken;
}
public String getPrevPageToken() {
return prevPageToken;
}
public void setPrevPageToken(String prevPageToken) {
this.prevPageToken = prevPageToken;
}
public int getRequestCount() {
return requestCount;
}
public void setRequestCount(int requestCount) {
this.requestCount = requestCount;
}
public int getResponseCount() {
return responseCount;
}
public void setResponseCount(int responseCount) {
this.responseCount = responseCount;
}
public static class PageInfo implements Serializable {
private int totalResults;
private int resultsPerPage;
public int getTotalResults() {
return totalResults;
}
public void setTotalResults(int totalResults) {
this.totalResults = totalResults;
}
public int getResultsPerPage() {
return resultsPerPage;
}
public void setResultsPerPage(int resultsPerPage) {
this.resultsPerPage = resultsPerPage;
}
}
}
Retrofit提供了封裝方法供我們去實現數據統一處理
/** Add converter factory for serialization and deserialization of objects. */
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
然後我們重新只需要重寫Converter.Factory類即可
public final class BaseConverterFactory extends Converter.Factory {
public static BaseConverterFactory create() {
return create(new Gson());
}
//工廠方法,用於創建實例
@SuppressWarnings("ConstantConditions")
public static BaseConverterFactory create(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
return new BaseConverterFactory(gson);
}
private final Gson gson;
private BaseConverterFactory(Gson gson) {
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new BaseResponseBodyConverter<>(gson, adapter);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new BaseRequestBodyConverter<>(gson, adapter);
}
}
Converter.Factory工廠類有幾個方法需要重寫
responseBodyConverter,返回結果處理
requestBodyConverter,請求參數處理
接下來我們只需要重寫這兩個接口的實現類即可
新建一個類,重寫requestBody數據的處理方法
class BaseRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
BaseRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}
同樣的我們新建一個類,去重寫responseBody數據處理方法
public class BaseResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final TypeAdapter<T> adapter;
BaseResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
String jsonString = value.string();
try {
BaseModelEntity baseModel = new GsonBuilder().disableHtmlEscaping().create().fromJson(jsonString,
new TypeToken<BaseModelEntity<T>>() {
}.getType());//利用統一實體類對象進行json解析
if (!"0".equals(baseModel.getCode())) {//判斷接口是否成功,如果不成功直接拋出異常
throw new BaseException(baseModel.getMsg(), baseModel.getCode());
}
//如果返回code是成功的話,則去除data對象直接返回,注意這裏data對象如果爲null,並且你接口觀察者用的是Consumer的話,會拋出異常,
//你可以嘗試把null轉爲爲一個字符串拋出
return adapter.fromJson(new Gson().toJson(baseModel.getData() == null ? "操作完成" : baseModel.getData()));
} catch (Exception e) {
e.printStackTrace();
//數據解析異常
throw e;
} finally {
value.close();
}
}
}
然後重寫Converter.Factory,把剛纔重寫的數據方法設置上去
retrofit = new Retrofit.Builder()
.baseUrl(BuildConfig.DEBUG ? BASE_DEBUG_URL : BASE_URL)
.addConverterFactory(BaseConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//支持RxJava2
.client(client)
.build();
在返回結果的統一處理中,我們拋出了幾個異常
- json解析異常
- 接口處理異常
那麼我們可以豐富一下我們的異常處理了
首先判斷返回的異常是不是服務器返回的
if (e instanceof BaseException) {
be = (BaseException) e;
//回調到view層 處理 或者根據項目情況處理
if (baseView != null) {
baseView.onErrorCode(new BaseModelEntity(be.getErrorCode(), be.getErrorMsg()));
}
}
如果是直接回調給activity和fragment處理,如果不是的服務器返回的,那麼可能是接口請求超時了或者請求被攔截了等等
if (e instanceof HttpException) {
// HTTP錯誤
be = new BaseException(BaseException.BAD_NETWORK_MSG, e, BaseException.BAD_NETWORK);
} else if (e instanceof ConnectException
|| e instanceof UnknownHostException) {
// 連接錯誤
be = new BaseException(BaseException.CONNECT_ERROR_MSG, e, BaseException.CONNECT_ERROR);
} else if (e instanceof InterruptedIOException) {
// 連接超時
be = new BaseException(BaseException.CONNECT_TIMEOUT_MSG, e, BaseException.CONNECT_TIMEOUT);
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) {
// 解析錯誤
be = new BaseException(BaseException.PARSE_ERROR_MSG, e, BaseException.PARSE_ERROR);
} else {
be = new BaseException(BaseException.OTHER_MSG, e, BaseException.OTHER);
}
}
這樣我們activity和fragment中可以根據不同的情況對不同的code進行相應的處理
到此網絡請求封裝基本完成啦,我們只需要在頁面中,把返回值對應的實體類綁定到xml中即可
mViewModel.getNews().observe(this,news -> {
binding.SetNews(news);
});
貼上完善後的BaseViewModel完整代碼
public class BaseViewModel<V extends BaseView> extends AndroidViewModel {
//離開頁面,是否取消網絡
private CompositeDisposable compositeDisposable;
//如果開啓,同一url還在請求網絡時,不會
public ArrayList<String> onNetTags;
private String dialogMessage = "正在加載,請稍後...";
private LifecycleTransformer objectLifecycleTransformer;
protected V baseView;
private boolean isShowDialog;
protected ApiServiceHelper apiServiceHelper = ApiRetrofit.getInstance().getApiService();
public BaseViewModel(@NonNull Application application) {
super(application);
this.isShowDialog = true;
}
protected void setBaseView(V baseView) {
this.baseView = baseView;
}
public void setShowDialog(boolean showDialog) {
isShowDialog = showDialog;
}
public V getBaseView() {
return baseView;
}
public boolean isShowDialog() {
return isShowDialog;
}
@Override
protected void onCleared() {
super.onCleared();
}
public void setDialogMessage(String dialogMessage) {
this.dialogMessage = dialogMessage;
}
private void addDisposable(Disposable disposable) {
if (compositeDisposable == null) {
compositeDisposable = new CompositeDisposable();
}
compositeDisposable.add(disposable);
}
private void removeDisposable() {
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
}
public void setObjectLifecycleTransformer(LifecycleTransformer objectLifecycleTransformer) {
this.objectLifecycleTransformer = objectLifecycleTransformer;
}
//把統一操作全部放在這,不會重連
@SuppressLint("CheckResult")
protected <T> MutableLiveData<T> observe(Observable observable, boolean isShowDialog,final MutableLiveData<T> liveData) {
this.isShowDialog = isShowDialog;
return observe(observable,liveData);
}
//把統一操作全部放在這,不會重連
@SuppressLint("CheckResult")
protected <T> MutableLiveData<T> observe(Observable observable, final MutableLiveData<T> liveData) {
observable.subscribeOn(Schedulers.io())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
addDisposable(disposable);
if (baseView != null && isShowDialog) {
baseView.showLoading(dialogMessage);
}
}
})
.doFinally(() -> {
if (baseView != null && isShowDialog) {
baseView.hideLoading();
}
})
.observeOn(AndroidSchedulers.mainThread())
// .compose(objectLifecycleTransformer)
.retryWhen((Function<Observable<Throwable>, ObservableSource<?>>) throwableObservable ->
throwableObservable.flatMap((Function<Throwable, ObservableSource<?>>) throwable -> {
if (throwable instanceof BaseException) {
BaseException baseException = (BaseException) throwable;
if (UserAccountHelper.isLoginPast(baseException.getErrorCode())) {
// 如果上面檢測到token過期就會進入到這裏
// 然後下面的方法就是更新token
return apiServiceHelper.getNewToken()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.doOnNext(loginResultBean -> {
UserAccountHelper.setToken(loginResultBean.getToken());//存儲新的token
});// 這裏更新完成後就會進行重訂閱,從Observable.just(null)重新開始走。
}
}
return Observable.error(throwable);
}))
.subscribe(o -> {
liveData.postValue((T) o);
}, consumerError);
return liveData;
}
protected Action finallyAction = new Action() {
@Override
public void run() throws Exception {
if (baseView != null && isShowDialog) {
baseView.hideLoading();
}
}
};
protected Consumer consumerError = (Consumer<Throwable>) e -> {
LogUtil.show("BaseViewModel|系統異常: " + e);
if (baseView != null && isShowDialog) {
baseView.hideLoading();
}
BaseException be = null;
if (e != null) {
if (e instanceof BaseException) {
be = (BaseException) e;
//回調到view層 處理 或者根據項目情況處理
if (baseView != null) {
baseView.onErrorCode(new BaseModelEntity(be.getErrorCode(), be.getErrorMsg()));
}
} else {
if (e instanceof HttpException) {
// HTTP錯誤
be = new BaseException(BaseException.BAD_NETWORK_MSG, e, BaseException.BAD_NETWORK);
} else if (e instanceof ConnectException
|| e instanceof UnknownHostException) {
// 連接錯誤
be = new BaseException(BaseException.CONNECT_ERROR_MSG, e, BaseException.CONNECT_ERROR);
} else if (e instanceof InterruptedIOException) {
// 連接超時
be = new BaseException(BaseException.CONNECT_TIMEOUT_MSG, e, BaseException.CONNECT_TIMEOUT);
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) {
// 解析錯誤
be = new BaseException(BaseException.PARSE_ERROR_MSG, e, BaseException.PARSE_ERROR);
} else {
be = new BaseException(BaseException.OTHER_MSG, e, BaseException.OTHER);
}
}
} else {
be = new BaseException(BaseException.OTHER_MSG, e, BaseException.OTHER);
}
LogUtil.show("BaseViewModel|異常消息: " + be.getErrorMsg());
if (baseView != null) {
baseView.onErrorCode(new BaseModelEntity(be.getErrorCode(), be.getErrorMsg()));
baseView.showToast(be.getErrorMsg());
}
};
}
ApiRetrofit完整代碼
public class ApiRetrofit {
private final String BASE_URL = "http://v.juhe.cn/";
private final String BASE_DEBUG_URL = "http://v.juhe.cn/";
private static ApiRetrofit apiRetrofit;
private Retrofit retrofit;
private OkHttpClient client;
private ApiServiceHelper apiServiceHelper;
/**
* 動態修改url
*/
private Interceptor urlInterceptor = chain -> {
// 獲取request
Request request = chain.request();
// 從request中獲取原有的HttpUrl實例oldHttpUrl
HttpUrl oldHttpUrl = request.url();
// 獲取request的創建者builder
Request.Builder builder = request.newBuilder();
// 從request中獲取headers,通過給定的鍵url_name
List<String> headerValues = request.headers("x-url");
if (headerValues.size() > 0) {
// 如果有這個header,先將配置的header刪除,因此header僅用作app和okhttp之間使用
builder.removeHeader("x-url");
// 匹配獲得新的BaseUrl
String headerValue = headerValues.get(0);
HttpUrl newBaseUrl = null;
if ("sub".equals(headerValue)) {
newBaseUrl = HttpUrl.parse(UserAccountHelper.getBaseUrl());
} else if ("admin".equals(headerValue)) {
newBaseUrl = HttpUrl.parse(BuildConfig.DEBUG ? BASE_DEBUG_URL : BASE_URL);
} else {
newBaseUrl = oldHttpUrl;
}
// 重建新的HttpUrl,修改需要修改的url部分
HttpUrl newFullUrl = oldHttpUrl
.newBuilder()
// 更換網絡協議
.scheme(newBaseUrl.scheme())
// 更換主機名
.host(newBaseUrl.host())
// 更換端口
.port(newBaseUrl.port())
.build();
// 重建這個request,通過builder.url(newFullUrl).build();
// 然後返回一個response至此結束脩改
return chain.proceed(builder.url(newFullUrl).build());
}
return chain.proceed(request);
};
/**
* 請求訪問quest
* response攔截器
* 日誌攔截器
*/
private Interceptor logInterceptor = chain -> {
String timeStamp = DateUtil.getTimestamp();
Request request = chain.request().newBuilder()
.addHeader("x-token", UserAccountHelper.getToken() == null ? "" : UserAccountHelper.getToken())
.addHeader("x-timestamp", timeStamp)
.addHeader("x-uuid", UUID.randomUUID().toString())
.addHeader("x-phoneidentity", ApiAccountHelper.getDeviceId())
.addHeader("x-phoneinfo", ApiAccountHelper.getPhoneInfo())
.build();
Response response = chain.proceed(request);
if (BuildConfig.DEBUG) {
printLog(request, response);
}
return response;
};
private void printLog(final Request request, final Response response) {
LogUtil.show("--------------------Request Start--------------------");
LogUtil.show("Method:" + request.method());
LogUtil.show("Url:" + request.url());
LogUtil.show("HttpHeader:" + request.headers().toString());
try {
LogUtil.show("請求參數:" + bodyToString(request.body()));
} catch (IOException e) {
LogUtil.show("請求參數解析失敗");
}
try {
ResponseBody responseBody = response.peekBody(1024 * 1024);
LogUtil.show("返回結果:" + responseBody.string());
} catch (Exception e) {
LogUtil.show("返回結果解析失敗");
}
LogUtil.show("--------------------Request End--------------------");
}
private String bodyToString(final RequestBody request) throws IOException {
final Buffer buffer = new Buffer();
if (request != null)
request.writeTo(buffer);
else
return "";
return buffer.readUtf8();
}
private ApiRetrofit() {
client = new OkHttpClient.Builder()
//添加log攔截器
// .addInterceptor(urlInterceptor)
.addInterceptor(logInterceptor)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BuildConfig.DEBUG ? BASE_DEBUG_URL : BASE_URL)
.addConverterFactory(BaseConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//支持RxJava2
.client(client)
.build();
apiServiceHelper = retrofit.create(ApiServiceHelper.class);
}
public static ApiRetrofit getInstance() {
if (apiRetrofit == null) {
synchronized (Object.class) {
if (apiRetrofit == null) {
apiRetrofit = new ApiRetrofit();
}
}
}
return apiRetrofit;
}
public ApiServiceHelper getApiService() {
return apiServiceHelper;
}
}
後續會上傳demo,如果有講解不清晰的可以留言