注册

什么是响应式编程:以RxJava为例

RxJava思想




  • 文章概述:



    • 本文围绕Rx编程思想(响应式编程)进行深入细致探讨;以获取服务器图片为例,通过传统方式与Rx方式对比进一步体现Rx 编程方式的魅力;借助卡片式编程思想,对Rx编程方式进行第一次优化;借助 Java泛型对Rx编程进一步优化;



Rx编程出现背景:改变思维来提升效率




  • 通过事件流动,推进业务执行




    • 从起点到终点,逻辑严密



      • 下一层依赖上一层:体现在函数参数



    • 链式调用只是里面的一环




    • 样例:每一层逻辑上关联



      • 起点(分发事件:点击登录)----------登录API-------请求服务器--------获取响应码----------> 终点(更新UI登录成功 消费事件)





RxJava 配合 Retrofit




  • 业务逻辑:



    • Retrofit通过OKHHTTP请求服务器拿到响应码,交给RxJava由RxJava处理数据



  • 防抖:



    • 一秒钟点击了20次,只响应一次



  • 网络嵌套:拿到主数据再拿到item数据




  • doNext运用:异步与主线之间频繁切换



    • 异步线程A拿到数据,切换至UI线程更新,再次切换到异步线程B,再拿到UI线程



对比说明Rx 编程优势:统一业务代码逻辑



  • 主要内容:以获取服务器图片为例,通过传统方式与Rx方式对比进一步体现Rx 编程方式的魅力;

传统模式获取图片




  • 实现效果:


    image-20220621192036883




  • 传统编写思路:




    • 弹出加载框




    • 开启异步线程:此时有多种途径



      • 封装方法....
      • 全部写在一起
      • new Thread
      • 使用 线程池



    • 将从服务器获取的图片转成Bitmap




    • 从异步线程切换至UI线程更新UI




    • 代码实现:


       public void downloadImageAction(View view) {
           progressDialog = new ProgressDialog(this);
           progressDialog.setTitle("下载图片中...");
           progressDialog.show();
       
           //       异步线程处理耗时任务
           new Thread(new Runnable() {
               @Override
               public void run() {
                   try {
                       URL url = new URL(PATH);
                       HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                       httpURLConnection.setConnectTimeout(5000);
                       int responseCode = httpURLConnection.getResponseCode(); // 才开始 request
                       if (responseCode == HttpURLConnection.HTTP_OK) {
                           InputStream inputStream = httpURLConnection.getInputStream();
                           //                       图片丢给bitmap
                           Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                           //                       使用Handler 进行切换
                           Message message = handler.obtainMessage();
                           message.obj = bitmap;
                           handler.sendMessage(message);
                      }
                  } catch (Exception e) {
                       e.printStackTrace();
                  }
              }
          }).start();
       }
       
       //   使用Handler处理问题
       private final Handler handler = new Handler(new Handler.Callback() {
       
           @Override
           public boolean handleMessage(@NonNull Message msg) {
               Bitmap bitmap = (Bitmap) msg.obj;
               image.setImageBitmap(bitmap);
       
               if (progressDialog != null) progressDialog.dismiss();
               return false;
          }
       });








  • 传统方式弊端:




    • 在具体实现(切换线程)时,因为思维不统一,导致实现方式不同






