1. 程式人生 > >RxJava + Retrofit2.0的專案實戰完美封裝

RxJava + Retrofit2.0的專案實戰完美封裝

Retrofit 和RxJava已經出來很久了,從去年開始rxjava和retrofit就開始火,所以之前在做專案的時候也用了rxjava和retrofit,今天就介紹一下在專案中如何封裝rxjava和retrofit。對於 RxJava 不是很瞭解的同學推薦你們看這篇文章給 Android 開發者的 RxJava 詳解。Retrofit的使用可以看看Android Retrofit 2.0使用

首先在我們的工程的build.gradle中新增依賴:

    compile 'io.reactivex:rxjava:1.1.8'
    compile 'io.reactivex:rxandroid:1.2.1'
compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0'

然後封裝retrofit:

public class HttpMethods {
    //介面根地址
    public static final String BASE_URL = "http://www.baidu.com";
    //設定超時時間
    private
static final long DEFAULT_TIMEOUT = 10_000L; private Retrofit retrofit; private OkHttpClient client; private static class SingletonHolder { private static final HttpMethods INSTANCE = new HttpMethods(); } //私有化構造方法 private HttpMethods() { client = new OkHttpClient.Builder() .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS) //新增請求頭
//.addInterceptor(new HeaderInterceptor()) //新增日誌列印攔截器 .addInterceptor(new LoggerInterceptor("===", true)) .build(); retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(client) //新增Gson解析 .addConverterFactory(GsonConverterFactory.create()) //新增rxjava .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); } public static HttpMethods getInstance() { return SingletonHolder.INSTANCE; } //這裡返回一個泛型類,主要返回的是定義的介面類 public <T> T createService(Class<T> clazz) { return retrofit.create(clazz); } }

這就是定義的介面類:

/**
 * 介面定義
 */
public interface ApiService {

    @FormUrlEncoded
    @POST("/sys/sendMsg")
    Observable<BaseEntity<DataEntity>> getData(@FieldMap Map<String, String> params);

}

這裡的根地址和介面中的地址都是隨便寫的一個地址,用的時候替換成自己專案的地址就行了。新增請求頭的話,由於專案中沒用到所以直接註釋了。
接下來就是封裝伺服器請求和返回資料。一般情況下返回的資料結構是這樣的:

{
   "status_code":10000,
   "error_msg":"請求成功!",
   "data":{
         "name":"張三",
         "age":3
   }
}

如果你們的伺服器返回不是這樣的格式那你就只有坐下來請他喝茶,跟他好好說了。大不了就懟他。

對於這樣的資料我們肯定要對status_code做出一些判斷,不同的status_code對應不同的錯誤資訊。所以我們新建一個BaseEntity,對應上面的資料結構。

public class BaseEntity<T> implements Serializable {

    private int status_code;
    private String error_msg;
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public int getStatus_code() {
        return status_code;
    }

    public void setStatus_code(int status_code) {
        this.status_code = status_code;
    }

    public String getError_msg() {
        return error_msg;
    }

    public void setError_msg(String error_msg) {
        this.error_msg = error_msg;
    }
}

這就是所有實體的一個基類,data可以為任何資料型別,所以我們使用泛型。
我們要對所以返回結果進行預處理,新建一個DefaultTransformer繼承Observable.Transformer,預處理無非就是對status_code進行判斷和解析,不同的錯誤返回不同的錯誤資訊。有個操作符compose。因為我們在每一個請求中都會處理status_code以及使用一些操作符,比如用observeOn和subscribeOn來切換執行緒。RxJava提供了一種解決方案:Transformer(轉換器),一般情況下就是通過使用操作符compose()來實現。

程式碼:

public class DefaultTransformer<T> implements Observable.Transformer<T, T> {
    @Override
    public Observable<T> call(Observable<T> tObservable) {
        return tObservable
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .map(new Func1<T, T>() {// 通用錯誤處理,判斷code
                    @Override
                    public T call(T t) {
                        if (((BaseEntity<T>)t).getStatus_code() != 10000) {
                            throw new ApiException(((BaseEntity<T>)t).getStatus_code(), ((BaseEntity<T>)t).getError_msg());
                        }
                        return t;
                    }
                });
    }

    public static <T> DefaultTransformer<T> create() {
        return new DefaultTransformer<>();
    }
}

這裡我們使用map操作符把Obserable< BaseEntity< T > >,轉換成為Observable< T >在內部對status_code進行了預處理。這裡當狀態碼不等於10000就表示請求出錯丟擲異常。這裡的ApiException是我們自定義的一個異常類,用來處理伺服器返回的異常。

程式碼:

public class ApiException extends IllegalArgumentException {

