0. 环境:
电脑:Windows10
Android Studio: 2024.3.2
编程语言: Java
Gradle version:8.11.1
Compile Sdk Version:35
Java 版本:Java11
1. 介绍RxJava
GitHub开源地址:https://github.com/ReactiveX/RxJava
依赖库:(两个都要)
implementation "io.reactivex.rxjava3:rxjava:3.1.10"
implementation "io.reactivex.rxjava3:rxandroid:3.0.2"
Rx即为响应式思想。GitHub上、网络上已经有很多介绍了。这边不赘述。
2. 使用RxJava加载图片案例
功能拆解:
1. 获得图片地址
2. 拿到图片地址后,下载图片。2.1下载为耗时操作,需要loading框2.2 通过网络请求,下载图片
3. 下载完成后,dismiss loading,并且使用imageView显示出来3.1 隐藏loading框3.2 imageView setImage
根据这个功能,来写代码:(了解观察者、被观察者、整条逻辑链路)
String PATH = "https://c-ssl.dtstatic.com/uploads/blog/202405/03/73SmDPGxIeB0Gel.thumb.1000_0.jpg"; // 图片地址
ProgressDialog progressDialog; //loading可以改为自定义的loading框
public void rxJavaDownloadImage() {// observable 为被观察者。被观察者为图片地址StringObservable.just(PATH)// 拿到图片地址后,通过网络请求下载图片.map(new Function<String, Bitmap>() {@Overridepublic Bitmap apply(String s) throws Throwable {// 网络下载图片URL url = new URL(PATH);HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();httpURLConnection.setConnectTimeout(5000);int responseCode = httpURLConnection.getResponseCode();if (responseCode == HttpURLConnection.HTTP_OK) {InputStream inputStream = httpURLConnection.getInputStream();Bitmap bitmap = BitmapFactory.decodeStream(inputStream);return bitmap;} else {Log.i("TAG", "apply: " + responseCode);}return null;}}).subscribeOn(Schedulers.io()) //切换到子线程.observeOn(AndroidSchedulers.mainThread()) // 将观察者,切换到主线程.subscribe(// observer为观察者,观察上一个动作结束时的结果new Observer<Bitmap>() {//订阅刚开始时@Overridepublic void onSubscribe(@NonNull Disposable d) {//加载loading框progressDialog = new ProgressDialog(getActivity());progressDialog.setTitle("downloading...");progressDialog.show();}// 获取到上个事件的结果时@Overridepublic void onNext(@NonNull Bitmap bitmap) {// imageView setBitmapbinding.imageView.setImageBitmap(bitmap);}// 出现错误时@Overridepublic void onError(@NonNull Throwable e) {// 如果出现错误,则显示裂开图片 error.pngif (progressDialog != null) {progressDialog.dismiss();}Log.i("----log----", "onError: ");e.printStackTrace();}// 事件结束时@Overridepublic void onComplete() {// 隐藏loading框if (progressDialog != null) {progressDialog.dismiss();}}});
}
我们再来回顾一下刚刚拆解的功能:
// 这边设置成常量PATH,实际应用中为变量
// Observable.just(PATH) 传递给下一个卡片String对象
1. 获得图片地址。// 在 .map(new Function<String, Bitmap>() 中操作,并且传递给下一个卡片bitmap对象
2. 拿到图片地址后,下载图片。// 这边使用ProgressDialog,实际应用中,会使用自定义UI// 在onSubscribe中操作,即订阅开始时2.1下载为耗时操作,需要loading框// 这边使用HttpURLConnection,实际应用中,会使用网络框架2.2 通过网络请求,下载图片// 在 subscribe中,new Observer<Bitmap>()新建一个观察者,用于观察上一个卡片给的bitmap对象
3. 下载完成后,dismiss loading,并且使用imageView显示出来// 在onComplete中操作,即事件完成时、事件结束时。3.1 隐藏loading框// 这边使用了binding来操作:binding.imageView.setImageBitmap(bitmap);详情请框ViewBinding框架// 在onNext中操作,即拿到上一张卡片给的bitmap对象3.2 imageView setImage
当然,代码中的方法:rxJavaDownloadImage(),我们通过按钮的点击事件来实现就好了。
这样,就完成了RxJava来加载图片的简单功能。
3. RxJava的修改案例
仅仅只是使用,还不够。
3.1 增加需求时的修改(了解.map)
此时有一个新需求:需要给图片增加水印。又该如何修改呢?
功能拆解
1. 获得图片地址
2. 拿到图片地址后,下载图片。2.1下载为耗时操作,需要loading框2.2 通过网络请求,下载图片
3. 下载完成后,dismiss loading,3.1 隐藏loading框
4. 给图片设置水印,并且使用imageView显示出来 //显示图片之前,增加“设置水印”的动作4.1 设置水印 //增加设置水印的动作4.2 imageView setImage
增加水印的功能,只需要在下载完图片后,插入“给图片增加水印”的map即可
// 增加水印的卡片
.map(new Function<Bitmap, Bitmap>() {@Overridepublic Bitmap apply(Bitmap bitmap) throws Throwable {// 在这里增加自定义的水印Paint paint = new Paint();paint.setTextSize(88);paint.setColor(Color.RED);return drawTextToBitmap(bitmap, "水印设置", paint, 88, 88);}
})
完整代码如下:
String PATH = "https://c-ssl.dtstatic.com/uploads/blog/202405/03/73SmDPGxIeB0Gel.thumb.1000_0.jpg"; // 图片地址ProgressDialog progressDialog; //loading可以改为自定义的loading框public void rxJavaDownloadImage() {// observable 为被观察者。被观察者为图片地址StringObservable.just(PATH)// 拿到图片地址后,通过网络请求下载图片.map(new Function<String, Bitmap>() {@Overridepublic Bitmap apply(String s) throws Throwable {// 网络下载图片URL url = new URL(PATH);HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();httpURLConnection.setConnectTimeout(5000);int responseCode = httpURLConnection.getResponseCode();if (responseCode == HttpURLConnection.HTTP_OK) {InputStream inputStream = httpURLConnection.getInputStream();Bitmap bitmap = BitmapFactory.decodeStream(inputStream);return bitmap;} else {Log.i("TAG", "apply: " + responseCode);}return null;}})// 增加水印的卡片.map(new Function<Bitmap, Bitmap>() {@Overridepublic Bitmap apply(Bitmap bitmap) throws Throwable {// 在这里增加自定义的水印Paint paint = new Paint();paint.setTextSize(88);paint.setColor(Color.RED);return drawTextToBitmap(bitmap, "水印设置", paint, 88, 88);}}).subscribeOn(Schedulers.io()) //切换到子线程.observeOn(AndroidSchedulers.mainThread()) // 将观察者,切换到主线程.subscribe(// observer为观察者,观察上一个动作结束时的结果new Observer<Bitmap>() {//订阅刚开始时@Overridepublic void onSubscribe(@NonNull Disposable d) {//加载loading框progressDialog = new ProgressDialog(getActivity());progressDialog.setTitle("downloading...");progressDialog.show();}// 获取到上个事件的结果时@Overridepublic void onNext(@NonNull Bitmap bitmap) {// imageView setBitmapbinding.imageView.setImageBitmap(bitmap);}// 出现错误时@Overridepublic void onError(@NonNull Throwable e) {// 如果出现错误,则显示裂开图片 error.pngif (progressDialog != null) {progressDialog.dismiss();}Log.i("----log----", "onError: ");e.printStackTrace();}// 事件结束时@Overridepublic void onComplete() {// 隐藏loading框if (progressDialog != null) {progressDialog.dismiss();}}});}/*** 增加水印的函数。(不是这篇文章的重点)* @return bitmap*/public static Bitmap drawTextToBitmap(Bitmap bitmap,String gText,Paint paint,int x,int y) {android.graphics.Bitmap.Config bitmapConfig =bitmap.getConfig();if(bitmapConfig == null) {bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;}bitmap = bitmap.copy(bitmapConfig, true);Canvas canvas = new Canvas(bitmap);Rect bounds = new Rect();paint.getTextBounds(gText, 0, gText.length(), bounds);canvas.drawText(gText, x, y, paint);return bitmap;}
我们可以发现:需要增加功能,仅仅只需要在该链路中增加一个.map来修改,完全不会破坏原有的代码逻辑。而且该map也可以直接复制到其他代码去使用。这个就是优势了
3.2 封装相同代码(了解ObservableTransformer)
我们发现,每一条逻辑都会包含线程切换。相同代码比较多。我们可以将其进行封装
引入一个ObservableTransformer<Upstream, Downstream>对象
查阅源码发现,ObservableTransformer 需要compose接口来调用。
Upstream:上游
Downstream:下游
所以,我们可以封装代码:
private final static <UD>ObservableTransformer<UD, UD> rxud() {return new ObservableTransformer<UD, UD>() {@Overridepublic @NonNull ObservableSource<UD> apply(@NonNull Observable<UD> upstream) {return upstream.subscribeOn(Schedulers.io()) //切换到子线程.observeOn(AndroidSchedulers.mainThread()) // 将观察者,切换到主线程;.map(new Function<UD, UD>() {@Overridepublic UD apply(UD ud) throws Throwable {// 甚至可以增加日志功能,此处为伪代码System.out.println("图片下载的时间" + System.currentTimeMillis());return ud;}});}};
}
可以看到return返回值 就是上游切换为子线程,下游切换为主线程。同时,我甚至可以在封装代码中,增加“日志功能”的卡片。这样每次下载图片的时间,都会被记录到日志当中。
要如何使用封装代码呢?使用.compose(rxud())即可。
请看下面的示例:
public void rxJavaDownloadImage() {// observable 为被观察者。被观察者为图片地址StringObservable.just(PATH)// 拿到图片地址后,通过网络请求下载图片.map(new Function<String, Bitmap>() {@Overridepublic Bitmap apply(String s) throws Throwable {// 网络下载图片URL url = new URL(PATH);HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();httpURLConnection.setConnectTimeout(5000);int responseCode = httpURLConnection.getResponseCode();if (responseCode == HttpURLConnection.HTTP_OK) {InputStream inputStream = httpURLConnection.getInputStream();Bitmap bitmap = BitmapFactory.decodeStream(inputStream);return bitmap;} else {Log.i("TAG", "apply: " + responseCode);}return null;}})// 增加水印的卡片.map(new Function<Bitmap, Bitmap>() {@Overridepublic Bitmap apply(Bitmap bitmap) throws Throwable {// 在这里增加自定义的水印Paint paint = new Paint();paint.setTextSize(88);paint.setColor(Color.RED);return drawTextToBitmap(bitmap, "水印设置", paint, 88, 88);}}).compose(rxud()) // 此处调用封装代码即可.subscribe(// observer为观察者,观察上一个动作结束时的结果new Observer<Bitmap>() {//订阅刚开始时@Overridepublic void onSubscribe(@NonNull Disposable d) {//加载loading框progressDialog = new ProgressDialog(getActivity());progressDialog.setTitle("downloading...");progressDialog.show();}// 获取到上个事件的结果时@Overridepublic void onNext(@NonNull Bitmap bitmap) {// imageView setBitmapbinding.imageView.setImageBitmap(bitmap);}// 出现错误时@Overridepublic void onError(@NonNull Throwable e) {// 如果出现错误,则显示裂开图片 error.pngif (progressDialog != null) {progressDialog.dismiss();}Toast.makeText(getActivity(), "download error", Toast.LENGTH_SHORT).show();Log.i("----log----", "onError: ");e.printStackTrace();}// 事件结束时@Overridepublic void onComplete() {// 隐藏loading框if (progressDialog != null) {progressDialog.dismiss();}}});}
这样我们就学会了封装代码。
后续开发过程中,如果出现功能A:下载图片、功能B:网络请求、 功能C:上传头像,我们都可以通过调用封装的代码来实现线程切换。
4. 网络请求替换成Retrofit
4.1 准备工作
依赖库:
// retrofit 核心依赖
implementation "com.squareup.retrofit2:retrofit:3.0.0"
// json解析工具依赖
implementation "com.squareup.retrofit2:converter-gson:3.0.0"
// rxjava转换处理工具
implementation "com.squareup.retrofit2:adapter-rxjava3:3.0.0"
项目api:即为网络请求接口
例子1:不带参数的网络请求
https://wanandroid.com/project/tree/json
例子2:带参数的网络请求
https://wanandroid.com/project/list/1/json?cid=294
其中,1为pageIndex;cid=294为body参数
4.2 创建bean
这里我使用插件(GsonFormatPlus)生成。没有安装过的,需要在marketplace搜索安装

安装完成后,新建一个bean的Java文件,然后按快捷键,快捷键是alt+s (win), option + s(mac)
粘贴进json数据后,(左下角Setting可以自定义格式) 点完成OK,就会自动生成bean的内容
4.3 创建NetApi接口
比较简单,我直接贴代码:
package com.liosen.androidnote.api;import com.liosen.androidnote.bean.ItemBean;
import com.liosen.androidnote.bean.ProjectBean;import io.reactivex.rxjava3.core.Observable;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;public interface NetApi {// https://wanandroid.com/project/tree/json// 其中base为 wanandroid.com/// 所以只需要后续部分即可@GET("project/tree/json")Observable<ProjectBean> getProject();// https://wanandroid.com/project/list/1/json?cid=294// 1为动态页码,通过@Path 传参// cid为网址参数,通过@Query 传参@GET("project/list/{pageIndex}/json")Observable<ItemBean> getProjectItem(@Path("pageIndex") int pageIndex, @Query("cid") int cid);
}
4.4 网络工具类
通过okhttp来进行网络请求。封装成工具类即可。
以下代码复制就好了。
package com.liosen.androidnote.util;import com.google.gson.Gson;import java.util.concurrent.TimeUnit;import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;public class HttpUtils {public static String BASE_URL = "https://wanandroid.com/";public static Retrofit getOnlineCookieRetrofit() {OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();OkHttpClient client = httpBuilder.readTimeout(15, TimeUnit.SECONDS).connectTimeout(15, TimeUnit.SECONDS).writeTimeout(15, TimeUnit.SECONDS).build();return new Retrofit.Builder().baseUrl(BASE_URL)// okhttp请求.client(client)// rxJava 处理// 添加一个json解析工具.addConverterFactory(GsonConverterFactory.create(new Gson()))// 添加 RxJava处理工具.addCallAdapterFactory(RxJava3CallAdapterFactory.create()).build();}
}
4.5 网络请求
/*** 获取项目的网络请求*/public void getProjectHttp() {netApi.getProject().compose(rxud()) // 此处用到 3.2中封装的代码.subscribe(new Consumer<ProjectBean>() {@Overridepublic void accept(ProjectBean projectBean) throws Throwable {Log.i(TAG, "accept: " + projectBean);}});}/*** 获取item的网络请求*/public void getProjectItemHttp() {//实际项目中,需要传入这两个参数。暂时先用常量表示getProjectItemHttp(1, 294);}public void getProjectItemHttp(int pageIndex, int cid) {netApi.getProjectItem(pageIndex,cid).compose(rxud()) // 此处用到 3.2中封装的代码//同理,如果还封装了日志功能,此处一样可以插入卡片// .map() 这样插入即可。详情查看3.1.subscribe(new Consumer<ItemBean>() {@Overridepublic void accept(ItemBean itemBean) throws Throwable {Log.i(TAG, "accept: " + itemBean);}});}
其中Consumer观察者为简易观察者,不像Observer一样,需要实现以下四种方法:
onSubscribe
onNext
onError
onComplete
仅需要实现accept() 即可。等同于onNext
5. 防快速点击(RxJava实现)
5.1 依赖库
implementation 'com.jakewharton.rxbinding4:rxbinding:4.0.0'
5.2 功能实现案例
功能描述:点击button,点击事件触发获取项目信息的http
要求:防快速点击(防手指抖动)避免服务器连续收到http请求
代码关键部分:
// clicks 传入view, 也就是button这个view。此处是使用viewBinding框架
// Button btn = findViewById(R.id.button); 此处传入btn也是一样的
RxView.clicks(binding.buttonRequest).throttleFirst(2000, TimeUnit.MILLISECONDS) //防抖动,2秒内只响应第1次.subscribe(new Consumer<Unit>() {//此处的Unit可以不用接收@Overridepublic void accept(Unit unit) throws Throwable {// 网络请求,获取项目http。与getProjectHttp() 函数一样netApi.getProject().compose(rxud()).subscribe(new Consumer<ProjectBean>() {@Overridepublic void accept(ProjectBean projectBean) throws Throwable {Log.i(TAG, "accept: " + projectBean);// 此处可以通过获得的projectBean,继续请求Itemfor (ProjectBean.DataDTO data : projectBean.getData()) {getProjectItemHttp(1, data.getId());}}});}})
;
6. 写在最后
至此,我们学会了:
修改RxJava的修改(增加卡片、删除卡片)、
封装操作线程代码、
RxJava + Retrofit、
RxView实现防快速点击。
以上功能,基本上可以在项目中使用了。
关于RxJava,可以查看我其他文章:
【安卓笔记】RxJava的使用+修改功能+搭配retrofit+RxView防快速点击:https://blog.csdn.net/liosen/article/details/149340103
【安卓笔记】RxJava之flatMap的使用:https://blog.csdn.net/liosen/article/details/149343166
【安卓笔记】RxJava的onNextDo的使用:https://blog.csdn.net/liosen/article/details/149343321