RxJava思路:采用观察者设计模式,实现响应式(Rx)编程




  • 以事件流动推进业务执行




  • 角色:




    • 起点:被观察者(为其分配异步线程--->请求服务器)


       // 起点
       Observable.just(PATH)  // 内部会分发 PATH Stirng // TODO 第二步



    • 终点:观察者(为其分配UI线程--->更新UI)


       //终点
       .subscribe(
           new Observer<Bitmap>() {
               //订阅
               @Override
               public void onSubscribe(Disposable d) {
                 
              }
               //拿到事件:因为上一层是一个String类型的Path事件
               @Override
               public void onNext(@NonNull Bitmap bitmap) {
                   image.setImageBitmap(bitmap);
              }
       
               // 错误事件
               @Override
               public void onError(Throwable e) {
       
              }
       
               // 完成事件
               @Override
               public void onComplete() {
               
              }
          });





  • 编写思路:框架在实际使用中是U型逻辑(终点--->起点--->终点--->……)




    • 第一步:处理终点中拿到事件后的业务逻辑


       //拿到事件:因为上一层是一个String类型的Path事件
       @Override
       public void onNext(@NonNull String s) {
           image.setImageBitmap(bitmap);
       }



      • 细节:onNext的参数问题




        • Rx 整体是以事件流动推进业务逻辑,如果上一层是String类型的事件(Path)那么它的下一层应该也是String类型的事件(参数为String类型)




        • 但Rx 中根据业务进行事件的拦截



          • A层(String事件),B层(Bitmap事件),逻辑为A层--->B层
          • 那么就需要在A层到B层之间添加一个拦截器,进行事件转换







    • 第二步:在起点与终点之间添加拦截器




      • 为什么要添加拦截器:业务需求是拿到一个Bitmap而起点提供的是String类型的事件




      • 拦截器为map(K,V):K为上层事件,V为下层事件


         //上层事件为String类型,由系统自动推断;但此时拦截器并不知道下一层是什么事件,因此为Object
         .map(new Function<String, Object>() {
         })






      • 终点要求Bitmap事件


         //根据业务将map 中的value改为 Bitmap类型
         .map(new Function<String, Object>() {
         })



        • 终点完成事件(onNext报错,联动变化):注意由Rx思想决定,那么终点处的完成事件参数因为Bitmap


           //由Rx思想决定,那么终点处的完成事件参数因为Bitmap
           @Override
           public void onNext(@NonNull Bitmap bitmap) {
           image.setImageBitmap(bitmap);
           }



        • 整体事件流向:


          image-20220622223332983








    • 第三步:在拦截器内添加网络请求


       @Override
       public Bitmap apply(@NonNull String s) throws Exception {
       
           //处理网络请求:将String类型的Path事件处理为Bitmap实例
           URL url = new URL(PATH);
           HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
           int responseCode = httpURLConnection.getResponseCode();
           if(responseCode == httpURLConnection.HTTP_OK){
               InputStream inputStream = httpURLConnection.getInputStream();
               Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
               return bitmap;
          }
           return null;
       }


      • 此时不要使用Handler,因为拦截器已经将String类型事件转为Bitmap类型了,将Bitmap流向终点进行显示



    • 第四步:分配线程




      • 起点到此时拦截器结束,应当分配异步线程(因为需要请求服务器)


         //给上边代码分配异步线程,用于请求服务器
         .subscribeOn(Schedulers.io())



      • 拦截器结束位置到终点处,应当分配UI主线程(因为需要更新UI)


         //给下边的代码分配主线程,用于更新UI
         .observeOn(AndroidSchedulers.mainThread())



        • 分配的主线程跟下面这个是一样的


           // Thread.currentThread().getName(); == Android的主线程,这个跟RxJava切的android主线程是一样的







    • 到此基础功能已经实现,为了使得用户友好,需要添加下列步骤






Rx 代码优化(一):卡片式编程




  • 什么是卡片式编程:



    • 因为Rx 响应式编程是依靠事件流动推进业务执行,那么我们可以在起点与终点之间添加卡片(拦截器)实现具体的业务功能



  • 代码扩展:点击按钮后立即加载对话框,拿到图片并更新,随后关闭对话框




    • 整体流程:




      • 预处理:点击按钮后,立即加载对话框,开始准备事件分发


         //在终点订阅开始处加载对话框(预处理操作)
         // 订阅开始:一订阅就要显示对话框
         @Override
         public void onSubscribe(Disposable d) {
             //                               第一步:事件分发前预准备
             progressDialog = new ProgressDialog(Test.this);
             progressDialog.setTitle("开始下载");
             progressDialog.show();
         }



      • 第一步:回到起点,开始分发事件


         Observable.just(PATH)



      • 第二步:拦截器工作将String事件转为Bitmap事件(附带网络请求,从服务器拿到数据)




      • 第三步:抵达终点拿到事件处,更新UI


         //拿到事件:因为上一层是一个String类型的Path事件
         @Override
         public void onNext(@NonNull Bitmap bitmap) {
             image.setImageBitmap(bitmap);
         }






      • 第四步:抵达终点完成事件完成处,此时事件整体结束(Rx 编程结束尾巴)


         // 完成事件
         @Override
         public void onComplete() {
             //如果不为空那么就隐藏起来
             if (progressDialog != null)
             progressDialog.dismiss();
         }










  • 这种编程方式成为卡片式编程




    • 好处:后期若需要添加功能,仅需在起点与重点之间添加对应的拦截器,在其中进行处理即可




    • 图片示例:一开始的



      • 事件流动顺序

      image-20220622223332983




      • 运行结果:


        image-20220622231216269









    • 图片示例:此时需要添加个需求,将下载下来的图片添加水印后再展示




      • 事件流动顺序


        image-20220622225848759




      • 添加代码:图片上绘制文字 加水印


         // 图片上绘制文字 加水印
         private final Bitmap drawTextToBitmap(Bitmap bitmap, String text, Paint paint, int paddingLeft, int paddingTop) {
             Bitmap.Config bitmapConfig = bitmap.getConfig();
         
             paint.setDither(true); // 获取跟清晰的图像采样
             paint.setFilterBitmap(true);// 过滤一些
             if (bitmapConfig == null) {
                 bitmapConfig = Bitmap.Config.ARGB_8888;
            }
             bitmap = bitmap.copy(bitmapConfig, true);
             Canvas canvas = new Canvas(bitmap);
         
             canvas.drawText(text, paddingLeft, paddingTop, paint);
             return bitmap;
         }



      • 添加代码:在前面一个拦截器后添加


         .map(new Function<Bitmap, Bitmap>() {
             @Override
             public Bitmap apply(@NonNull Bitmap bitmap) throws Exception {
                 //开始添加水印
                 Paint paint = new Paint();
                 paint.setTextSize(88);
                 paint.setColor(Color.GREEN);
                 return drawTextToBitmap(bitmap,"水印",paint,88,88);
            }
         })



      • 运行结果:从服务器获取图片并添加水印


        image-20220622231334525






    • 还可以添加:及时记录日志等功能






