什么是响应式编程:以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 编程方式的魅力;
传统模式获取图片
实现效果:
传统编写思路:
弹出加载框
开启异步线程:此时有多种途径
- 封装方法....
- 全部写在一起
- 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);
}整体事件流向:
第三步:在拦截器内添加网络请求
@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();
}
这种编程方式成为卡片式编程
好处:后期若需要添加功能,仅需在起点与重点之间添加对应的拦截器,在其中进行处理即可
图片示例:一开始的
- 事件流动顺序
运行结果:
图片示例:此时需要添加个需求,将下载下来的图片添加水印后再展示
事件流动顺序
添加代码:图片上绘制文字 加水印
// 图片上绘制文字 加水印
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);
}
})运行结果:从服务器获取图片并添加水印
还可以添加:及时记录日志等功能
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
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。