一步步封裝實現自己的網路請求框架
一、前言
現如今 Android 領域流行的網路請求框架基本都是用 Retrofit 加 RxJava 來搭配構建的,而以 ViewModel + LiveData + Retrofit + RxJava 來構建請求框架的例子要相對少得多。而本文就是以這四者作為基礎元件,介紹如何一步步封裝實現自己的網路請求框架(本文實現的例子不僅僅只是一個網路請求框架,同時也是在介紹應用的架構模式),希望對你有所幫助
目前已實現的功能或者說特色包含以下幾點:
1、網路請求結果基於觀察者模式進行傳遞,回撥操作與 UI 層的生命週期相繫結,避免了記憶體洩漏
2、資料載入時的 startLoading 與載入結束後的 dismissLoading 操作都是自動呼叫的,具體實現都封裝在基類中。當然,子類也可以實現自己的特定實現。例如,本文提供的例子中,BaseActivity 實現的載入對話方塊是 ProgressDialog ,子 Activity 可以自主實現其他彈窗形式
3、當網路請求結果為非成功狀態時(網路請求失敗或者業務請求失敗),預設操作是用 Toast 提示失敗原因,支援自定義實現失敗時的操作
4、邏輯操作與 UI 層相分離,基於觀察者模式來實現訊息驅動 UI 變化。提供了在 ViewModel 中操作 UI 變化的能力,包括使 Activity / Fragment 彈出對話方塊、Toast 訊息、finishActivity 等 UI 操作,但 ViewModel 不持有 Activity / Fragment 的引用,而是基於訊息驅動實現,從而避免了記憶體洩漏
原始碼點選這裡檢視: ViewModel_Retrofit_RxJava
Apk 點選這裡下載: ViewModel_Retrofit_RxJava
二、封裝 BaseViewModel 與 BaseActivity
ViewModel與 LiveData 都是 Android Jetpack 架構元件之一。ViewModel 被設計用來儲存和管理 UI 相關資料,以便資料能在介面銷燬時(比如螢幕旋轉)儲存資料,而與 ViewModel 相掛鉤的 LiveData 是一個用於儲存可以被觀察的值的資料持有類,且遵循應用元件的生命週期,只有在元件的生命週期處於活躍狀態時才會收到資料更新通知
既然是訊息驅動,那麼自然需要一個用於抽象訊息型別的 Event 類
/** * 作者:leavesC * 時間:2018/9/30 22:17 * 描述: * GitHub:https://github.com/leavesC * Blog:https://www.jianshu.com/u/9df45b87cfdf */ public class BaseEvent { private int action; public BaseEvent(int action) { this.action = action; } public int getAction() { return action; } } public class BaseActionEvent extends BaseEvent { public static final int SHOW_LOADING_DIALOG = 1; public static final int DISMISS_LOADING_DIALOG = 2; public static final int SHOW_TOAST = 3; public static final int FINISH = 4; public static final int FINISH_WITH_RESULT_OK = 5; private String message; public BaseActionEvent(int action) { super(action); } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
BaseActionEvent即用於向 View 層 傳遞 Action 的 Model ,在 ViewModel 通過向 View 層 傳遞不同的訊息型別,從而觸發相對應的操作。因此, BaseViewModel 需要向子類提供預設的實現
public interface IViewModelAction { void startLoading(); void startLoading(String message); void dismissLoading(); void showToast(String message); void finish(); void finishWithResultOk(); MutableLiveData<BaseActionEvent> getActionLiveData(); }
/** * 作者:leavesC * 時間:2018/9/30 22:24 * 描述: * GitHub:https://github.com/leavesC * Blog:https://www.jianshu.com/u/9df45b87cfdf */ public class BaseViewModel extends ViewModel implements IViewModelAction { private MutableLiveData<BaseActionEvent> actionLiveData; protected LifecycleOwner lifecycleOwner; public BaseViewModel() { actionLiveData = new MutableLiveData<>(); } @Override public void startLoading() { startLoading(null); } @Override public void startLoading(String message) { BaseActionEvent baseActionEvent = new BaseActionEvent(BaseActionEvent.SHOW_LOADING_DIALOG); baseActionEvent.setMessage(message); actionLiveData.setValue(baseActionEvent); } @Override public void dismissLoading() { actionLiveData.setValue(new BaseActionEvent(BaseActionEvent.DISMISS_LOADING_DIALOG)); } @Override public void showToast(String message) { BaseActionEvent baseActionEvent = new BaseActionEvent(BaseActionEvent.SHOW_TOAST); baseActionEvent.setMessage(message); actionLiveData.setValue(baseActionEvent); } @Override public void finish() { actionLiveData.setValue(new BaseActionEvent(BaseActionEvent.FINISH)); } @Override public void finishWithResultOk() { actionLiveData.setValue(new BaseActionEvent(BaseActionEvent.FINISH_WITH_RESULT_OK)); } @Override public MutableLiveData<BaseActionEvent> getActionLiveData() { return actionLiveData; } void setLifecycleOwner(LifecycleOwner lifecycleOwner) { this.lifecycleOwner = lifecycleOwner; } }
那作為訊息傳送方的 BaseViewModel 的具體實現就完成了,之後是訊息的接收方 BaseActivity / BaseFragment
BaseActivity通過監聽 BaseViewModel 中 actionLiveData 的資料變化從而在 網路請求開始載入時 startLoading ,在 載入結束時 dismissLoading
一般一個 Activity 對應一個 ViewModel ,少部分情況是會對應多個 ViewModel ,因此 initViewModel() 宣告為了抽象方法,而 initViewModelList() 預設返回了 null
/** * 作者:leavesC * 時間:2017/11/29 21:04 * 描述: * GitHub:https://github.com/leavesC * Blog:https://www.jianshu.com/u/9df45b87cfdf */ @SuppressLint("Registered") public abstract class BaseActivity extends AppCompatActivity { private ProgressDialog loadingDialog; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); initViewModelEvent(); } protected abstract ViewModel initViewModel(); protected List<ViewModel> initViewModelList() { return null; } private void initViewModelEvent() { List<ViewModel> viewModelList = initViewModelList(); if (viewModelList != null && viewModelList.size() > 0) { observeEvent(viewModelList); } else { ViewModel viewModel = initViewModel(); if (viewModel != null) { List<ViewModel> modelList = new ArrayList<>(); modelList.add(viewModel); observeEvent(modelList); } } } private void observeEvent(List<ViewModel> viewModelList) { for (ViewModel viewModel : viewModelList) { if (viewModel instanceof IViewModelAction) { IViewModelAction viewModelAction = (IViewModelAction) viewModel; viewModelAction.getActionLiveData().observe(this, baseActionEvent -> { if (baseActionEvent != null) { switch (baseActionEvent.getAction()) { case BaseActionEvent.SHOW_LOADING_DIALOG: { startLoading(baseActionEvent.getMessage()); break; } case BaseActionEvent.DISMISS_LOADING_DIALOG: { dismissLoading(); break; } case BaseActionEvent.SHOW_TOAST: { showToast(baseActionEvent.getMessage()); break; } case BaseActionEvent.FINISH: { finish(); break; } case BaseActionEvent.FINISH_WITH_RESULT_OK: { setResult(RESULT_OK); finish(); break; } } } }); } } } @Override protected void onDestroy() { super.onDestroy(); dismissLoading(); } protected void startLoading() { startLoading(null); } protected void startLoading(String message) { if (loadingDialog == null) { loadingDialog = new ProgressDialog(this); loadingDialog.setCancelable(false); loadingDialog.setCanceledOnTouchOutside(false); } loadingDialog.setTitle(message); loadingDialog.show(); } protected void dismissLoading() { if (loadingDialog != null && loadingDialog.isShowing()) { loadingDialog.dismiss(); } } protected void showToast(String message) { Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); } protected void finishWithResultOk() { setResult(RESULT_OK); finish(); } protected BaseActivity getContext() { return BaseActivity.this; } protected void startActivity(Class cl) { startActivity(new Intent(this, cl)); } public void startActivityForResult(Class cl, int requestCode) { startActivityForResult(new Intent(this, cl), requestCode); } @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) protected boolean isFinishingOrDestroyed() { return isFinishing() || isDestroyed(); } }
三、封裝 Retrofit 與 RxJava
在前言中說了,框架預設實現了請求失敗時的操作(Toast 提示失敗原因),也支援自定義回撥介面。因此,需要兩個回撥介面,一個只包含請求成功時的回撥介面,另一個多包含了一個請求失敗時的回撥介面
/** * 作者:leavesC * 時間:2018/10/27 20:53 * 描述: * GitHub:https://github.com/leavesC * Blog:https://www.jianshu.com/u/9df45b87cfdf */ public interface RequestCallback<T> { void onSuccess(T t); } public interface RequestMultiplyCallback<T> extends RequestCallback<T> { void onFail(BaseException e); }
此外,為了在網路請求成功但業務邏輯請求失敗時(例如,請求引數缺失、Token失效等),可以丟擲詳細的失敗資訊,需要自定義 BaseException
public class BaseException extends RuntimeException { private int errorCode = HttpCode.CODE_UNKNOWN; public BaseException() { } public BaseException(int errorCode, String errorMessage) { super(errorMessage); this.errorCode = errorCode; } public int getErrorCode() { return errorCode; } }
實現具體的異常類
public class ParamterInvalidException extends BaseException { public ParamterInvalidException() { super(HttpCode.CODE_PARAMETER_INVALID, "引數有誤"); } } public class TokenInvalidException extends BaseException { public TokenInvalidException() { super(HttpCode.CODE_TOKEN_INVALID, "Token失效"); } } ···
為了提升效能, Retrofit 一般是設計成單例模式。為了應對應用中 BaseUrl 可能有多個的情況(本文提供的Demo就是如此),此處使用 Map 來儲存多個 Retrofit 例項
/** * 作者:leavesC * 時間:2018/10/26 23:11 * 描述: * GitHub:https://github.com/leavesC * Blog:https://www.jianshu.com/u/9df45b87cfdf */ public class RetrofitManagement { private static final long READ_TIMEOUT = 6000; private static final long WRITE_TIMEOUT = 6000; private static final long CONNECT_TIMEOUT = 6000; private final Map<String, Object> serviceMap = new ConcurrentHashMap<>(); private RetrofitManagement() { } public static RetrofitManagement getInstance() { return RetrofitHolder.retrofitManagement; } private static class RetrofitHolder { private static final RetrofitManagement retrofitManagement = new RetrofitManagement(); } private Retrofit createRetrofit(String url) { OkHttpClient.Builder builder = new OkHttpClient.Builder() .readTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS) .writeTimeout(WRITE_TIMEOUT, TimeUnit.MILLISECONDS) .connectTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS) .addInterceptor(new HttpInterceptor()) .addInterceptor(new HeaderInterceptor()) .addInterceptor(new FilterInterceptor()) .retryOnConnectionFailure(true); if (BuildConfig.DEBUG) { HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(); httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); builder.addInterceptor(httpLoggingInterceptor); builder.addInterceptor(new ChuckInterceptor(ContextHolder.getContext())); } OkHttpClient client = builder.build(); return new Retrofit.Builder() .client(client) .baseUrl(url) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); } <T> ObservableTransformer<BaseResponseBody<T>, T> applySchedulers() { return observable -> observable.subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(result -> { switch (result.getCode()) { case HttpCode.CODE_SUCCESS: { return createData(result.getData()); } case HttpCode.CODE_TOKEN_INVALID: { throw new TokenInvalidException(); } case HttpCode.CODE_ACCOUNT_INVALID: { throw new AccountInvalidException(); } default: { throw new ServerResultException(result.getCode(), result.getMsg()); } } }); } private <T> Observable<T> createData(T t) { return Observable.create(new ObservableOnSubscribe<T>() { @Override public void subscribe(ObservableEmitter<T> emitter) { try { emitter.onNext(t); emitter.onComplete(); } catch (Exception e) { emitter.onError(e); } } }); } <T> T getService(Class<T> clz) { return getService(clz, HttpConfig.BASE_URL_WEATHER); } <T> T getService(Class<T> clz, String host) { T value; if (serviceMap.containsKey(host)) { Object obj = serviceMap.get(host); if (obj == null) { value = createRetrofit(host).create(clz); serviceMap.put(host, value); } else { value = (T) obj; } } else { value = createRetrofit(host).create(clz); serviceMap.put(host, value); } return value; } }
此外還需要一個自定義的 Observer 來對資料請求結果進行自定義回撥
/** * 作者:leavesC * 時間:2018/10/27 20:52 * 描述: * GitHub:https://github.com/leavesC * Blog:https://www.jianshu.com/u/9df45b87cfdf */ public class BaseSubscriber<T> extends DisposableObserver<T> { private BaseViewModel baseViewModel; private RequestCallback<T> requestCallback; public BaseSubscriber(BaseViewModel baseViewModel) { this.baseViewModel = baseViewModel; } BaseSubscriber(BaseViewModel baseViewModel, RequestCallback<T> requestCallback) { this.baseViewModel = baseViewModel; this.requestCallback = requestCallback; } @Override public void onNext(T t) { if (requestCallback != null) { requestCallback.onSuccess(t); } } @Override public void onError(Throwable e) { e.printStackTrace(); if (requestCallback instanceof RequestMultiplyCallback) { RequestMultiplyCallback callback = (RequestMultiplyCallback) requestCallback; if (e instanceof BaseException) { callback.onFail((BaseException) e); } else { callback.onFail(new BaseException(HttpCode.CODE_UNKNOWN, e.getMessage())); } } else { if (baseViewModel == null) { Toast.makeText(ContextHolder.getContext(), e.getMessage(), Toast.LENGTH_SHORT).show(); } else { baseViewModel.showToast(e.getMessage()); } } } @Override public void onComplete() { } }
四、BaseRemoteDataSource 與 BaseRepo
上文所介紹的 RequestCallback、RetrofitManagement 與 BaseSubscriber 還是一個個單獨的個體,還需要一個 連結器 來將之串起來,這個連結器的實現類即 BaseRemoteDataSource
在這裡,對 BaseRemoteDataSource 的定位是將之當成一個 介面實現者 ,即在 RemoteDataSource 中實際呼叫各個請求介面,並通過 RxJava 來控制 loading 彈出以及銷燬的時機
一般而言, BaseRemoteDataSource 的實現類中宣告的是具有相關邏輯的介面。例如,對於登入模組,可宣告一個 LoginDataSource ,對於設定模組,可以宣告一個 SettingsDataSource
/** * 作者:leavesC * 時間:2018/10/27 7:42 * 描述: * GitHub:https://github.com/leavesC * Blog:https://www.jianshu.com/u/9df45b87cfdf */ public abstract class BaseRemoteDataSource { private CompositeDisposable compositeDisposable; private BaseViewModel baseViewModel; public BaseRemoteDataSource(BaseViewModel baseViewModel) { this.compositeDisposable = new CompositeDisposable(); this.baseViewModel = baseViewModel; } protected <T> T getService(Class<T> clz) { return RetrofitManagement.getInstance().getService(clz); } protected <T> T getService(Class<T> clz, String host) { return RetrofitManagement.getInstance().getService(clz, host); } private <T> ObservableTransformer<BaseResponseBody<T>, T> applySchedulers() { return RetrofitManagement.getInstance().applySchedulers(); } protected <T> void execute(Observable observable, RequestCallback<T> callback) { execute(observable, new BaseSubscriber<>(baseViewModel, callback), true); } protected <T> void execute(Observable observable, RequestMultiplyCallback<T> callback) { execute(observable, new BaseSubscriber<>(baseViewModel, callback), true); } public void executeWithoutDismiss(Observable observable, Observer observer) { execute(observable, observer, false); } private void execute(Observable observable, Observer observer, boolean isDismiss) { Disposable disposable = (Disposable) observable .throttleFirst(500, TimeUnit.MILLISECONDS) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .compose(applySchedulers()) .compose(isDismiss ? loadingTransformer() : loadingTransformerWithoutDismiss()) .subscribeWith(observer); addDisposable(disposable); } private void addDisposable(Disposable disposable) { compositeDisposable.add(disposable); } public void dispose() { if (!compositeDisposable.isDisposed()) { compositeDisposable.dispose(); } } private void startLoading() { if (baseViewModel != null) { baseViewModel.startLoading(); } } private void dismissLoading() { if (baseViewModel != null) { baseViewModel.dismissLoading(); } } private <T> ObservableTransformer<T, T> loadingTransformer() { return observable -> observable .subscribeOn(AndroidSchedulers.mainThread()) .unsubscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe(disposable -> startLoading()) .doFinally(this::dismissLoading); } private <T> ObservableTransformer<T, T> loadingTransformerWithoutDismiss() { return observable -> observable .subscribeOn(AndroidSchedulers.mainThread()) .unsubscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe(disposable -> startLoading()); } }
除了 BaseRemoteDataSource 外,還需要一個 BaseRepo 。對 BaseRepo 的定位是將其當做一個介面排程器,其持有 BaseRemoteDataSource 的例項並中轉 ViewModel 的介面呼叫請求,並可以在 BaseRepo 分擔一部分 資料處理邏輯
/** * 作者:leavesC * 時間:2018/10/27 21:10 * 描述: * GitHub:https://github.com/leavesC * Blog:https://www.jianshu.com/u/9df45b87cfdf */ public class BaseRepo<T> { protected T remoteDataSource; public BaseRepo(T remoteDataSource) { this.remoteDataSource = remoteDataSource; } }
這樣, ViewModel 不關心介面的實際呼叫實現,方便以後更換 BaseRemoteDataSource 的實現方式,且將一部分的資料處理邏輯放到了 BaseRepo ,有利於邏輯的複用
五、實踐操作(1)-請求天氣資料
上文講了一些基礎元件的邏輯實現以及對其的定位,此小節就以一個請求天氣資料的介面為例,來介紹如何具體實現一個網路請求的整體流程
首先是宣告介面
public interface ApiService { @Headers({HttpConfig.HTTP_REQUEST_TYPE_KEY + ":" + HttpConfig.HTTP_REQUEST_WEATHER}) @GET("onebox/weather/query") Observable<BaseResponseBody<Weather>> queryWeather(@Query("cityname") String cityName); }
增加的頭部資訊是為了標明該介面的請求型別,因為本文作為 demo 的幾個介面所用到的 baseUrl 以及 請求key 並不相同,因此通過宣告頭部來為介面動態指定請求引數,而這就需要用到 Retrofit 的攔截器了
public class FilterInterceptor implements Interceptor { @NonNull @Override public Response intercept(@NonNull Chain chain) throws IOException { Request originalRequest = chain.request(); HttpUrl.Builder httpBuilder = originalRequest.url().newBuilder(); Headers headers = originalRequest.headers(); if (headers != null && headers.size() > 0) { String requestType = headers.get(HttpConfig.HTTP_REQUEST_TYPE_KEY); if (!TextUtils.isEmpty(requestType)) { switch (requestType) { case HttpConfig.HTTP_REQUEST_WEATHER: { httpBuilder.addQueryParameter(HttpConfig.KEY, HttpConfig.KEY_WEATHER); break; } case HttpConfig.HTTP_REQUEST_QR_CODE: { httpBuilder.addQueryParameter(HttpConfig.KEY, HttpConfig.KEY_QR_CODE); break; } case HttpConfig.HTTP_REQUEST_NEWS: { httpBuilder.addQueryParameter(HttpConfig.KEY, HttpConfig.KEY_NEWS); break; } } } } Request.Builder requestBuilder = originalRequest.newBuilder() .removeHeader(HttpConfig.HTTP_REQUEST_TYPE_KEY) .url(httpBuilder.build()); return chain.proceed(requestBuilder.build()); } }
宣告 BaseRemoteDataSource 的實現類 WeatherDataSource
public class WeatherDataSource extends BaseRemoteDataSource implements IWeatherDataSource { public WeatherDataSource(BaseViewModel baseViewModel) { super(baseViewModel); } @Override public void queryWeather(String cityName, RequestCallback<Weather> responseCallback) { execute(getService(ApiService.class).queryWeather(cityName), responseCallback); } }
宣告 BaseRepo 的實現類 WeatherRepo
public class WeatherRepo extends BaseRepo<IWeatherDataSource> { public WeatherRepo(IWeatherDataSource remoteDataSource) { super(remoteDataSource); } public MutableLiveData<Weather> queryWeather(String cityName) { MutableLiveData<Weather> weatherMutableLiveData = new MutableLiveData<>(); remoteDataSource.queryWeather(cityName, new RequestCallback<Weather>() { @Override public void onSuccess(Weather weather) { weatherMutableLiveData.setValue(weather); } }); return weatherMutableLiveData; } }
還需要一個 WeatherViewModel , View 層 通過呼叫 queryWeather() 方法在請求成功時觸發 weatherLiveData 更新資料, View 層 已事先監聽 weatherLiveData ,並在資料更新時就可以立即收到最新資料
public class WeatherViewModel extends BaseViewModel { private MutableLiveData<Weather> weatherLiveData; private WeatherRepo weatherRepo; public WeatherViewModel() { weatherLiveData = new MutableLiveData<>(); weatherRepo = new WeatherRepo(new WeatherDataSource(this)); } public void queryWeather(String cityName) { weatherRepo.queryWeather(cityName).observe(lifecycleOwner, new Observer<Weather>() { @Override public void onChanged(@Nullable Weather weather) { weatherLiveData.setValue(weather); } }); } public MutableLiveData<Weather> getWeatherLiveData() { return weatherLiveData; } }
在 QueryWeatherActivity 中打印出介面的請求結果
public class QueryWeatherActivity extends BaseActivity { private static final String TAG = "QueryWeatherActivity"; private WeatherViewModel weatherViewModel; private EditText et_cityName; private TextView tv_weather; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_query_weather); et_cityName = findViewById(R.id.et_cityName); tv_weather = findViewById(R.id.tv_weather); } @Override protected ViewModel initViewModel() { weatherViewModel = LViewModelProviders.of(this, WeatherViewModel.class); weatherViewModel.getWeatherLiveData().observe(this, this::handlerWeather); return weatherViewModel; } private void handlerWeather(Weather weather) { StringBuilder result = new StringBuilder(); for (Weather.InnerWeather.NearestWeather nearestWeather : weather.getData().getWeather()) { result.append("\n\n").append(new Gson().toJson(nearestWeather)); } tv_weather.setText(result.toString()); } public void queryWeather(View view) { tv_weather.setText(null); weatherViewModel.queryWeather(et_cityName.getText().toString()); } }

