1. 程式人生 > >RxJava2+Retrofit2實現網路請求和解析封裝

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實現網路請求postget請求

例項程式碼於百度雲-一些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--menuOkHttp框架(未封裝),結合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