RxJava 系列四: doOnNext 的运用

1,880 阅读6分钟

doOnNext 的运用

本文概述

  • 本文以具体案例(需要频繁在异步线程与主线程之间切换),引出了doOnNext;记录了编写思路、编写细节、完整代码等;向读者展示了,doOnNext的独特魅力;

业务需求

  • 需要频繁的(主/异步)切换线程

  • 示意图:

    图片.png

应用场景:

  • 例如银行(会频繁切换APP)

工程结构:

图片.png

整体思路:

 *  Retrofit + RxJava
 *  需求:
 *  1.请求服务器注册操作
 *  2.注册完成之后,更新注册UI
 *  3.马上去登录服务器操作
 *  4.登录完成之后,更新登录的UI

具体结构:

 * wy.RxJava配合Retrofit。
 * RxJava + Retrofit (请求网络OkHttp  ---- Retorfit  --- Observable)
 *
 * 1.OkHttp 请求网络 (Retorfit)
 * 2.Retorfit 返回一个结果 (Retorfit--- Observable
 * 3.最终的结果 是RxJava中的 被观察者 上游 Observable
 * 4.一行代码写完需求流程: 从上往下
 *    1.请求服务器,执行注册操作(耗时)切换异步线程
 *    2.更新注册后的所有 注册相关UI - main  切换主线程
 *    3.请求服务器,执行登录操作(耗时)切换异步线程
 *    4.更新登录后的所有 登录相关UI - main  切换主线程
 *
 * 5.看RxJava另外一种的执行流程
 *   初始点 开始点 订阅
 *   1.onSubscribe
 *   2.registerAction(new RegisterRequest())
 *   3..doOnNext 更新注册后的 所有UI
 *   4.flatMap执行登录的耗时操作
 *   5.订阅的观察者 下游 onNext 方法,更新所有登录后的UI
 *   6.progressDialog.dismiss()

环境准备:

  • 封装操作:为调用处上面分配异步线程,为调用处下面分配主线程

     //DownLoadActivity.rxud
     public final static <UD> ObservableTransformer<UD, UD> rxud() {
         return new ObservableTransformer<UD, UD>() {
             @Override
             public ObservableSource<UD> apply(Observable<UD> upstream) {
                 return  upstream.subscribeOn(Schedulers.io())     // 给上面代码分配异步线程
                     .observeOn(AndroidSchedulers.mainThread()) // 给下面代码分配主线程;
                     .map(new Function<UD, UD>() {
                         @Override
                         public UD apply(UD ud) throws Exception {
                             Log.d(TAG, "apply: 我监听到你了,居然再执行");
                             return ud;
                         }
                     });
                 // .....        ;
             }
         };
     }
    
  • 封装操作:封装了Retrofit(返回一个Retrofit 实例)

     package com.xiangxue.rxjavademo.doOnNext.retrofit_okhttp_rxjava.retrofit_okhttp;
     ​
     import java.util.concurrent.TimeUnit;
     ​
     import butterknife.BuildConfig;
     import okhttp3.OkHttpClient;
     import okhttp3.logging.HttpLoggingInterceptor;
     import retrofit2.Retrofit;
     import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
     import retrofit2.converter.gson.GsonConverterFactory;
     ​
     public class MyRetrofit {
     ​
         // 把Retrofit给Build出来  Retrofit给创建出来
         public static Retrofit createRetrofit() {
     ​
             OkHttpClient.Builder builder = new OkHttpClient.Builder();
     ​
             builder.readTimeout(10, TimeUnit.SECONDS);
             builder.connectTimeout(9, TimeUnit.SECONDS);
     ​
             if (BuildConfig.DEBUG) {
                 HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
                 interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
                 builder.addInterceptor(interceptor);
             }
     ​
             return new Retrofit.Builder().baseUrl("http://xxxxxxx")
                     .client(builder.build())
                     .addConverterFactory(GsonConverterFactory.create())
                     .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                     .build();
         }
     ​
     }
    

业务代码:分开写

 // TODO 方式一 分开写
 @SuppressLint("CheckResult")
 public void request(View view) {
     // 1.请求服务器注册操作
     // 2.注册完成之后,更新注册UI
     MyRetrofit.createRetrofit().create(IReqeustNetwork.class)
         //注册操作:异步操作,耗时
         .registerAction(new RegisterRequest())
         //为上面分配异步线程,为下面分配主线程
         .compose(DownloadActivity.rxud())
         //RxJava 处理终点信息
         .subscribe(new Consumer<RegisterResponse>() {
             @Override
             public void accept(RegisterResponse registerResponse) throws Exception {
                 // 更新注册相关的所有UI
                 // .....
             }
         });
 ​
 ​
     // 3.马上去登录服务器操作
     // 4.登录完成之后,更新登录的UI
     MyRetrofit.createRetrofit().create(IReqeustNetwork.class)
         //登录操作:异步操作,耗时
         .loginAction(new LoginReqeust())
         //为上面分配异步线程,为下面分配主线程
         .compose(DownloadActivity.rxud())
         //RxJava 处理终点信息
         .subscribe(new Consumer<LoginResponse>() {
             @Override
             public void accept(LoginResponse loginResponse) throws Exception {
                 // 更新登录相关的所有UI
                 // .....
             }
         });
 }

业务代码:将整体业务逻辑合并

  • 思路:

     /**
      * 一行代码 实现需求
      * 需求:
      *   还有弹出加载
      *  * 1.请求服务器注册操作
      *  * 2.注册完成之后,更新注册UI
      *  * 3.马上去登录服务器操作
      *  * 4.登录完成之后,更新登录的UI
      */
    
  • 完整代码:

     private ProgressDialog progressDialog;
     Disposable disposable;
     ​
     public void request2(View view) {
         MyRetrofit.createRetrofit().create(IReqeustNetwork.class)
             // todo 1.请求服务器注册操作(应当在异步线程)   
             .registerAction(new RegisterRequest())
             //给上面分配异步线程
             .subscribeOn(Schedulers.io())
             // 给下面分配主线程
             .observeOn(AndroidSchedulers.mainThread())
             //编写细节一:这里是不能写.subScribe(new Comsumer)
             .doOnNext(new Consumer<RegisterResponse>() { // todo 3
                 @Override
                 public void accept(RegisterResponse registerResponse) throws Exception {
                     // todo 2.注册完成之后,更新注册UI
                 }
             })
             // todo 3.马上去登录服务器操作(切换到异步线程)
                 // 给下面分配了异步线程
             .observeOn(Schedulers.io())
             //使用flatMap解决嵌套导致代码结构不美观
                 //编写细节二:flatMap的第二个参数应该写什么?
             .flatMap(new Function<RegisterResponse,                                                   ObservableSource<LoginResponse>>() { 
                
                  @Override
                      public ObservableSource<LoginResponse> apply(RegisterResponse                      registerResponse) throws Exception {
                          Observable<LoginResponse> loginResponseObservable = MyRetrofit.createRetrofit().create(IReqeustNetwork.class)
                                 .loginAction(new LoginReqeust());
                          //返回的是登录响应的结果
                         return loginResponseObservable;
                     }
                 })
                  // 给下面 执行主线程:因为后面第四步的时候要更新UI
                 .observeOn(AndroidSchedulers.mainThread())
             
                 .subscribe(new Observer<LoginResponse>() {
                     // 一定是主线程,为什么,因为 subscribe 马上调用onSubscribe
                     @Override
                     public void onSubscribe(Disposable d) {
                         // TODO 1
                         progressDialog = new ProgressDialog(RequestActivity.this);
                         progressDialog.show();
                         // UI 操作
                         disposable = d;
                     }
     ​
                     @Override
                     public void onNext(LoginResponse loginResponse) { // todo 5
                         // TODO 4.登录完成之后,更新登录的UI
                     }
     ​
                     @Override
                     public void onError(Throwable e) {
     ​
                     }
     ​
                     // todo 6
                     @Override
                     public void onComplete() {
                         // 杀青了
                         if (progressDialog != null) {
                             progressDialog.dismiss();
                         }
                     }
                 });
     ​
     }
    

编写细节:

  • 编写细节一:在第一次切换到main之后是不能写subScribe的,要写doOnNext

    • 写subScribe:返回Disposable(此时流程会终止掉了)

    图片.png

    • 写doOnNext:返回Observable

      • 此时流程不会终止,整体业务上是不断添加卡片进行事件的拦截(进行操作)而不是将流程给断掉了

    图片.png

  • 编写细节二:flatMap的第二个参数应该写什么?

    • flatMap的第一个参数:此卡片接收的事件类型

      • 注册响应
    • flatMap的第一个参数:此卡片向后分发的事件类型

      • 登录响应
  • 编写细节三:线程的切换

    • 给上面分配异步线程

       .subscribeOn(Schedulers.io())
      
    • 给下面分配主线程

       .observeOn(AndroidSchedulers.mainThread())
      
    • 给下面分配异步线程

       .observeOn(Schedulers.io())
      
  • 实际的业务走向流程

    • 第一步:弹出加载框

       progressDialog = new ProgressDialog(RequestActivity.this);
       progressDialog.show();
      
    • 第二步:请求服务器进行注册操作

       .registerAction(new RegisterRequest())
      
    • 第三步:更新注册后的UI

       .doOnNext(new Consumer<RegisterResponse>() { // todo 3
           @Override
           public void accept(RegisterResponse registerResponse) throws Exception {
               // todo 2.注册完成之后,更新注册UI
           }
       })
      
    • 第四步:请求服务器进行登录操作

       .flatMap(new Function<RegisterResponse, ObservableSource<LoginResponse>>() { 
           @Override
           public ObservableSource<LoginResponse> apply(RegisterResponse registerResponse) throws Exception {
               Observable<LoginResponse> loginResponseObservable = MyRetrofit.createRetrofit().create(IReqeustNetwork.class)
                   .loginAction(new LoginReqeust());
               return loginResponseObservable;
           }
       })
      
    • 第五步:更新登录UI

       @Override
       public void onNext(LoginResponse loginResponse) { // todo 5
           // TODO 4.登录完成之后,更新登录的UI
       }
      
    • 第六步:整个链条结束

       // todo 6
       @Override
       public void onComplete() {
           // 杀青了
           if (progressDialog != null) {
               progressDialog.dismiss();
           }
       }
      
  • 细节四:RxJava起点到终点是单向的,从源码中来的

  • 细节五:Disposable内存泄漏问题

    • 基础:Rx 在每次事件分发前内部会判断Disposable 是否被中断,没有被中断才分发,中断了就不会分发;
    • 具体场景:在Activity最网络请求,但网卡,但是这个时候用户杀掉了Activity;此时,在事件分发的时候就需要去检测中断标记,此时终止事件流动(避免了内存泄漏)
     //声明 Disposable
     Disposable disposable;
     ​
     //在订阅处给 Disposable赋值
     .subscribe(new Observer<LoginResponse>() {
     ​
         // 一定是主线程,为什么,因为 subscribe 马上调用onSubscribe
         @Override
         public void onSubscribe(Disposable d) {
             // TODO 第一步:订阅后弹出加载框
             progressDialog = new ProgressDialog(RequestActivity.this);
             progressDialog.show();
     ​
             // UI 操作
     ​
             disposable = d;
         }
     ​
         //在Activity回收的时候,就要把这个 Disposable回收掉,不然有可能出现内存泄漏
         @Override
         protected void onDestroy() {
             super.onDestroy();
             // 必须这样写,最起码的标准
             if (disposable != null)
                 if (!disposable.isDisposed())
                     disposable.dispose();
         }
    
  • 细节六:onSubscribe一定执行在主线程

    • 因为请求函数是由UI控件触发---》request是主线程

    • 订阅(subscribe)也是主线程,一订阅后会立马触发onSubscribe

      • 因此,onSubscribe也是主线程

      • 这个里面可以写UI操作