RxJava 2: 用Retrofit2架构Android MVVM 生命周期
来源:互联网 发布:算法导论 电子书 编辑:程序博客网 时间:2024/05/05 13:53
原文: https://medium.com/@manuelvicnt/rxjava2-android-mvvm-lifecycle-app-structure-with-retrofit-2-cf903849f49e#.elz8jqnoi
一年多前,我写了一个帖子MVVM, RxJava and Retrofit。现在看, 这个帖子有点过时了。你会惊奇,一年之内你能学习多少东西。如果你回顾一下,你会对自己的代码感到尴尬。不仅是代码本身,还有你怎么到达那里的过程。对我来说,全部都好像是遗留代码。
根据新的情景和库, 我试着改进这个架构。让我们继续同一个例子(在这里获取更多信息)。这次,我将使用第一个稳定版本的Rxjava2和Retrofit。
在这篇文章中,我们将理解,在用Retrofit的MVVM架构的实际例子中, 如何使用Rxjava 2。我们也将讲到,利用网络请求响应到视图层的生命周期,怎么提高你应用的性能。
获取信息
应用结构
如果我们快速了解下不同层...
- Retrofit层: 实际上是发出网络请求
- APIService层:负责网络请求,包括解析响应,如果有必要处理它
- RequestManager层:准备将要发送的数据;链接不同的网络请求。
- ViewModel层: 处理视图层需要的逻辑
- View层:视图是哑的,只是处理用户输入
亲自动手
在这篇文章中,我将大量论述一个小项目,你将看见一切是怎么实现的
manuelvicnt/RxJava2-MVVM-Android-Structure
生命周期导致Views 和 ViewModels之间的问题?
在上个用Rxjava1的文章中,我们在ViewModels中有Subjects,响应信息到有Subscribers的Views中。当我说我在一年之中我学习了很多,你记得这部分?嗯,就是这个例子。
我们全都遇见过相同的问题:如果应用回到后台,我们不想取消网络请求,或者多次请求网络。
其中我们面对的一个问题是,Subscriber/Observer的onNext() 或者onComplete()方法被调用,但View不在屏幕中(注:即不在前台)。如果Subscriber试着回信息到视图(通过一个BusManager或者一个Callback),在那个方法中,我们试着更新任何UI的控件,那么我们的应用可能Crash。当持有信息时,Subject相当有帮助,直到View显示来获取。
如果你看一下新的代码仓库,Views 和 ViewModels之间的通信是通过一个接口(或者叫回调),我们叫它Contract。这给你提供了灵活性:在ViewModel的上面即插即用任何的View。
假设你有不同的Views,取决于你的设备是智能手机、平板电脑或者智能手表,所有这些都可能分享同一个ViewModel,但是反之不亦然(注:一个View不能有多个ViewModel)。
怎么解决生命周期的问题?
定义一个接口来每个时刻发生了什么
public interface Lifecycle { interface View { } interface ViewModel { void onViewResumed(); void onViewAttached(@NonNull Lifecycle.View viewCallback); void onViewDetached(); }}
这样,ViewModel清楚了生命周期,什么时候显示什么或者不显示的逻辑将移到ViewModel中(本应该这样的),所以当有信息的时候,它能有相应的响应和通知视图。
View和ViewModel之间的Contract
Contact定义了View需要从ViewModel获取了什么,反之亦然。通常,我们根据一个界面定义一个contract,尽管你也可以根据一个功能来定义。
在我们的例子中,我们有个Home界面,能够刷新User数据。我们定义我们的contract为:
public interface HomeContract { interface View extends Lifecycle.View { void showSuccessfulMessage(String message); } interface ViewModel extends Lifecycle.ViewModel { void getUserData(); }}
这个contract扩展了Lifecycle contract,所以ViewModel也将知道生命周期
Rxjava 2 响应流的类型
Rxjava 2中,引进了一些概念,重命名了另外一些。看下文档获取更多的信息
两者之间重要的不同是背压的处理。基本上,Flowable是能够处理背压的Observer,同样的关系连接了FlowableProcessor和Subject,Subscriber和Observer,等等。
记住,Completable、Single和Maybe不处理背压。
为了学习的目的,我们将Retrofit返回Observable对象。如果我们想处理背压呢?如果我们知道预期的结果,想通过指定想要获得的Stream来优化我们的代码呢?
使用Completable
让我们注册调用作为例子。因为RegistrationAPIService是处理这个信息的,我们不想返回Stream,因为在RequestManager层响应没有使用。我们仅仅关系这个调用是否成功。为此,我们返回Completable对象,忽略我们从Observable获取的元素。
public Completable register(RegistrationRequest request) { return registrationAPI.register(request) .doOnSubscribe(disposable -> isRequestingRegistration = true) .doOnTerminate(() -> isRequestingRegistration = false) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .onErrorResumeNext(this::handleRegistrationError) .doOnNext(registrationResponse -> processRegistrationResponse(request, registrationResponse)) .ignoreElements();}
使用Maybe
如果我们想把response返回到RequestManager层,但是因为这是个网络请求,而且我们知道我们将收到一个对象,我们可以使用Maybe(有可能,body是空的,所以当null对象时,我们使用Maybe来避免异常)
记住,用singleElement()操作子,而不是singleElement()操作子。如果你使用第二个,你获取不到什么,它将抛一个异常,因为它一直会尝试获取第一个元素,即使没有第一个元素。
public Maybe<LoginResponse> login(LoginRequest request) { return loginAPI.login(request.getNickname(), request.getPassword()) .doOnSubscribe(disposable -> isRequestingLogin = true) .doOnTerminate(() -> isRequestingLogin = false) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .onErrorResumeNext(this::handleLoginError) .doOnNext(this::processLoginResponse) .singleElement();}
使用Flowable
就像我们前面说的,Flowable和Observable有相同的行为,但是能够处理背压。为此,当Observable转为Flowable的时候,我们不得不指定我们想要的哪个策略。
有不同的策略:Buffer(缓冲所有onNext的值,直至下游消费它),DROP(放弃最近的onNext值如果下游不能赶上),ERROR(发出MissingBackpressureException,万一下游不能赶上)也是Observable相同的行为,LATEST(保留最新的onNext值,重写前面的值如果下游不能赶上)和MISSING(onNext事件没有任何缓冲和丢弃)。
在我们的Games例子中,我们使用BUFFER策略,因为我们不想失去任何game,万一下游不能赶上。这可能有点慢,但是所有的事件就在那里。
public Flowable<GamesResponse> getGames(GamesRequest request) { return gamesAPI.getGamesInformation(request.getNickname()) .doOnSubscribe(disposable -> isRequestingGames = true) .doOnTerminate(() -> isRequestingGames = false) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnError(this::handleAccountError) .toFlowable(BackpressureStrategy.BUFFER);}
使用Zip操作子同时进行不同的网络请求
如果你想同时不同的网络请求,仅当所有的网络请求都成功的时候,得到通知,这时,你应该使用Zip操作子。这非常强大!这是我喜欢的操作子之一。
#UserDataRequestManager.java
public Flowable<Object> getUserData() { return Flowable.zip( getAccount(), getGames(), this::processUserDataResult);}
private Flowable<AccountResponse> getAccount() { return accountAPIService.getAccount(createAccountRequest());}private Flowable<GamesResponse> getGames() { return gamesAPIService.getGames(createGamesRequest());}
连接不同的网络请求
我们看到怎么每个网络请求返回不同类型的Stream。让我们看看我们怎么连接它们。计划是,Registration请求,Login请求,然后是UserData。进行多合一。
UserData返回Flowable。然而,Login请求返回Maybe。我们必须匹配它们。
#AuthenticationRequestManager.java
private MaybeSource<Object> makeGetUserDataRequest(LoginResponse loginResponse) { return userDataRequestManager.getUserData().singleElement();}
如果响应是成功的,Login请求将获取UserData。我们预计getUserDataRequestMethod返回Maybe,我们可以用flatMap()操作子连接它们。
#AuthenticationRequestManager.java
public MaybeSource<Object> login() { return loginAPIService.login(createLoginRequest()) .flatMap(this::makeGetUserDataRequest);}
现在如果我们想调用Registration,然后是Login请求,我们仅仅在Completable完成后调动Login请求。我们用andThen()操作子来完成
#AuthenticationRequestManager.java
public MaybeSource<Object> register() { return registrationAPIService.register(createBodyForRegistration()) .andThen(makeLoginRequest());}
private MaybeSource<Object> makeLoginRequest() { return login();}
观察输入源
如果我们看看文档,我们能发现,Observers(为Observables)和Subscribers (为Flowables)怎么在它们的借口中暴露一个新的方法:onSubscribe()。
Observer以Disposable来订阅,Disposable处理或者取消这个订阅。Subscriber以Subscription来订阅,而且可以取消订阅,它能请求一些项(我们能在这里看见背压功能)。
很多时候,我们不想重写Observer或者Subscriber的onSubscribe方法(就像我们在Rxjava 1一样)。为此,我们仅仅可以用DisposableObserver或者DisposableSubscriber订阅Stream。
当你观察一个Stream,如果你想得到Subscription或者Disposable,你不得不用subscribeWith(),而不是subscribe()。
如果你不想取消订阅,你可以使用subscribe():
public void getUserData() { userDataRequestManager.getUserData() .subscribe(new HomeSubscriber());}
如果你想取消订阅或者dispose:
public void getUserData() { Disposable userDataSubscription = userDataRequestManager.getUserData() .subscribeWith(new HomeSubscriber()); userDataSubscription.dispose();}
后台处理和生命周期
当视图不在前台,为了避免通知,我们得持有这个信息直至视图变得可见(准备做应该做的事情),然后分派这个信息。在我们的用例中,当应用在后台或者视图不可见,我们仅仅想要一个网络请求而不是多个。
方案1:用生命周期Contract方法
用生命周期的方法,我们创建了一个抽象类来处理请求状态。我们保存状态和那里发生的最后一个错误。
我们也可以创建不同的Observers,取决于Stream的类型。比如,Login请求是由MaybeObserver处理。
protected class MaybeNetworkObserver<T> extends DisposableMaybeObserver<T> { @Override public void onSuccess(T value) { requestState = REQUEST_SUCCEEDED; } @Override public void onError(Throwable e) { lastError = e; requestState = REQUEST_FAILED; } @Override public void onComplete() { }}
我们可以看见,在这个情况中,onSuccess(T)是设置requestState为SUCCEEDED的方法,因为它是DisposableMaybeObserver(如果它是DisposableObserver,那么那个应该在onComplete方法)。当进行网络请求,这个Observer是在Login的ViewModel中使用。如果我们看下这个类,他的方法定义如下:
public class LoginViewModel extends NetworkViewModel implements LoginContract.ViewModel {
public void login() { authenticationRequestManager.login() .subscribe(new LoginObserver()); }}
private class LoginObserver extends MaybeNetworkObserver<Object> { @Override public void onSuccess(Object value) { onLoginCompleted(); } @Override public void onError(Throwable e) { onLoginError(e); } @Override public void onComplete() { }}
onLoginError() 和 onLoginCompleted() 是定义在这个类的内部,处理好的和坏的情况。正如你看见的,这个情形中,我们可以在authenticationRequestManager Maybe Stream调用subscribe(),因为我们不反订阅
当应用到后台时这么处理呢?我们用onViewResumed()方法:
@Overridepublic void onViewResumed() { @RequestState int requestState = getRequestState(); if (requestState == REQUEST_SUCCEEDED) { onLoginCompleted(); } else if (requestState == REQUEST_FAILED) { onLoginError(getLastError()); }}
当视图恢复了,我们的状态是REQUEST_SUCCEEDED,然后我们通知视图,如果失败,我们以错误通知。可能你注意到了,当响应来了,LoginObserver类里面的代码被调用,如果视图就在那里的话,我们可以通知视图?如果视图不在那里,我们需要判空来避免调用。看下面的代码:
private void onLoginCompleted() { if (viewCallback != null) { viewCallback.hideProgressDialog(); viewCallback.launchHomeActivity(); }}
方案2:用Processor(支持背压的Subject)
当我们在HomeActivity中下拉刷新时,HomeViewModel获取UserData。我们使用Processor,而不是使用标准的Subscriber。
这个解决方案为下拉刷新行为而设计的。我们一直想进行那个网络请求,假使你不想进行多个网络请求,然后得到最后一个响应,这个实现有一点点不一样。
这个例子中,我们使用AsyncProcessor,因为我们仅仅想要源发送的最后一个信息,这个信息还没有被消费,不是所有的元素。
所以,当我们下拉刷新,我们一直getUserData()网络请求。然而,当视图从ViewModel分离的时候,我们不想取消网络,而且当视图恢复的时候我们处理这个信息。
关键是AsyncProcessor,这个对象将订阅UserData Flowable,然后将持有这个信息到Subscriber请求它。
因为我们一直想进行这个网络请求,我们每次创建一个新的AsyncProcessor。然后,我们用这个对象订阅到AsyncProcessor,我们想获取响应,然后在本地字段中持有它(所以,当视图分离的时候我们能处理它)。最后,我们进行网络请求用AsyncProcessor作为AsyncProcessor。
# HomeViewModel.java
private AsyncProcessor<Object> userDataProcessor;private Disposable userDataDisposable;
public void getUserData() { userDataProcessor = AsyncProcessor.create(); userDataDisposable = userDataProcessor.subscribeWith(new HomeSubscriber()); userDataRequestManager.getUserData().subscribe(userDataProcessor);}
当视图分离的时候发生了什么?我们取消当前的Disposable。注意到,网络请求不是被取消了因为它使用AsyncProcessor订阅了。
@Overridepublic void onViewDetached() { this.viewCallback = null; if (isNetworkRequestMade()) { userDataDisposable.dispose(); }}
private boolean isNetworkRequestMade() { return userDataDisposable != null;}
当视图恢复的时候,我们检查是否我们是否已经进行了一个网络请求。如果如此,我们重新连接我们的Subscriber到AsyncProcessor。如果网络请求正在进行,当消息来的时候,我们能得到通知。如果它已经来了,我们马上得到通知。
@Overridepublic void onViewResumed() { if (isNetworkRequestMade()) { userDataProcessor.subscribe(new HomeSubscriber()); }}
这个解决方案的特点是,Subscriber的代码永远不会在后台执行。因为这个,我们不需要检查视图的为空性。viewCallback对象永远不会为空。
private class HomeSubscriber extends DisposableSubscriber<Object> { @Override public void onNext(Object o) { } @Override public void onError(Throwable t) { viewCallback.showSuccessfulMessage("Refreshed"); } @Override public void onComplete() { viewCallback.showSuccessfulMessage("Refreshed"); viewCallback.hideLoading(); }}
模拟Retrofit网络请求
如果你看看这个工程,我用一个客户端来模拟网络请求,添加延时,所以当应用回到后台时我们能测试它。
Retrofit Builder上用RxJava2CallAdapterFactory,在Retrofit上开启Rxjava 2特性。
public static Retrofit getAdapter() { OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new MockInterceptor()) .build(); return new Retrofit.Builder() .baseUrl("http://www.mock.com/") .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build();}
拦截器一直在两秒后返回一个成功的响应。这是可以改进的,检查哪个请求已经进行了,然后返回作为body的部分的正确JSON响应。
public class MockInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { addDelay(); return new Response.Builder() .code(200) .message("OK") .request(chain.request()) .protocol(Protocol.HTTP_1_0) .body(ResponseBody.create(MediaType.parse("application/json"), "{}")) .build(); } private void addDelay() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }}
其他的考量
当你看看代码仓库,一些部分的代码相当糟糕。你看见Singletons的使用了吗?(比如,UserDataRequestManager),这个太伤眼了但是我没有时间把它变得更好。
你可能想知道... 问题是什么?嗯,单元测试的singletons是最糟糕的事情,因为我们在单元测试中持有他们的状态。
这么修复它呢?依赖注射!或者,你可以手动的传递对象,这不是太理想,或者,你可以整合Dagger 2(比Dagger1好多了因为都是在编译阶段)。我尽量避免手动完成:你顶层架构类中(主要是在视图层中)有大量的方法,这个类创建和传递对象,这些对象只是在你低层次部分的架构中(**哎**)。想象一个Fragment, 创建了一个APIService,把它传递到所有的层级中!太糟糕了!
结论
当你迁移你代码到Rxjava2,确保你以自己的想要的方式使用Streams和Observers。
这是一个很好的总结: 怎么用MVVM构建你的应用和以高效的方式处理Views的生命周期
- RxJava 2: 用Retrofit2架构Android MVVM 生命周期
- android之dagger2+rxjava+retrofit2+mvp架构的结合
- 小白装逼:Android retrofit2+rxjava
- Retrofit2 + RxJava + Okhttp + RecyclerView用MVP架构展示数据
- Retrofit2.0+RxJava+Dragger2实现不一样的Android网络架构搭建
- Android 生命周期架构组件与 RxJava 完美协作
- Retrofit2+RxJava
- retrofit2+rxJava
- RXJava+retrofit2
- Android-->Rxjava与Retrofit2的结合实战
- Android RxJava+Retrofit2.0+MVP模式整合
- retrofit2 RxJava在android中的使用
- Android Retrofit2+OkHttp3+RxJava 三联合
- Android-网络框架04Retrofit2.0+RxJava
- Android Retrofit2.0+RxJava简单示例
- Android Okhttp+Retrofit2.0+RxJava简单解析
- Android RxJava+Retrofit2.0+MVP的小试牛刀
- Android Rxjava+Retrofit2 多图片+文字上传
- prim
- 前端书籍推荐
- (转)Android View 事件分发机制 源码解析 (上)
- Oracle OCI-22053:溢出错误 解决时的坑(去其他项目组帮忙时遇到的)
- 手把手教你玩转SOCKET模型:重叠I/O篇 -----很不错
- RxJava 2: 用Retrofit2架构Android MVVM 生命周期
- react组件API的7个方法
- Eclipse 中git 的使用
- HTML5滑动门动画(侧滑,上滑)
- Unity渲染优化中文翻译(一)
- MSP430F1612 常用术语
- RecycleView添加item点击效果
- script标签在html中的位置
- 启动hive报错:[ERROR] Terminal initialization failed; falling back to unsupported