RxJava 2: 用Retrofit2架构Android MVVM 生命周期

原文: 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。我们也将讲到,利用网络请求响应到视图层的生命周期,怎么提高你应用的性能

获取信息

RxJava 2: 用Retrofit2架构Android MVVM 生命周期_第1张图片

应用结构

如果我们快速了解下不同层...

  • 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();
    }
}
View将在自己的onResume()中,调用Lifecycle.ViewModel#onViewResumed();在onStart()中调用Lifecycle.ViewModel#onViewAttached(this);在onDestroy()中调用Lifecycle.ViewModel#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,等等。

记住,CompletableSingleMaybe不处理背压。

为了学习的目的,我们将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 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 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 getUserData() {

    return Flowable.zip(
                getAccount(),
                getGames(),
                this::processUserDataResult);
}
 
   
  
private Flowable getAccount() {

    return accountAPIService.getAccount(createAccountRequest());
}

private Flowable getGames() {

    return gamesAPIService.getGames(createGamesRequest());
}

连接不同的网络请求

我们看到怎么每个网络请求返回不同类型的Stream。让我们看看我们怎么连接它们。计划是,Registration请求,Login请求,然后是UserData。进行多合一。

UserData返回Flowable。然而,Login请求返回Maybe。我们必须匹配它们。

#AuthenticationRequestManager.java
private MaybeSource makeGetUserDataRequest(LoginResponse loginResponse) {

    return userDataRequestManager.getUserData().singleElement();
} 
 
   
  

如果响应是成功的,Login请求将获取UserData。我们预计getUserDataRequestMethod返回Maybe,我们可以用flatMap()操作子连接它们。

#AuthenticationRequestManager.java
public MaybeSource login() {

    return loginAPIService.login(createLoginRequest())
            .flatMap(this::makeGetUserDataRequest);
}
 
   
  

现在如果我们想调用Registration,然后是Login请求,我们仅仅在Completable完成后调动Login请求。我们用andThen()操作子来完成

#AuthenticationRequestManager.java
public MaybeSource register() {

    return registrationAPIService.register(createBodyForRegistration())
            .andThen(makeLoginRequest());
}
 
   
  
private MaybeSource 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 extends DisposableMaybeObserver {

    @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 {

    @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()方法:

@Override
public 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 userDataProcessor;
private Disposable userDataDisposable;
 
   
  
public void getUserData() {

    userDataProcessor = AsyncProcessor.create();
    userDataDisposable = userDataProcessor.subscribeWith(new HomeSubscriber());

        userDataRequestManager.getUserData().subscribe(userDataProcessor);
}

当视图分离的时候发生了什么?我们取消当前的Disposable。注意到,网络请求不是被取消了因为它使用AsyncProcessor订阅了。

@Override
public void onViewDetached() {

    this.viewCallback = null;

    if (isNetworkRequestMade()) {
        userDataDisposable.dispose();
    }
}
private boolean isNetworkRequestMade() {
    
    return userDataDisposable != null;
}

当视图恢复的时候,我们检查是否我们是否已经进行了一个网络请求。如果如此,我们重新连接我们的Subscriber到AsyncProcessor。如果网络请求正在进行,当消息来的时候,我们能得到通知。如果它已经来了,我们马上得到通知。

@Override
public void onViewResumed() {

    if (isNetworkRequestMade()) {
        
        userDataProcessor.subscribe(new HomeSubscriber());
    }
}

这个解决方案的特点是,Subscriber的代码永远不会在后台执行。因为这个,我们不需要检查视图的为空性。viewCallback对象永远不会为空。

private class HomeSubscriber extends DisposableSubscriber {

    @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的生命周期


你可能感兴趣的:(Android)