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