RxJava2+Retrofit2實現網路請求和解析封裝
半年多前寫過一篇用Retrofit2請求網路和解析的部落格,Retrofit2的簡單應用與封裝,不過當時其實還是遺留了不少細節問題沒有處理,比如如果有公共引數放Header裡面怎麼處理,請求過程中想顯示進度框怎麼處理,退出時要退出網路請求怎麼處理等等,這兩天看了下RxJava,主要是看了這篇文章RxJava詳解,雖然並不是說的RxJava2,但原理差不多,講得非常清楚,覺得有點意思,就想用RxJava來重新改進一下之前的這個封裝
首先宣告,這篇部落格側重是講如何使用Retrofit2和RxJava2封裝網路請求和解析,並不會重點介紹Retrofit2和RxJava2的知識,如對基礎知識不瞭解,請先檢視上面的連結學習,另外,Retrofit和Retrofit2,RxJava和RxJava2還是有一些不同的地方,本篇部落格是基於Retrofit2和RxJava2
1. 新增依賴
專案中用到Retrofit2,RxJava2,還用到Jackson,我們首先要在build.gradle中新增相關的依賴
compile 'com.squareup.retrofit2:retrofit:2.0.0' compile 'com.squareup.retrofit2:converter-jackson:2.0.0' compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0-RC3' compile 'io.reactivex.rxjava2:rxjava:2.0.0-RC3' compile 'io.reactivex.rxjava2:rxandroid:2.0.0-RC1'
2. 定義Retrofit訪問的介面
public interface RetrofitService {
@FormUrlEncoded
@POST("getUser")
Observable<BaseEntity<User>> getUser(@FieldMap Map<String, String> map);
}
指定使用POST方式,訪問伺服器方法名為getUser,引數@FieldMap可以用來傳遞多個引數,當然如果引數較少,也可以直接使用@Field這裡唯一要注意的是方法的返回值,首先如果我們記得的話,在直接使用Retrofit,不使用RxJava時,我們的返回值都是Call,而如果我們想跟RxJava結合,這裡的返回值物件就應該為Observable,Observable的泛型這裡為BaseEntity<User>,這個在上面的部落格中也講過,這裡再說明下
public class BaseEntity<E> implements Serializable {
private int code;
private String message;
private E data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public E getData() {
return data;
}
public void setData(E data) {
this.data = data;
}
}
BaseEntity是伺服器返回值的通用格式,它由三個部分組成,code表示成功還是失敗,0為成功,非0為失敗,message是提示內容,而主要的內容都封裝在data裡面,data為泛型,可以指定為任何內容,我們這個例子中就是一個User物件
public class User implements Serializable{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
就是一個簡單的實體類,這個根據實際情況大家可以隨意定義3. 對Retrofit2的基本設定
網路請求部分主要使用Retrofit2來實現,我們先看下基礎的設定,直接上程式碼
public class RetroFactory {
private static String baseUrl = "http://192.168.0.107:8082/MyWeb/";
private RetroFactory() {
}
private static OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
builder.addHeader("token", "abc");
return chain.proceed(builder.build());
}
}).connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
private static RetrofitService retrofitService = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(JacksonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(httpClient)
.build()
.create(RetrofitService.class);
public static RetrofitService getInstance() {
return retrofitService;
}
}
這裡baseUrl就是我們伺服器的專案執行的地址,我是在我本機啟動了一個Tomcat,192.168.0.107是我本機的IP,8082是Tomcat的埠號,MyWeb是我伺服器專案的名稱。
接著看,Retrofit2內部是使用OkHttp3的,我們對網路訪問的一些設定都可以通過OkHttp來進行,這裡首先呼叫了一個addInterceptor方法,用來增加一個攔截器,而攔截器的內容是給每個網路請求增加了一個通用的Header欄位,名為token,值為abc,這在實際專案中是非常常見的,每個請求都通過token來識別是否是有效的請求,防止惡意請求,當然,實際token應該是動態生成的,我這裡只是演示如何在Header中新增通用內容,就直接賦值為abc了。
下面接著設定了connectTimeout和readTimeout的超時時間為30秒,實際網路訪問中,存在各種異常情況,掉線,不通,時斷時續等等,那設定超時時間就非常必要了,肯定不能無限等待,其中connectTimeout是連線超時時間,在指定時間內還沒有連線到伺服器就會報SocketTimeout異常,而readTimeout是讀取超時時間,是連線後在指定時間還沒有獲取到資料就超時。
設定為OkHttp,我們再來看Retrofit本身的設定,這裡baseUrl就是我們上面講的公用的連結,addConverterFactory是指定使用Jackson來解析Json資料,當然,你也可以使用Gson或者FastJson,不過資料量大的時候,Gson的效率不高,推薦使用Jackson和FastJson,而addCallAdapterFactory,通過這個轉換,才能將伺服器的返回值從Retrofit預設的Call變為Observable
最後,提供一個方法返回RetrofitService物件,這整個其實就是一個懶漢式單例模式
4. 定義網路請求Activity的公共基類
public class NetworkBaseActivity extends AppCompatActivity {
public ProgressDialog pd;
public Function<Observable, ObservableSource> composeFunction;
private final long RETRY_TIMES = 1;
private boolean showLoading = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
}
private void init() {
pd = new ProgressDialog(this);
composeFunction = new Function<Observable, ObservableSource>() {
@Override
public ObservableSource apply(Observable observable) throws Exception {
return observable.retry(RETRY_TIMES)
.subscribeOn(Schedulers.io())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
if (NetworkUtil.isNetworkAvailable(NetworkBaseActivity.this)) {
if (showLoading) {
if(pd != null && !pd.isShowing()){
pd.show();
}
}
} else {
Toast.makeText(NetworkBaseActivity.this, "網路連線異常,請檢查網路", Toast.LENGTH_LONG).show();
}
}
})
.observeOn(AndroidSchedulers.mainThread());
}
};
}
public void setLoadingFlag(boolean show) {
showLoading = show;
}
@Override
protected void onStop() {
super.onStop();
if (pd != null && pd.isShowing()) {
pd.dismiss();
}
}
}
這裡的ProgressDialog是一個簡單的進度框,因為有的網路請求可能耗時較長,如果介面不提供任何互動的話,使用者會誤以為程式卡死,使用者體驗較差,提供一個進度框就可以解決這個問題。Function是對Observable的一些基礎設定,等會再具體看,RETRY_TIMES,顧名思義,就是重試的次數,網路環境較差或出現其它異常情況的時候,我們希望程式可以自動進行重試,最後一個showLoading用來設定是否顯示進度框,原則上一般的請求都要顯示,所以預設值是true,但也有例外,舉個例子,進應用的時候需要檢測是否有版本更新,這個操作我們肯定是希望在後臺進行而使用者感知不到的,如果這裡出現一個進度框就會莫名其妙,所以我們提供這樣一個設定。
下面我們具體看下這個composeFunction,retry方法就是剛才說到的重試次數,不指定預設為0,subscribeOn用來指定網路請求所在的執行緒,這裡用IO執行緒,doOnSubscribe是在事件傳送前進行的操作,所以我們可以做一些初始化的工作,isNetworkAvailable用來檢測網路是否是連線的
public class NetworkUtil {
public static boolean isNetworkAvailable(Activity activity) {
Context context = activity.getApplicationContext();
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager == null) {
return false;
} else {
NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo();
if (networkInfo != null && networkInfo.length > 0) {
for (int i = 0; i < networkInfo.length; i++) {
if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
}
return false;
}
}
這都是套路,不多說,如果網路連線正常,而且showLoading也為true,我們就顯示一個進度框,否則提示使用者網路連線異常。
observeOn是用來指定Observer操作的執行緒,也就是我們得到伺服器返回的結果後的執行緒,因為我們需要操作控制元件,所以只能在UI執行緒進行。這裡多說一句,doOnSubscribe的執行緒是什麼呢?它既不是在subscribeOn指定的執行緒,更不是在observeOn指定的執行緒,而是執行subscribe時所在的執行緒,subscribe我們現在還沒用到,後面會看到,本程式subscribe是在主執行緒執行,所以doOnSubscribe也就是在主執行緒了,我們這裡顯示進度框就是要在主執行緒進行,所以不用特意去指定,如果subscribe不在主執行緒,那可以在doOnSubscribe後通過subscribeOn指定它所在的執行緒。
setLoadingFlag方法就是提供一個設定是否顯示進度框的途徑。
最後的onStop,保險起見,我們判斷一下,進度框是否關閉,如果沒關閉要關掉,後面我們還會看到,進度框關閉時我們也會取消訂閱,防止已經退出後還在處理請求。
5. 封裝Observer
上面我們對Observable進行了封裝,那現在我們再來封裝Observer
public abstract class BaseObserver<T> implements Observer<BaseEntity<T>> {
private Context mContext;
private ProgressDialog mDialog;
private Disposable mDisposable;
private final int SUCCESS_CODE = 0;
public BaseObserver(Context context, ProgressDialog dialog) {
mContext = context;
mDialog = dialog;
mDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
mDisposable.dispose();
}
});
}
@Override
public void onSubscribe(Disposable d) {
mDisposable = d;
}
@Override
public void onNext(BaseEntity<T> value) {
if (value.getCode() == SUCCESS_CODE) {
T t = value.getData();
onHandleSuccess(t);
} else {
onHandleError(value.getCode(), value.getMessage());
}
}
@Override
public void onError(Throwable e) {
Log.d("gesanri", "error:" + e.toString());
if(mDialog != null && mDialog.isShowing()){
mDialog.dismiss();
}
Toast.makeText(mContext, "網路異常,請稍後再試", Toast.LENGTH_LONG).show();
}
@Override
public void onComplete() {
Log.d("gesanri", "onComplete");
if(mDialog != null && mDialog.isShowing()){
mDialog.dismiss();
}
}
abstract void onHandleSuccess(T t);
void onHandleError(int code, String message) {
Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
}
}
Observer是一個介面,它提供了4個方法,onSubscribe用來隨時取消和Observable的連線,onNext用來處理Observable的返回,也就是網路連線的返回,onComplete在onNext後被呼叫,表示完成,onError表示發生了錯誤,onComplete和onError兩個方法中只會並且肯定會有一個方法被呼叫。
onSubscribe提供了一個Disposable引數,我們呼叫它的dispose方法就可以終止訂閱,在dialog的setOnCancelListener中,我們呼叫它來取消訂閱,這樣如果使用者在請求的過程中覺得等待時間過長,點選返回鍵關閉進度框或者退出應用時,我們就可以取消訂閱而不繼續進行處理了。不過這裡有一點要注意的是,這個dispose方法並不會影響到伺服器端,如果請求已經發送到伺服器端,那就算客戶端呼叫了dispose方法,伺服器端的程式碼依然會繼續執行,在一些服務端介面涉及插入資料庫的操作時,就要特別注意,考慮客戶端在dispose方法後,呼叫伺服器端的方法去執行一個類似回滾的操作,否則客戶端取消了訂閱,伺服器端依然會執行完成,這個要根據專案的實際情況來具體對待。
onNext方法,是我們要處理的主要方法,在這之中,我們通過判斷返回值中的code,來判斷要做的操作,如果code為0,也就是成功,我們就執行onHandleSuccess方法,如果code不為0,也就是失敗,我們就執行onHandleError方法,注意,一般情況下,成功是要單獨處理,而失敗只給使用者提示就可以了,所以這裡我將onHandleSuccess宣告為抽象的,也就是子類必須要實現,而onHandleError不是抽象的,子類可以選擇實現或就用預設的實現即可。
最後,不管是進入了onComplete還是onError方法,都要記得關閉進度框
6. 定義呼叫網路請求的Activity
public class MainActivity extends NetworkBaseActivity {
private TextView name;
private TextView age;
private Observable observable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
name = (TextView) findViewById(R.id.name);
age = (TextView) findViewById(R.id.age);
getUsers();
}
private void getUsers() {
Map<String, String> map = new HashMap<String, String>();
map.put("id", "123");
map.put("name", "gesanri");
observable = RetroFactory.getInstance().getUser(map);
observable.compose(composeFunction).subscribe(new BaseObserver<User>(MainActivity.this, pd) {
@Override
void onHandleSuccess(User user) {
name.setText("姓名:" + user.getName());
age.setText("年齡:" + user.getAge());
}
});
}
}
這個類繼承了我們之前第4步中定義的公共基類,佈局檔案也很簡單
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/age"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
這裡功能很簡單,就是假設傳了2個引數,id和name,完後接收伺服器返回的資料,顯示姓名和年齡。可以看到,經過我們前面幾步的封裝,Activity的實現已經非常乾淨了,短短几行程式碼就實現了網路的請求和返回資料的解析
7.伺服器端的實現
@WebServlet(name = "getUser", value = "/getUser")
public class GetUserServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("token:" + req.getHeader("token"));
System.out.println("id:" + req.getParameter("id"));
System.out.println("name:" + req.getParameter("name"));
resp.setCharacterEncoding("utf-8");
if (req.getHeader("token").equals("abc")) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
resp.getWriter()
.write("{\"code\":0, \"message\":\"獲取使用者成功!\", \"data\":{\"name\":\"張三\", \"age\":23}}");
} else {
resp.getWriter()
.write("{\"code\":1, \"message\":\"獲取使用者失敗!\", \"data\":\"\"}");
}
}
}
最後我們簡單看下伺服器端的實現,我們接收了三個引數,一個是Header裡面的Token, 這個是每個請求都有的,另外兩個是這個getUser請求特定的引數,我們判斷如果token不為abc,就返回錯誤,如果是abc,就返回成功,實際專案肯定是要從資料庫獲取,這裡主要是演示客戶端,就偷懶直接返回資料了。這裡為了在客戶端演示進度框,就休眠了5秒。整個封裝過程就是這樣,還是比較基礎的,當然,不管是Retrofit還是RxJava,能做的工作遠遠不止這些,我個人瞭解的也比較少和淺,大家可以自己繼續深入學習,根據專案的實際需要來不斷優化自己的框架。
相關推薦
RxJava2+Retrofit2實現網路請求和解析封裝
半年多前寫過一篇用Retrofit2請求網路和解析的部落格,Retrofit2的簡單應用與封裝,不過當時其實還是遺留了不少細節問題沒有處理,比如如果有公共引數放Header裡面怎麼處理,請求過程中想顯示進度框怎麼處理,退出時要退出網路請求怎麼處理等等,這兩天看了下RxJav
JS 和 ajax 實現網路請求 和 對應的類封裝 回撥函式實現
先上效果圖 如下: 1、使用ajax 實現網路請求 程式碼如下: function HttpRequest(){ // document.alert('進入這個方法'); //使用 api 框架 跨域 請求
使用retrofit2.0實現網路請求post和get請求
例項程式碼於百度雲-一些androiddemo compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.squareu
RxEasyHttp一款基於RxJava2+Retrofit2實現簡單易用的網路請求框架
RxEasyHttp 本庫是一款基於Retrofit2+RxJava2實現簡單易用的網路請求框架,結合android平臺特性的網路封裝庫,採用api鏈式呼叫一點到底,整合cookie管理,多種快取模式,極簡https配置,上傳下載進度顯示,請求錯誤自動重試
Retrofit2+Rxjava2+OKHttp3+RxAndroid 實現網路請求的demo案例
在之前的專案中一直都用著比較老的網路框架,比如volly,okgo,okhttp等等,平時寫demo 的時候偶爾也會用到新的框架拿來練練手,這兩天寫了一個關於retrofit2的案例,分享出來。 大牛們恐怕已經寫到爛的了,有不足請多多指教。 demo開始前,先匯
Android--menu和OkHttp框架(未封裝),結合Executors(執行緒池)實現網路請求的案例
涉及到的 知識點: 1.安卓UI元件menu 2.OkHttp框架 3.Executors(執行緒池) OkHttp結構簡介 案例程式碼 import android.os.Bundle; import android.suppo
通過代理模式,對第三方網路請求框架進行封裝,實現任意切換網路框架
最近在網上學習了一篇課程,講的是通過代理模式對第三方框架進行封裝。 感覺講的很不錯,受益良多,特此記錄。 首先什麼是代理模式? 代理模式就是:為其他物件提供一種代理,以控制對這個物件的訪問。 舉個例子:沒空下去吃飯,找個同事幫忙買飯就是代理模式;平常租房子, 嫌麻
Android 基於Retrofit2.0的支援多主機地址的網路請求類的封裝
一、首先在Module級別的build.gradle檔案中新增依賴 implementation 'com.squareup.retrofit2:retrofit:2.3.0' implementation 'com.squareup.retrofit2:adapter-r
解析Node.js通過axios實現網路請求
本次給大家分享一篇node.js通過axios實現網路請求的方法,寫的十分的全面細緻,具有一定的參考價值,對此有需要的朋友可以參考學習下。如有不足之處,歡迎批評指正。 1、使用Npm 下載axios npm install --save axios var update_u
微信小程式實現快遞查詢功能(介面傳值、JSON資料請求和解析、radio-group的使用...)
執行效果: 請求資料之前需要首先在小程式平臺設定伺服器域名 第一個介面的實現:介面傳值、radio-group的使用 first.wxml <!--first.wxml--&g
[Swift通天遁地]四、網路和執行緒-(4)使用Alamofire實現網路請求
本文將演示如何使用第三方庫實現網路請求服務。 首先確保在專案中已經安裝了所需的第三方庫。 點選【Podfile】,檢視安裝配置檔案。 1 source 'https://github.com/CocoaPods/Specs.git' 2 platform :ios, '12.0' 3 use_
RxJava+Retrofit2+MVP實現網路請求
上一遍部落格介紹了RxJava+Retrofit2的使用。在前段時間,刷招聘簡歷的時候,發現有一部分的公司會要求MVP模式的理解和具體使用。在現在越來越複雜的業務,我們的Activity的負擔也是越來越大,因此接著這篇我結合MVP模式來介紹一下自己對RxJava
封裝Activity基類+側邊欄+導航欄+輪播圖+Async網路請求+Gson解析+多個ViewPage與Fragment=首頁面
1.導包+依賴+Xlistview外掛 compile 'com.google.code.gson:gson:2.2.4' <uses-permission android:name="android.permission.WRITE_EXTERNAL_
Fiddler實現修改請求和返回數據
point lan request www. 點擊 led 圖片 默認 rul 步驟如下: 點擊rules->Automatic Breakpoints,在這個選項下,我們可以看到三個可選項; Before Requests:在請求發出前攔截請求; After Req
Android RxJava 實戰系列:優雅實現 網路請求巢狀回撥
轉自-----http://blog.csdn.net/carson_ho/article/details/78315696,請為大神打call 前言 Rxjava,由於其基於事件流的鏈式呼叫、邏輯簡潔 & 使用簡單的特點,深受各大 Android
Android RxJava 實戰講解:優雅實現 網路請求輪詢
轉自-----http://blog.csdn.net/carson_ho/article/details/78256466 前言 Rxjava,由於其基於事件流的鏈式呼叫、邏輯簡潔 & 使用簡單的特點,深受各大 Android開發者的歡迎。
HTTP網路請求GSON解析網路不可用跳轉到設定
//1主 mainactivity介面 package com.example.guoxinyu20181101; import android.annotation.SuppressLint; import android.content.DialogInterface; import
Swift使用Alamofire實現網路請求
Alamofire是一個用Swift編寫的HTTP網路庫,由此前熱門開源專案AFNetworking的的作者mattt開發,可非常簡單地用於非同步網路通訊。 要獲取最新版本的 Alamofire,前往https://github.com/Alamofire/Alamofire然後單擊網頁右邊
Http網路請求資料解析json展示資料+網路圖片展示+資料庫+Viewpager+Fragment
1.MainActivity主頁面 package com.bwie.guoxinyu; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import and
OKHTTP-RecyclerView實現網路請求資料
build.gradle中匯入需要依賴 implementation ‘com.google.code.gson:gson:2.8.5’ implementation ‘com.android.support:design:27.1.1’ compile ‘cn.yipianfengye