Rx 代码优化(二):封装代码部分功能提升程序结构



  • 封装线程分配

 //为上游(起点到拦截器结束)分配异步线程,为下游(拦截器结束位置到终点结束)分配android主线程
 private final static <UD> ObservableTransformer<UD,UD> opMixed(){
     return new ObservableTransformer<UD, UD>() {
         @NonNull
         @Override
         //分配线程
         public ObservableSource<UD> apply(@NonNull Observable<UD> upstream) {
             return upstream.subscribeOn(Schedulers.io()).
             observeOn(AndroidSchedulers.mainThread())
             //继续链式调用
            .map(new Function<UD, UD>() {
                 @Override
                 public UD apply(@NonNull UD ud) throws Exception {
                     Log.d(TAG,"日志记录")
                     return ud;
                }
            })
 
             //还可以加卡片(拦截器)
            ;
 
        }
 
    };
 }


  • 仅需在终点前调用封装好的库就行了

 ……
 //是需要在终点前调用封装好的东西就行了
 .compose(opMixed())
     //终点
    .subscribe(

Rx 编程完整代码:


 package com.xiangxue.rxjavademo.downloadimg;
 
 import android.app.ProgressDialog;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.util.Log;
 import android.view.View;
 import android.widget.ImageView;
 
 import androidx.annotation.NonNull;
 import androidx.appcompat.app.AppCompatActivity;
 
 import com.xiangxue.rxjavademo.R;
 
 import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
 
 import io.reactivex.Observable;
 import io.reactivex.ObservableSource;
 import io.reactivex.ObservableTransformer;
 import io.reactivex.Observer;
 import io.reactivex.android.schedulers.AndroidSchedulers;
 import io.reactivex.disposables.Disposable;
 import io.reactivex.functions.Function;
 import io.reactivex.schedulers.Schedulers;
 
 public class Test extends AppCompatActivity {
 
     // 网络图片的链接地址,String类型的Path事件
     private final static String PATH = "http://pic1.win4000.com/wallpaper/c/53cdd1f7c1f21.jpg";
 
     // 弹出加载框
     private ProgressDialog progressDialog;
 
     // ImageView控件,用来显示结果图像
     private ImageView image;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_download);
         image = findViewById(R.id.image);
 
         // Thread.currentThread().getName(); == Android的主线程,这个跟RxJava切的android主线程是一样的
    }
 
     // 通过订阅将 起点 和 终点 关联起来
     public void rxJavaDownloadImageAction(View view) {
 
         // 起点
         Observable.just(PATH)  // 内部会分发 PATH Stirng // TODO 第二步
         //流程中的卡片
        .map(new Function<String, Bitmap>() {
             @Override
             public Bitmap apply(@NonNull String s) throws Exception {
 
                 //处理网络请求:将String类型的Path事件处理为Bitmap实例
                 URL url = new URL(PATH);
                 HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                 int responseCode = httpURLConnection.getResponseCode();
                 if(responseCode == httpURLConnection.HTTP_OK){
                     InputStream inputStream = httpURLConnection.getInputStream();
                     Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                     return bitmap;
                }
                 return null;
            }
        })
         //给上边代码分配异步线程,用于请求服务器
        .subscribeOn(Schedulers.io())
         //给下边的代码分配主线程,用于更新UI
        .observeOn(AndroidSchedulers.mainThread())
 
         //终点
        .subscribe(
             new Observer<Bitmap>() {
 
                 // 订阅开始:一订阅就要显示对话框
                 @Override
                 public void onSubscribe(Disposable d) {
                     //                               第一步:事件分发前预准备
                     progressDialog = new ProgressDialog(Test.this);
                     progressDialog.setTitle("开始下载");
                     progressDialog.show();
                }
                 //拿到事件:因为上一层是一个String类型的Path事件
                 @Override
                 public void onNext(@NonNull Bitmap bitmap) {
                     image.setImageBitmap(bitmap);
                }
 
                 // 错误事件
                 @Override
                 public void onError(Throwable e) {
 
                }
 
                 // 完成事件
                 @Override
                 public void onComplete() {
                     //如果不为空那么就隐藏起来
                     if (progressDialog != null)
                     progressDialog.dismiss();
                }
            });
 
    }
 
 
     // 图片上绘制文字 加水印
     private final Bitmap drawTextToBitmap(Bitmap bitmap, String text, Paint paint, int paddingLeft, int paddingTop) {
         Bitmap.Config bitmapConfig = bitmap.getConfig();
 
         paint.setDither(true); // 获取跟清晰的图像采样
         paint.setFilterBitmap(true);// 过滤一些
         if (bitmapConfig == null) {
             bitmapConfig = Bitmap.Config.ARGB_8888;
        }
         bitmap = bitmap.copy(bitmapConfig, true);
         Canvas canvas = new Canvas(bitmap);
 
         canvas.drawText(text, paddingLeft, paddingTop, paint);
         return bitmap;
    }
 }

作者:WAsbry
链接:https://juejin.cn/post/7112098300626485284
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册