也許有人會覺得為了請求一個介面需要建立三個實現類( WeatherDataSource、WeatherRepo、WeatherViewModel )以及一個介面( IQrCodeDataSource )有點繁瑣,但這是想要劃分職責並實現邏輯與UI相隔離的必然結果。 WeatherDataSource 用來實現介面的實際呼叫,只負責請求資料並傳遞請求結果。WeatherRepo 用來遮蔽 WeatherViewModel 對 WeatherDataSource 的感知,並承擔起一部分資料處理邏輯。WeatherViewModel 用於實現邏輯與 UI 的隔離,並保障資料不因為頁面重建而丟失。這樣,Activity 就可以儘量只承擔資料呈現的職責,而不必摻雜資料處理邏輯
六、實踐操作(2)-請求生成二維碼
此處再來看一個例子,用於生成指定內容的二維碼
public class QrCodeDataSource extends BaseRemoteDataSource implements IQrCodeDataSource { public QrCodeDataSource(BaseViewModel baseViewModel) { super(baseViewModel); } @Override public void createQrCode(String text, int width, RequestCallback<QrCode> callback) { execute(getService(ApiService.class, HttpConfig.BASE_URL_QR_CODE).createQrCode(text, width), callback); } }
此處介面請求回來的只是一段 base64 編碼的字串,而外部希望獲取到的自然是一個可以直接使用的 Bitmap ,因此可以在 Repo 中先對資料進行轉換後再傳遞到外部
public class QrCodeRepo extends BaseRepo<IQrCodeDataSource> { public QrCodeRepo(IQrCodeDataSource remoteDataSource) { super(remoteDataSource); } public MutableLiveData<QrCode> createQrCode(String text, int width) { MutableLiveData<QrCode> liveData = new MutableLiveData<>(); remoteDataSource.createQrCode(text, width, new RequestCallback<QrCode>() { @SuppressLint("CheckResult") @Override public void onSuccess(QrCode qrCode) { Observable.create(new ObservableOnSubscribe<Bitmap>() { @Override public void subscribe(@NonNull ObservableEmitter<Bitmap> emitter) throws Exception { Bitmap bitmap = base64ToBitmap(qrCode.getBase64_image()); emitter.onNext(bitmap); emitter.onComplete(); } }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<Bitmap>() { @Override public void accept(@NonNull Bitmap bitmap) throws Exception { qrCode.setBitmap(bitmap); liveData.setValue(qrCode); } }); } }); return liveData; } private static Bitmap base64ToBitmap(String base64String) { byte[] decode = Base64.decode(base64String, Base64.DEFAULT); return BitmapFactory.decodeByteArray(decode, 0, decode.length); } }
public class QrCodeViewModel extends BaseViewModel { private MutableLiveData<QrCode> qrCodeLiveData; private QrCodeRepo qrCodeRepo; public QrCodeViewModel() { qrCodeLiveData = new MutableLiveData<>(); qrCodeRepo = new QrCodeRepo(new QrCodeDataSource(this)); } public void createQrCode(String text, int width) { qrCodeRepo.createQrCode(text, width).observe(lifecycleOwner, new Observer<QrCode>() { @Override public void onChanged(@Nullable QrCode qrCode) { qrCodeLiveData.setValue(qrCode); } }); } public MutableLiveData<QrCode> getQrCodeLiveData() { return qrCodeLiveData; } }