    private int code;

    public ApiException(int code, String msg) {
        super(msg);
        this.code = code;
    }

    public int getCode() {
        return code;
    }
}

接下來就是處理網路請求的操作,和顯示載入等待的dialog:

public abstract class ApiSubscriber<T> extends Subscriber<T> {

    private LoadingDialog mDialog;

    public ApiSubscriber() {

    }

    public ApiSubscriber(@NonNull Context context) {
        mDialog = new LoadingDialog(context);
    }

    @Override
    public void onStart() {
        if (mDialog != null)
            mDialog.show();
    }

    @Override
    public void onCompleted() {
        if (mDialog != null && mDialog.isShowing())
            mDialog.dismiss();
    }

    /**
     * 只要鏈式呼叫中丟擲了異常都會走這個回撥
     */
    @Override
    public void onError(Throwable e) {
        if (mDialog != null && mDialog.isShowing())
            mDialog.dismiss();
        if (e instanceof ApiException) {
            //處理伺服器返回的錯誤
        } else if (e instanceof ConnectException || e instanceof UnknownHostException) {
            ToastUtils.showShort("網路異常,請檢查網路");
        } else if (e instanceof TimeoutException || e instanceof SocketTimeoutException) {
            ToastUtils.showShort("網路不暢,請稍後再試!");
        } else if (e instanceof JsonSyntaxException) {
            ToastUtils.showShort("資料解析異常");
        } else {
            ToastUtils.showShort("服務端錯誤");
        }
        e.printStackTrace();
    }
}

這裡新建一個ApiSubscriber類繼承Subscriber類,寫了兩個構造方法,一個是顯示dialog,一個是不顯示dialog。重寫了三個方法,這裡我們沒有重寫onNext方法,因為這個方法是請求成功返回資料的,所以我們等到請求資料介面去重寫。在onError裡面做了所有的錯誤處理,在裡面可以根據伺服器返回的錯誤碼對不同的錯誤做不同的處理。

接下來的話就是管理生命週期了。有個專門的庫可以管理生命週期的叫RxLifecycle,可以去看看。不過我們不用這個,我們在BaseActivity裡面寫。

public abstract class BaseActivity extends AppCompatActivity {

    private CompositeSubscription mCompositeSubscription;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        onUnsubscribe();
    }
    /**
     * 所有rx訂閱後,需要呼叫此方法,用於在detachView時取消訂閱
     */
    protected void addSubscription(Subscription subscribe) {
        if (mCompositeSubscription == null)
            mCompositeSubscription = new CompositeSubscription();
        mCompositeSubscription.add(subscribe);
    }

    /**
     * 取消本頁面所有訂閱
     */
    protected void onUnsubscribe() {
        if (mCompositeSubscription != null && mCompositeSubscription.hasSubscriptions()) {
            mCompositeSubscription.unsubscribe();
        }
    }

}

我們在請求資料的地方呼叫addSubscription(Subscription subscribe)方法,當activity在onDestroy()的時候就取消訂閱了。

最後就是我們的請求資料了,這是之前定義的一個偽介面:

public interface ApiService {

    @FormUrlEncoded
    @POST("/sys/sendMsg")
    Observable<BaseEntity<DataEntity>> getData(@FieldMap Map<String, String> params);

}

然後我們在activity裡面這樣寫:

public class MainActivity extends BaseActivity {

    private TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.tv_content);
        findViewById(R.id.btn_request).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                getData();
            }
        });
    }

    private void getData() {
        Map<String, String> params = new HashMap<>();
        params.put("user_name", "");
        params.put("user_pwd", "");
        Subscription subscribe = HttpMethods.getInstance()
                .createService(ApiService.class)
                .getData(params)
                .compose(DefaultTransformer.<BaseEntity<DataEntity>>create())
                .subscribe(new ApiSubscriber<BaseEntity<DataEntity>>(this) {
                    @Override
                    public void onNext(BaseEntity<DataEntity> entity) {

                    }
                });
        addSubscription(subscribe);//新增訂閱
    }
}

在onNext方法裡面就能獲取到我們的資料了,當然這個介面是請求不成功的,在使用的時候替換成自己的介面就行了。retrofit還有很多的註解,這裡只是使用了@FormUrlEncoded註解(表單的形式)做個示例。到這裡封裝就基本完成了,我也是菜鳥,希望大家多提意見,互相學習。

原始碼地址