RxJava 用起来很爽,特别是和 retrofit 一起用了请求网络数据。对于大部分初学者呢,都会出现这样的用法:
service = GithubService.createGithubService(githubToken); view.setOnClickListener( v -> service.user(name) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(System.out::println));
当点击一个按钮的时候,去请求服务器数据然后使用返回的结果刷新 UI。(比如当前显示用户信息的界面上有个刷新按钮,点击一下就去请求数据并刷新界面)。
笔者就曾经写过这样的代码。但是经过简单的测试就发现这是有问题的!
1. 由于网络请求是在后台线程发生的,并且需要时间,如果网络请求没有完成,用户点击了返回按键退出了当前界面则会引起 Activity 泄露
2. 每次点击刷新按钮都会触发一个新的网络请求,同时网络请求返回的顺序是不确定的,可能导致收到的数据问题
3. 如果把收到的数据保存到一个集合中,多次点击刷新按钮会导致同样的结果出现在数据集合中
比如输入一个用户名并点击刷新按钮查看其详细资料,在结果没有返回的时候,再次输入一个新的用户名并点击刷新按钮,则有可能第二次请求先返回,然后第一个请求结果才返回,这样用户最终看到的是第一个用户名对应的详细信息而不是第二个用户的。
其中第一个问题比较好解决,使用一个 CompositeSubscription ,把每个 Subscription 都添加到这里,在 onDestroy 里面取消注册即可(subscriptions.unsubscribe())。
subscriptions = new CompositeSubscription(); service = GithubService.createGithubService(githubToken); view.setOnClickListener( v -> { Subscriptionsub = service.user(name) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(System.out::println); subscriptions.add(sub); });
但是另外两个问题呢?
这里就要使用 Subject 来转发 View 的onClick 事件了。例如下面使用 PublishSubject:
subject = PublishSubject.create(); subscriptions = new CompositeSubscription(); service = GithubService.createGithubService(githubToken); view.setOnClickListener(v-> subject.onNext(v)); Subscriptionsub = subject.flatMap(v-> service.user(name)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(System.out::println); subscriptions.add(sub);
这样可以避免每次点击刷新按钮都创建一个新的 Subscription。而第三种情况,可以使用 switchMap 来解决
subject = PublishSubject.create(); subscriptions = new CompositeSubscription(); service = GithubService.createGithubService(githubToken); view.setOnClickListener(v-> subject.onNext(v)); Subscriptionsub = subject.switchMap(v-> service.user(name)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(System.out::println); subscriptions.add(sub);
另外为了避免用户快速的点击同一个按钮,则可以使用 throttleFirst 来过滤掉后面一段时间内的点击事件。
同时如果使用了 RxBinding 和 RxLifecycle 则代码会更加简洁清晰。
RxView.clicks(view) .subscribeOn(AndroidSchedulers.mainThread()) .doOnNext(aVoid1 -> _adapter.clear()) .throttleFirst(300, TimeUnit.MILLISECONDS) .observeOn(Schedulers.io()) .switchMap(aVoid -> _githubService.contributors(_username.getText().toString(), _repo.getText().toString())) .compose(bindUntilEvent(FragmentEvent.DESTROY_VIEW)) .observeOn(AndroidSchedulers.mainThread()) .subscribe(contributors -> { for (Contributor c : contributors) { _adapter.add(format("%s has made %d contributions to %s", c.login, c.contributions, _repo.getText().toString())); } });
上面的代码根据 https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/app/src/main/java/com/morihacky/android/rxjava/fragments/RetrofitFragment.java 中的代码修改而来,可以把上面的代码插入到 onCreateView 函数的 return 语句之前。同时取消掉 onListContributorsClicked 函数。
同时 RxJava-Android-Samples 这个项目有 3432 人加星 有 639 人 fork。 说明还是有不少人关注的,但是里面的示例用法还是有不少问题的。所以大家在参考示例项目学习的时候,一定要学会思考,不能直接复制粘贴代码就拿来用了,RxJava-Android-Samples 项目只是告诉 Rxjava 初学者 RxJava 可以这么用,那是具体用的对不对就不一定了!