七、實踐操作(3)-請求失敗示例
前言說了,本文封裝的網路框架當網路請求結果為非成功狀態時(網路請求失敗或者業務請求失敗),預設操作是用 Toast 提示失敗原因,也支援自定義實現失敗時的操作。此處就來看當請求失敗時如何進行處理
此處需要宣告兩個並不存在的介面
public interface ApiService { @GET("leavesC/test1") Observable<BaseResponseBody<String>> test1(); @GET("leavesC/test2") Observable<BaseResponseBody<String>> test2(); }
public class FailExampleDataSource extends BaseRemoteDataSource implements IFailExampleDataSource { public FailExampleDataSource(BaseViewModel baseViewModel) { super(baseViewModel); } @Override public void test1(RequestCallback<String> callback) { execute(getService(ApiService.class).test1(), callback); } @Override public void test2(RequestCallback<String> callback) { execute(getService(ApiService.class).test2(), callback); } }
public class FailExampleRepo extends BaseRepo<IFailExampleDataSource> { public FailExampleRepo(IFailExampleDataSource remoteDataSource) { super(remoteDataSource); } public MutableLiveData<String> test1() { MutableLiveData<String> newsPackMutableLiveData = new MutableLiveData<>(); remoteDataSource.test1(new RequestCallback<String>() { @Override public void onSuccess(String newsPack) { newsPackMutableLiveData.setValue(newsPack); } }); return newsPackMutableLiveData; } public void test2(RequestMultiplyCallback<String> callback) { remoteDataSource.test2(callback); } }
test1()方法用的是基礎類的預設失敗回撥,即直接 Toast 提示失敗資訊。而 test2() 方法則是自定義了請求失敗時的回撥操作
public class FailExampleViewModel extends BaseViewModel { private MutableLiveData<String> test1LiveData = new MutableLiveData<>(); private MutableLiveData<String> test2LiveData = new MutableLiveData<>(); private FailExampleRepo failExampleRepo = new FailExampleRepo(new FailExampleDataSource(this)); public void test1() { failExampleRepo.test1().observe(lifecycleOwner, new Observer<String>() { @Override public void onChanged(@Nullable String s) { test1LiveData.setValue(s); } }); } public void test2() { failExampleRepo.test2(new RequestMultiplyCallback<String>() { @Override public void onFail(BaseException e) { showToast("test2方法請求失敗:" + e.getMessage()); finish(); } @Override public void onSuccess(String s) { test2LiveData.setValue(s); } }); } }

八、結束語
這就是整個請求框架的大體架構了,也經過了實際專案的考驗了,目前執行良好,但裡面可能還會包含一些不合理的地方,歡迎大家指正反饋,如果覺得對你有所幫助,也歡迎 star
原始碼點選這裡檢視: ViewModel_Retrofit_RxJava
Apk 點選這裡下載: ViewModel_Retrofit_RxJava