Android RxJava系列三: 與Retrofit2結合使用和封
前言
本篇文章主要介紹Rxjava與Retrofit結合使用,對Rxjava和Retrofit不熟悉的可以去看我之前的兩篇介紹
基本使用
定義請求介面
public interface GetRequest_Interface {
@POST("/app/auth/school/list")
Observable<School> postSchool(@Body RequestBody route);//根據學校名獲取學校
}
建立 Retrofit介面例項
GetRequest_Interface request = new Retrofit.Builder()
.baseUrl(API.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(GetRequest_Interface.class);
構建請求引數
這裡以請求體為Json 字串為準
HashMap<String, Object> map = new HashMap<>();
map.put(key, value);
Gson gson=new Gson();
String strEntity = gson.toJson(map);
KLog.d("22222222RequestBody"+strEntity);
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json;charset=UTF-8"),strEntity);
開始網路請求
Observable<School> observable = request.postSchool(body);
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<School>() {
@Override
public void onSubscribe(Disposable d) {
//此處做一些請求開始時的初始化事件,例如彈出一個dialog
}
@Override
public void onNext(School school) {
//此處處理請求成功業務(code == 200 )
}
@Override
public void onError(Throwable e) {
//此處處理請求失敗業務(code != 200 )
}
@Override
public void onComplete() {
//請求完成處理業務,關閉請求佇列,關閉dialog等
}
});
至此,Rxjava 與 Retrofit 結合基本使用就結束了,基於以上就可以愉快的完成網路請求工作了,是不是很方便簡潔.
當然了,對於我們的業務來說,不可能只有一次網路請求,基本處處都需要進去網路請求,而且也不可能如上面一樣,如此簡單. 一般我們的業務中都需要配置一些其他的引數資訊,如果每一次網路請求都像上面那樣寫的話,當然是可以的,但是你的程式碼就太low了,也不符合程式設計規範.
所以基於你會熟練的使用了的前提下,我們就需要將以上程式碼進行簡單封裝.
關於封裝我想多說一句
對於封裝,很多人都認為封裝就是使程式碼足夠簡潔,邏輯足夠清晰,符合開閉原則等,的確是這樣的,但是需要使情況而定,如果你寫的程式碼是服務廣大人群,也就是開源專案,那就要考慮很多因素,做到足夠開放.
但如果只是用到自己的專案裡,那我們必須要明確一點,那就是定製化的前提是符合自己業務的需求,而不要過於封裝.所以也就有為什麼我們常常需要對別人的開源框架做二次封裝,就是這個道理,沒有最好的封裝,只有最合適的封裝.
封裝篇
Url統一存放
public interface API {
//此處存放所有的BaseUrl
String BASE_URL = ""; //核心後臺API
String BASE_SCHOOL_URL = ""; //學校API
}
存放所有請求api
public interface GetRequest_Interface {
/*-------------------------------------所有網路請求 API-------------------------------------------------------*/
@POST("/app/auth/captcha")
Observable<Phone> postPhone(@Body RequestBody route); //獲取驗證碼
@POST("/app/auth/login")
Observable<RegistLogin> postRegist(@Body RequestBody route);//登入註冊
}
封裝處理網路請求所需的引數資訊
- 初始化 配置 OkHttpClient
根據自己業務需求初始化OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS) //l 連線超時時間
.writeTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS) //讀寫超時
.readTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS) //讀取超時
.retryOnConnectionFailure(true) //失敗重連
.addNetworkInterceptor(tokenInterceptor) //新增網路攔截器
.addInterceptor(tokenRespInterceptor)
//.authenticator(authenticator) //授權認證
.build();
這裡需要用到OkHttp3的攔截器相關內容,不熟悉的可以先去了解
- 統一新增公共請求頭
Interceptor tokenInterceptor = new Interceptor() { //全域性攔截器,
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();//獲取原始請求
Request.Builder requestBuilder = originalRequest.newBuilder() //建立新的請求
.addHeader("Accept", "application/json")
.addHeader("Content-Type", "application/json; charset=utf-8")
.removeHeader("User-Agent")
.addHeader("User-Agent",BaseUtils.getUserAgent())
.method(originalRequest.method(),originalRequest.body());
return chain.proceed(requestBuilder.build()); //重新請求
- 全域性動態新增Token
Interceptor tokenInterceptor = new Interceptor() { //全域性攔截器,往請求頭部新增 token 欄位,實現了全域性新增 token
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();//獲取原始請求
Request.Builder requestBuilder = originalRequest.newBuilder() //建立新的請求
.addHeader("Accept", "application/json")
.addHeader("Content-Type", "application/json; charset=utf-8")
.removeHeader("User-Agent")
.addHeader("User-Agent",BaseUtils.getUserAgent())
.method(originalRequest.method(),originalRequest.body());
// Log.e("----------------",originalRequest.body().toString());
Request tokenRequest = null; //帶有token的請求
if (StringUtils.isEmpty(App.mmkv.decodeString(BaseConfig.TOKEN,null))){
return chain.proceed(requestBuilder.build());
}
tokenRequest = requestBuilder
.header("Authorization","Bearer "+App.mmkv.decodeString(BaseConfig.TOKEN,null))
.build();
return chain.proceed(tokenRequest);
}
};
這裡使用了騰訊的MMKV框架進去本地儲存Token,因為我們一開始是沒有拿到Token的,所以需要進行動態新增
- 自動判斷token是否過期,過期無感重新整理
在進行網路互動的時候,服務端簽發的Token是有時效性的而且一般比較短,過了有效期就需要重新請求,而這個過程我們不能讓使用者察覺到,所以需要實現使用者無感知的情況下重新整理請求新的Token.
Interceptor tokenRespInterceptor = new Interceptor() { //攔截返回體 判斷token是否過期.過期重寫拉取新的token
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
// KLog.d( response.body().string());
if (isTokenExpired(response)){
KLog.d( "自動重新整理Token,然後重新請求資料");
//同步請求方式,獲取最新的Token
String newToken = getNewToken();
if (newToken != null){
//使用新的Token,建立新的請求
Request newRequest = chain.request()
.newBuilder()
.header("Authorization", "Bearer " + newToken)
.build();
//重新請求
return chain.proceed(newRequest);
}
}
return response.newBuilder().body(ResponseBody.create(MediaType.parse("UTF-8"),body)).build();
}
};
這裡需要根據服務端約定好的過期規則進去判斷,這裡簡單示範一下
/**
* 根據Response,判斷Token是否失效
*
* @param response
* @return
*/
private boolean isTokenExpired(Response response) {
try {
body = response.body().string();
JSONObject object = new JSONObject(body);
message = object.getString("Message");
code = object.getInt("Code");
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
if ("Token is expired".equals(message)&& code == 1) {
return true;
}
return false;
}
獲取新的Token
/**
* 同步請求方式,獲取最新的Token
*
* @return
*/
private String getNewToken() {
// 透過獲取token的介面,同步請求介面
GetRequest_Interface request = new Retrofit.Builder()
.baseUrl(API.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(GetRequest_Interface.class);
// KLog.e(Remember.getString("refresh_token",null));
RequestBody body = BaseUtils.convertJson(BaseUtils.paramsMap("refresh_token",App.mmkv.decodeString(BaseConfig.REFRESH_TOKEN,null)));
Call<RefreshToken> call = request.postRefreshToken(body);
try {
response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
KLog.e(response.body().getCode()+response.body().getMessage());
if (response.code() == 200 &&response.body().getCode() ==0){
newToken = response.body().getData().getToken();
KLog.e("---------------"+newToken);
App.mmkv.encode(BaseConfig.TOKEN,newToken);
App.mmkv.encode(BaseConfig.SCHOOL_TOKEN,response.body().getData().getSchool_token());
App.mmkv.encode(BaseConfig.EXPIRE_IN,response.body().getData().getExpire_in());
}
return newToken;
}
- 初始化 配置 Retrofit
retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(API.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
至此,關於網路請求的相關引數資訊就基本配置完成
將上述配置步驟進行封裝
/**
* Created by darryrzhong
*
*
* 統一的Retrofit入口
*/
public class RetrofitHelper {
private static final int DEFAULT_TIME_OUT = 5;//超時時間 5s
private static final int DEFAULT_READ_TIME_OUT = 10;
private final Retrofit retrofit;
private String body;
private retrofit2.Response<RefreshToken> response;
private String newToken;
private String message;
private int code;
private RetrofitHelper(){
Interceptor tokenInterceptor = new Interceptor() { //全域性攔截器,往請求頭部新增 token 欄位,實現了全域性新增 token
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();//獲取原始請求
Request.Builder requestBuilder = originalRequest.newBuilder() //建立新的請求
.addHeader("Accept", "application/json")
.addHeader("Content-Type", "application/json; charset=utf-8")
.removeHeader("User-Agent")
.addHeader("User-Agent",BaseUtils.getUserAgent())
.method(originalRequest.method(),originalRequest.body());
// Log.e("----------------",originalRequest.body().toString());
Request tokenRequest = null; //帶有token的請求
if (StringUtils.isEmpty(App.mmkv.decodeString(BaseConfig.TOKEN,null))){
return chain.proceed(requestBuilder.build());
}
tokenRequest = requestBuilder
.header("Authorization","Bearer "+App.mmkv.decodeString(BaseConfig.TOKEN,null))
.build();
return chain.proceed(tokenRequest);
}
};
Interceptor tokenRespInterceptor = new Interceptor() { //攔截返回體 判斷token是否過期.過期重寫拉取新的token
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
// KLog.d( response.body().string());
if (isTokenExpired(response)){
KLog.d( "自動重新整理Token,然後重新請求資料");
//同步請求方式,獲取最新的Token
String newToken = getNewToken();
if (newToken != null){
//使用新的Token,建立新的請求
Request newRequest = chain.request()
.newBuilder()
.header("Authorization", "Bearer " + newToken)
.build();
//重新請求
return chain.proceed(newRequest);
}
}
return response.newBuilder().body(ResponseBody.create(MediaType.parse("UTF-8"),body)).build();
}
};
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS) //l 連線超時時間
.writeTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS) //讀寫超時
.readTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS) //讀取超時
.retryOnConnectionFailure(true) //失敗重連
.addNetworkInterceptor(tokenInterceptor) //新增網路攔截器
.addInterceptor(tokenRespInterceptor)
//.authenticator(authenticator) //授權認證
.build();
retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(API.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
/**
* 同步請求方式,獲取最新的Token
*
* @return
*/
private String getNewToken() {
// 透過獲取token的介面,同步請求介面
GetRequest_Interface request = new Retrofit.Builder()
.baseUrl(API.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(GetRequest_Interface.class);
RequestBody body = BaseUtils.convertJson(BaseUtils.paramsMap("refresh_token",App.mmkv.decodeString(BaseConfig.REFRESH_TOKEN,null)));
Call<RefreshToken> call = request.postRefreshToken(body);
try {
response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
KLog.e(response.body().getCode()+response.body().getMessage());
if (response.code() == 200 &&response.body().getCode() ==0){
newToken = response.body().getData().getToken();
KLog.e("---------------"+newToken);
App.mmkv.encode(BaseConfig.TOKEN,newToken);
App.mmkv.encode(BaseConfig.SCHOOL_TOKEN,response.body().getData().getSchool_token());
App.mmkv.encode(BaseConfig.EXPIRE_IN,response.body().getData().getExpire_in());
}
return newToken;
}
/**
* 根據Response,判斷Token是否失效
*
* @param response
* @return
*/
private boolean isTokenExpired(Response response) {
try {
body = response.body().string();
JSONObject object = new JSONObject(body);
message = object.getString("Message");
code = object.getInt("Code");
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
if ("Token is expired".equals(message)&& code == 1) {
return true;
}
return false;
}
private static class SingletonHolder{
private static final RetrofitHelper INSTANCE = new RetrofitHelper();
}
/**
* 獲取RetrofitServiceManager
* @return
*/
public static RetrofitHelper getInstance(){
return SingletonHolder.INSTANCE;
}
/**
* 獲取對應的Service
* @param service Service 的 class
* @param <T>
* @return
*/
public <T> T create(Class<T> service){
return retrofit.create(service);
}
}
如果業務中有多個BaseUrl的話,可以直接寫個方法暴露出去就好了.
統一處理請求結果的BaseObserver
- 首先建立一個請求結果的回撥 ResponseCallBack
public interface ResponseCallBack<T> {
void onSuccess(T t);
void onFault(String errorMsg);
}
- 建立一個請求開始時的初始化回撥 ProgressListener
public interface ProgressListener {
void startProgress();
void cancelProgress();
}
- 建立統一處理結果的BaseObserver
建立BaseObserver,在回撥中進行業務處理
public class BaseObserver<T> implements Observer<T> {
private ResponseCallBack responseCallBack;
private ProgressListener progressListener;
private Disposable disposable;
public BaseObserver(ResponseCallBack responseCallBack,ProgressListener progressListener){
this.responseCallBack = responseCallBack;
this.progressListener = progressListener;
}
}
在 onSubscribe () 方法中進行請求開始時的初始化操作
@Override
public void onSubscribe(Disposable d) {
this.disposable = d;
if (progressListener != null){
progressListener.startProgress();
}
}
在 onNext () 方法中處理請求成功業務
@Override
public void onNext(T t) {
responseCallBack.onSuccess(t);
}
在onError () 方法中統一處理請求失敗資訊
@Override
public void onError(Throwable e) {
KLog.d(e.getMessage());
try {
if (e instanceof SocketTimeoutException) {//請求超時
responseCallBack.onFault("請求超時,請稍後再試");
} else if (e instanceof ConnectException) {//網路連線超時
responseCallBack.onFault("網路連線超時,請檢查網路狀態");
} else if (e instanceof SSLHandshakeException) {//安全證照異常
responseCallBack.onFault("安全證照異常");
} else if (e instanceof HttpException) {//請求的地址不存在
int code = ((HttpException) e).code();
if (code == 504) {
responseCallBack.onFault("網路異常,請檢查您的網路狀態");
} else if (code == 404) {
responseCallBack.onFault("請求的地址不存在");
} else {
responseCallBack.onFault("請求失敗");
}
} else if (e instanceof UnknownHostException) {//域名解析失敗
responseCallBack.onFault("域名解析失敗");
} else {
responseCallBack.onFault("error:" + e.getMessage());
}
} catch (Exception e2) {
e2.printStackTrace();
} finally {
Log.e("OnSuccessAndFaultSub", "error:" + e.getMessage());
if (disposable !=null && !disposable.isDisposed()){ //事件完成取消訂閱
disposable.dispose();
}
if (progressListener!=null){
progressListener.cancelProgress();
}
}
}
在 onComplete () 中處理請求成功結束後的業務
@Override
public void onComplete() {
if (disposable !=null && !disposable.isDisposed()){ //事件完成取消訂閱
disposable.dispose();
}
if (progressListener!=null){
progressListener.cancelProgress();
}
}
程式碼如下:
/**
* Created by darryrzhong
*
*/
public class BaseObserver<T> implements Observer<T> {
private ResponseCallBack responseCallBack;
private ProgressListener progressListener;
private Disposable disposable;
public BaseObserver(ResponseCallBack responseCallBack,ProgressListener progressListener){
this.responseCallBack = responseCallBack;
this.progressListener = progressListener;
}
@Override
public void onSubscribe(Disposable d) {
this.disposable = d;
if (progressListener != null){
progressListener.startProgress();
}
}
@Override
public void onNext(T t) {
responseCallBack.onSuccess(t);
}
@Override
public void onError(Throwable e) {
KLog.d(e.getMessage());
try {
if (e instanceof SocketTimeoutException) {//請求超時
responseCallBack.onFault("請求超時,請稍後再試");
} else if (e instanceof ConnectException) {//網路連線超時
responseCallBack.onFault("網路連線超時,請檢查網路狀態");
} else if (e instanceof SSLHandshakeException) {//安全證照異常
responseCallBack.onFault("安全證照異常");
} else if (e instanceof HttpException) {//請求的地址不存在
int code = ((HttpException) e).code();
if (code == 504) {
responseCallBack.onFault("網路異常,請檢查您的網路狀態");
} else if (code == 404) {
responseCallBack.onFault("請求的地址不存在");
} else {
responseCallBack.onFault("請求失敗");
}
} else if (e instanceof UnknownHostException) {//域名解析失敗
responseCallBack.onFault("域名解析失敗");
} else {
responseCallBack.onFault("error:" + e.getMessage());
}
} catch (Exception e2) {
e2.printStackTrace();
} finally {
Log.e("OnSuccessAndFaultSub", "error:" + e.getMessage());
if (disposable !=null && !disposable.isDisposed()){ //事件完成取消訂閱
disposable.dispose();
}
if (progressListener!=null){
progressListener.cancelProgress();
}
}
}
@Override
public void onComplete() {
if (disposable !=null && !disposable.isDisposed()){ //事件完成取消訂閱
disposable.dispose();
}
if (progressListener!=null){
progressListener.cancelProgress();
}
}
}
至此,統一處理結果的BaseObserver封裝完畢
其他 (請求傳參,返回JSON)
這裡根據服務端接收資料不同而有不同方式,想要了解更多傳參方式,可以自行去了解Retrofit的註解,這裡只介紹向服務端傳遞Json資料.
- Json 資料格式
首先我們看一下標準的Json資料格式
返回體:
{
"Code": 0,
"Data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC*********",
"refresh_token": "c9ced011-***************************",
"expire_in": 1560330991,
"student_id": 33
},
"Message": "登入成功"
}
請求體:
{"phone":"13145214436","id":"12345"}
返回體的資料解析就不說了,說說請求體怎麼傳遞
首先我們把最外面的{ } json 物件當做是一個 map 物件,這樣是不是一下子就知道怎麼轉化了,對的,就是你想的那樣.
HashMap<String, Object> map = new HashMap<>();
map.put("phone", "13145214436");
map.put("id", "12345");
然後把map物件轉化成json字串,傳給服務端就行了
Gson gson=new Gson();
String strEntity = gson.toJson(map);
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json;charset=UTF-8"),strEntity);
至於更復雜的請求體也是一樣的做法
{
"school_id":1,
"student_id":23,
"start_time":"2019-05-10 15:04:05",
"end_time":"2019-06-10 15:04:05",
"points": [
{
"longitude": 0,
"latitude": 0
},
{
"longitude": 0,
"latitude": 0
},
{
"longitude": 0,
"latitude": 0
},
{
"longitude": 0,
"latitude": 0
}
]
}
上面的請求體中有個json陣列,陣列裡面又巢狀了json物件,還是一樣的做法
把json陣列看成是一個list,對的,有和上面一樣的套路了是不是很簡單,
使用示例
這裡以登入業務做個簡單示範
GetRequest_Interface request = RetrofitHelper.getInstance().create(GetRequest_Interface.class); //request請求入口
HashMap<String,Object> params = new HashMap();
params.put("phone",phone);
params.put("id",id);
RequestBody body = BaseUtils.convertJson(params);
Observable<RegistLogin> observable= request.postRegist(body);
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new BaseObserver<RegistLogin>(new ResponseCallBack<RegistLogin>() {
@Override
public void onSuccess(RegistLogin registLogin) {
//此處處理 code == 200
}
@Override
public void onFault(String errorMsg) {
BaseUtils.showToast(mContext,errorMsg);
}
}, new ProgressListener() {
@Override
public void startProgress() {
dialog = BaseUtils.showSpotsDialog(mContext,"登入中");
dialog.show();
}
@Override
public void cancelProgress() {
dialog.dismiss();
}
}));
這樣一來,是不是程式碼明瞭簡潔,程式碼質量明顯提高了一個層次
至此,Rxjava 和 Retrofit 結合使用 與封裝就基本完成了,再次說明一下,沒有最完美的封裝,只有最適合自己業務的封裝,所以,如果需要請進行自己的業務定製,這裡只提供思路.
歡迎關注作者,更多幹貨等你來拿喲.
請賞個小紅心!因為你的鼓勵是我寫作的最大動力!
更多精彩文章請關注
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4650/viewspace-2823225/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Retrofit2<三> rxJava 分析RxJava
- Rxjava2與Retrofit2的使用RxJava
- Android基於Retrofit2.0+RxJava 封裝的超好用的RetrofitClient工具類(完美結合RxJava)(六)...AndroidRxJava封裝client
- 初探RxJava以及結合Retrofit的使用RxJava
- Android RxJava使用介紹(三) RxJava的操作符AndroidRxJava
- RxJava 與 Retrofit 結合的最佳實踐RxJava
- RxJava操作符系列三RxJava
- RxJava如何結合觀察者與鏈式處理RxJava
- Android RxJava:基礎介紹與使用AndroidRxJava
- 網路庫與Rxjava2結合常見使用場景介紹RxJava
- RxJava 系列-3:使用 SubjectRxJava
- RxJava使用總結RxJava
- Retrofit2的再封裝實戰—多執行緒下載與斷點續傳(三)封裝執行緒斷點
- Android實現Rxjava2+Retrofit完美封裝AndroidRxJava封裝
- 【轉載】在Android中使用Rust:Rust與Android的結合AndroidRust
- Retrofit2使用方式和原始碼解析原始碼
- Android結合DataBinding封裝的BaseBindingAdapterAndroid封裝APT
- RxJava 系列-2:背壓和 FlowableRxJava
- RxJava2 系列-3:使用 SubjectRxJava
- 大話RxJava:三、RxJava的中級使用方法RxJava
- Kotlin中Retrofit與RxJava的簡單封裝KotlinRxJava封裝
- 詳解Android RxJava的使用AndroidRxJava
- Android 淺析 RxJava (一) 使用AndroidRxJava
- RxJava 使用場景小結RxJava
- Android RxJava使用介紹(二) RxJava的操作符AndroidRxJava
- Android RxJava使用介紹(四) RxJava的操作符AndroidRxJava
- Android的shape和顏色選擇器結合使用Android
- Android非同步框架RxJava 1.x系列(三) - 執行緒排程器SchedulerAndroid非同步框架RxJava執行緒
- 【Android架構】基於MVP模式的Retrofit2+RXjava封裝之檔案上傳(三)Android架構MVP模式RxJava封裝
- `GitHub page` 和 `gitbook` 結合使用Github
- 結合使用 Hadoop 和 CouchbaseHadoop
- LlamaIndex RAG 和ReAct結合使用AIIndexReact
- Retrofit2/Rxkotlin網路請求的封裝Kotlin封裝
- RxJava 合併操作RxJava
- RxJava2 系列-2:背壓和FlowableRxJava
- RxJava 系列-1:一篇的比較全面的 RxJava2 方法總結RxJava
- XTask與RxJava的使用比較RxJava
- Android RxJava使用介紹(一) Hello WorldAndroidRxJava