一步步封裝實現自己的網路請求框架

来源:https://www.cnblogs.com/leavesC/archive/2019/02/13/10371873.html
-Advertisement-
Play Games

一、前言 現如今 Android 領域流行的網路請求框架基本都是用 Retrofit 加 RxJava 來搭配構建的,而以 ViewModel + LiveData + Retrofit + RxJava 來構建請求框架的例子要相對少得多。而本文就是以這四者作為基礎組件,介紹如何一步步封裝實現自己的 ...


一、前言

現如今 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

ViewModelLiveData 都是 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 層傳遞 ActionModel,在 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 通過監聽 BaseViewModelactionLiveData 的數據變化從而在網路請求開始載入時 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;
    }

}

還需要一個 WeatherViewModelView 層通過調用 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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • mysql壓測mysql自帶就有一個叫mysqlslap的壓力測試工具,通過模擬多個併發客戶端訪問MySQL來執行壓力測試,並且能很好的對比多個存儲引擎在相同環境下的併發壓力性能差別。通過mysqlslap –help可以獲得可用的選項,這裡列一些主要的參數,更詳細的說明參考官方手冊。如果是系統自帶... ...
  • 大家可能使用Navicat Premium時發現很方便,比如複製表或數據結構等,其實這種複製表數據或結構方法就是create table as 和create table like 這種方式實現細心的朋友會問,他們有啥區別呢?。。。廢話不多說,直入正題:比如這裡有張表數據t1: 註意上面有索引: C ...
  • 一、查看所有表的行數select a.name as '表名',b.rows as '表數據行數'from sysobjects a inner join sysindexes bon a.id = b.idwhere a.type = 'u'and b.indid in (0,1)--and a. ...
  • [20190212]刪除tab$記錄的恢復3.txt--//春節前幾天做了刪除tan$記錄的測試,鏈接:http://blog.itpub.net/267265/viewspace-2565245/=> [20190130]刪除tab$記錄的恢復.txthttp://blog.itpub.net/2 ...
  • [20190130]刪除tab$記錄的恢復2.txt--//前面鏈接寫好了腳本,開始測試刪除後的恢復.千萬不要在生產系統做這樣的測試!!--//參考鏈接:http://blog.itpub.net/267265/viewspace-2565245/=>[20190130]刪除tab$記錄的恢復.tx ...
  • [20190130]刪除tab$記錄的恢復.txt--//網上提到許多刪除tab$的案例,主要原因在於沒有從官方正規渠道下載oracle版本,還有一些來自工具裡面帶有一些腳本刪除tab$記錄.--//首先我並不知道許多人的恢復方法,僅僅簡單提到恢複數據字典,我想到既然是刪除,反向的操作就是恢復.也就 ...
  • 創建項目 使用Xamarin開發安卓項目,首先需要安裝VS2017以上版本。因為VS2017以上的版本,可以直接創建Xamarin項目。 另外用Xamarin開發安卓項目,還需要使用Intel的CPU,並且得是雙核以上的CPU,因為調試時,需要使用電腦的虛擬化,奔騰4之類的CPU是不支持虛擬化的。 ...
  • 一、Android 個人手機通訊錄開發 數據存儲:SQLite 資料庫 開發工具:Android Studio 二、Phone Module 簡介 1. 界面展示 2. 文件結構簡單分析 三、個人手機通訊錄代碼實現 1. 清單文件 (AndroidManifest.xml) 2. MainActiv ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...