去年的時候學習了RxJAVA和Retrofit的基本用法,但一直沒有在實際項目中運用。今年開做新項目,果斷在新項目中引入了RxJava和Retrofit。本篇文章將介紹筆者在項目中對Retrofit的封裝。
先來看一下封裝過后的Retrofit如何使用。
RetrofitHelper.getApiService() .getMezi() .compose(this.<List<MeiZi>>bindToLifecycle()) .compose(ProgressUtils.<List<MeiZi>>ApplyProgressBar(this)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new DefaultObserver<List<MeiZi>>() { @Override public void onSuccess(List<MeiZi> response) { showToast("請求成功,妹子個數為" + response.size()); } });
沒錯,就是這么簡潔的一個鏈式調用,可以顯示加載動畫,還加入了Retrofit生命周期的管理。
開始之前需要先在module項目里的Gradle文件中添加用到的依賴庫
compile "io.reactivex.rxjava2:rxjava:$rootProject.ext.rxjava2Version" compile "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofit2Version" compile "com.squareup.retrofit2:converter-scalars:$rootProject.ext.retrofit2Version" compile "com.squareup.retrofit2:converter-gson:$rootProject.ext.retrofit2Version" compile "com.squareup.retrofit2:adapter-rxjava2:$rootProject.ext.retrofit2Version" compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0' compile 'io.reactivex.rxjava2:rxandroid:2.0.1' compile 'com.squareup.okhttp3:logging-interceptor:3.4.1' compile "com.trello.rxlifecycle2:rxlifecycle:$rootProject.ext.rxlifecycle" //compile "com.trello.rxlifecycle2:rxlifecycle-android:$rootProject.ext.rxlifecycle" compile "com.trello.rxlifecycle2:rxlifecycle-components:$rootProject.ext.rxlifecycle"
為了方便依賴庫版本的修改我們采用”io.reactivex.rxjava2:rxjava:$rootProject.ext.rxjava2Version”這中方式添加依賴,因此需要在project的build.gradle文件的加上以下內容:
ext { supportLibVersion = '25.1.0' butterknifeVersion = '8.5.1' rxjava2Version = '2.0.8' retrofit2Version = '2.2.0' rxlifecycle='2.1.0' gsonVersion = '2.8.0' }
下面將通過幾個小節對本次封裝作詳細的解析:
- 服務器響應數據的基類BasicResponse
- 構建初始化Retrofit的工具類IdeaApi
- 通過GsonConverterFactory獲取真實響應數據
- 封裝DefaultObserver處理服務器響應
- 處理加載Loading
- 管理Retrofit生命周期
- 如何使用封裝
- 小結
一.服務器響應數據的基類BasicResponse。
假定服務器返回的Json數據格式如下:
{ "code": 200, "message": "成功", "content": { ... } }
根據Json數據格式構建我們的BasicResponse(BasicResponse中的字段內容需要根據自己服務器返回的數據確定)。代碼如下:
public class BasicResponse<T> { private int code; private String message; private T content; ...此處省去get、set方法。
二.構建初始化Retrofit的工具類IdeaApi。
該類通過RetrofitUtils來獲取ApiService的實例。代碼如下:
public class IdeaApi { public static <T> T getApiService(Class<T> cls,String baseUrl) { Retrofit retrofit = RetrofitUtils .getRetrofitBuilder(baseUrl).build(); return retrofit.create(cls); } }
RetrofitUtils用來構建Retrofit.Builder,并對OkHttp做以下幾個方面的配置:
- 設置日志攔截器,攔截服務器返回的json數據。Retrofit將請求到json數據直接轉換成了實體類,但有時候我們需要查看json數據,Retrofit并沒有提供直接獲取json數據的功能。因此我們需要自定義一個日志攔截器攔截json數據,并輸入到控制臺。
- 設置Http請求頭。給OkHttp 添加請求頭攔截器,配置請求頭信息。還可以為接口統一添加請求頭數據。例如,把用戶名、密碼(或者token)統一添加到請求頭。后續每個接口的請求頭中都會攜帶用戶名、密碼(或者token)數據,避免了為每個接口單獨添加。
- 為OkHttp配置緩存。同樣可以同過攔截器實現緩存處理。包括控制緩存的最大生命值,控制緩存的過期時間。
- 如果采用https,我們還可以在此處理證書校驗以及服務器校驗。
- 為Retrofit添加GsonConverterFactory。此處是一個比較重要的環節,將在后邊詳細講解。
RetrofitUtils 代碼如下:
public class RetrofitUtils { public static OkHttpClient.Builder getOkHttpClientBuilder() { HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { @Override public void log(String message) { try { LogUtils.e("OKHttp-----", URLDecoder.decode(message, "utf-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); LogUtils.e("OKHttp-----", message); } } }); loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); File cacheFile = new File(Utils.getContext().getCacheDir(), "cache"); Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb return new OkHttpClient.Builder() .readTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS) .connectTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS) .addInterceptor(loggingInterceptor) .addInterceptor(new HttpHeaderInterceptor()) .addNetworkInterceptor(new HttpCacheInterceptor()) // .sslSocketFactory(SslContextFactory.getSSLSocketFactoryForTwoWay()) // https認證 如果要使用https且為自定義證書 可以去掉這兩行注釋,并自行配制證書。 // .hostnameVerifier(new SafeHostnameVerifier()) .cache(cache); } public static Retrofit.Builder getRetrofitBuilder(String baseUrl) { Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create(); OkHttpClient okHttpClient = RetrofitUtils.getOkHttpClientBuilder().build(); return new Retrofit.Builder() .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl(baseUrl); } }
三.通過GsonConverterFactory獲取真實響應數據
在第一節中我們構建了服務器響應數據BasicResponse,BasicResponse由code、message、和content三個字段。其中code為服務器返回的錯誤碼。我們會事先和服務器約定成功時的code值,比如200表示請求成功。但通常在請求服務器數據過程中免不了會出現各種錯誤。例如用戶登錄時密碼錯誤、請求參數錯誤的情況。此時服務器會根據錯誤情況返回對應的錯誤碼。一般來說,我們只關心成功時即code為200時的content數據。而對于code不為200時我們只需要給出對應的Toast提示即可。事實上我們對我們有用的僅僅時code為200時的content數據。因此我們可以考慮過濾掉code和message,在請求成功的回調中只返回content的內容。
在此種情況下就需要我們通過自定義GsonConverterFactory來實現了。我們可以直接從Retrofit的源碼中copy出GsonConverterFactory的三個相關類來做修改。
其中最終要的一部分是修改GsonResponseBodyConverter中的convert方法。在該方法中拿到服務器響應數據并判斷code是否為200。如果是,則獲取到content并返回,如果不是,則在此處可以拋出對應的自定義的異常。然后再Observer中統一處理異常情況。GsonResponseBodyConverter代碼如下:
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, Object> { private final TypeAdapter<T> adapter; GsonResponseBodyConverter(TypeAdapter<T> adapter) { this.adapter = adapter; } @Override public Object convert(ResponseBody value) throws IOException { try { BasicResponse response = (BasicResponse) adapter.fromJson(value.charStream()); if (response.getCode()==200) { return response.getResults(); } else { // 特定 API 的錯誤,在相應的 DefaultObserver 的 onError 的方法中進行處理 throw new ServerResponseException(response.getCode(), response.getMessage()); } } finally { value.close(); } return null; } }
四.構建DefaultObserver處理服務器響應。
上一節中我們講到了在請求服務器時可能出現的一些例如密碼錯誤、參數錯誤的情況,服務器給我們返回了對應的錯誤碼,我們根據錯誤碼拋出了對應自定義異常。除此之外在我們發起網絡請求時還可能發生一些異常情況。例如沒有網絡、請求超時或者服務器返回了數據但在解析時出現了數據解析異常等。對于這樣的情況我們也要進行統一處理的。那么我們就需要自定義一個DefaultObserver類繼承Observer,并重寫相應的方法。
該類中最重要的兩個方法時onNext和onError。
1.在服務器返回數據成功的情況下會回調到onNext方法。因此我們可以在DefaultObserver中定義一個抽象方法onSuccess(T response),在調用網絡時重寫onSuccess方法即可。
2.如果在請求服務器過程中出現任何異常,都會回調到onError方法中。包括上節中我們自己拋出的異常都會回調到onError。因此我們的重頭戲就是處理onError。在onError中我們根據異常信息給出對應的Toast提示即可。
DefaultObserver類的代碼如下:
public abstract class DefaultObserver<T> implements Observer<T> { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(T response) { onSuccess(response); onFinish(); } @Override public void onError(Throwable e) { LogUtils.e("Retrofit", e.getMessage()); if (e instanceof HttpException) { // HTTP錯誤 onException(ExceptionReason.BAD_NETWORK); } else if (e instanceof ConnectException || e instanceof UnknownHostException) { // 連接錯誤 onException(ExceptionReason.CONNECT_ERROR); } else if (e instanceof InterruptedIOException) { // 連接超時 onException(ExceptionReason.CONNECT_TIMEOUT); } else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException) { // 解析錯誤 onException(ExceptionReason.PARSE_ERROR); }else if(e instanceof ServerResponseException){ onFail(e.getMessage()); } else { onException(ExceptionReason.UNKNOWN_ERROR); } onFinish(); } @Override public void onComplete() { } /** * 請求成功 * * @param response 服務器返回的數據 */ abstract public void onSuccess(T response); /** * 服務器返回數據,但響應碼不為200 * */ public void onFail(String message) { ToastUtils.show(message); } public void onFinish(){} /** * 請求異常 * * @param reason */ public void onException(ExceptionReason reason) { switch (reason) { case CONNECT_ERROR: ToastUtils.show(R.string.connect_error, Toast.LENGTH_SHORT); break; case CONNECT_TIMEOUT: ToastUtils.show(R.string.connect_timeout, Toast.LENGTH_SHORT); break; case BAD_NETWORK: ToastUtils.show(R.string.bad_network, Toast.LENGTH_SHORT); break; case PARSE_ERROR: ToastUtils.show(R.string.parse_error, Toast.LENGTH_SHORT); break; case UNKNOWN_ERROR: default: ToastUtils.show(R.string.unknown_error, Toast.LENGTH_SHORT); break; } } /** * 請求網絡失敗原因 */ public enum ExceptionReason { /** * 解析數據失敗 */ PARSE_ERROR, /** * 網絡問題 */ BAD_NETWORK, /** * 連接錯誤 */ CONNECT_ERROR, /** * 連接超時 */ CONNECT_TIMEOUT, /** * 未知錯誤 */ UNKNOWN_ERROR, } }
五.處理加載Loading
關于Loading我們可以通過RxJava的compose操作符來做一個非常優雅的處理。首先定義一個ProgressUtils工具類,然后通過RxJava的ObservableTransformer做一個變換來處理Loading。想要顯示Loading,只需要加上.compose(ProgressUtils.< T >applyProgressBar(this))即可。
ProgressUtils代碼如下:
public class ProgressUtils { public static <T> ObservableTransformer<T, T> applyProgressBar( @NonNull final Activity activity, String msg) { final WeakReference<Activity> activityWeakReference = new WeakReference<>(activity); final DialogUtils dialogUtils = new DialogUtils(); dialogUtils.showProgress(activityWeakReference.get()); return new ObservableTransformer<T, T>() { @Override public ObservableSource<T> apply(Observable<T> upstream) { return upstream.doOnSubscribe(new Consumer<Disposable>() { @Override public void accept(Disposable disposable) throws Exception { } }).doOnTerminate(new Action() { @Override public void run() throws Exception { Activity context; if ((context = activityWeakReference.get()) != null && !context.isFinishing()) { dialogUtils.dismissProgress(); } } }).doOnSubscribe(new Consumer<Disposable>() { @Override public void accept(Disposable disposable) throws Exception { /*Activity context; if ((context = activityWeakReference.get()) != null && !context.isFinishing()) { dialogUtils.dismissProgress(); }*/ } }); } }; } public static <T> ObservableTransformer<T, T> applyProgressBar( @NonNull final Activity activity) { return applyProgressBar(activity, ""); } }
至此關于RxJava和Retrofit的二次封裝已經基本完成。但是我們不能忽略了很重要的一點,就是網絡請求的生命周期。我們將在下一節中詳細講解。
## 六、管理Retrofit生命周期
當activity被銷毀時,網絡請求也應該隨之終止的。要不然就可能造成內存泄漏。會嚴重影到響App的性能!因此Retrofit生命周期的管理也是比較重要的一點內容。在這里我們使用 RxLifecycle來對Retrofit進行生命周期管理。其使用流程如下:
1.在gradel中添加依賴如下:
compile 'com.trello.rxlifecycle2:rxlifecycle:2.1.0' compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'
2.讓我們的BaseActivity繼承RxAppCompatActivity。
具體代碼如下:
public abstract class BaseActivity extends RxAppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutId()); init(savedInstanceState); } protected void showToast(String msg) { ToastUtils.show(msg); } protected abstract @LayoutRes int getLayoutId(); protected abstract void init(Bundle savedInstanceState); }
同樣我們項目的BaseFragment繼承RxFragment(注意使用繼承V4包下的RxFragment),如下:
public abstract class BaseFragment extends RxFragment { public View rootView; public LayoutInflater inflater; @Nullable @Override public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); this.inflater = inflater; if (rootView == null) { rootView = inflater.inflate(this.getLayoutId(), container, false); init(savedInstanceState); } ViewGroup parent = (ViewGroup) rootView.getParent(); if (parent != null) { parent.removeView(rootView); } return rootView; } protected abstract int getLayoutId(); protected abstract void init(Bundle savedInstanceState); protected void showToast(String msg) { ToastUtils.show(msg); } @Override public void onResume() { super.onResume(); } @Override public void onPause() { super.onPause(); } @Override public void onDestroyView() { super.onDestroyView(); } }
3.使用compose操作符管理Retrofit生命周期了:
myObservable .compose(bindToLifecycle()) .subscribe(); 或者 myObservable .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY)) .subscribe();
關于RxLifecycle的詳細使用方法可以參考 RxLifecycle官網
七.如何使用封裝
前面幾節內容講解了如何RxJava進行二次封裝,封裝部分的代碼可以放在我們項目的Library模塊中。那么封裝好之后我們應該如何在app模塊中使用呢?
1.定義一個接口來存放我們項目的API
public interface IdeaApiService { /** * 此接口服務器響應數據BasicResponse的泛型T應該是List<MeiZi> * 即BasicResponse<List<MeiZi>> * @return BasicResponse<List<MeiZi>> */ @Headers("Cache-Control: public, max-age=10")//設置緩存 緩存時間為100s @GET("福利/10/1") Observable<List<MeiZi>> getMezi(); /** * 登錄 接口為假接口 并不能返回數據 * @return */ @POST("login.do") Observable<LoginResponse> login(@Body LoginRequest request); /** * 刷新token 接口為假接口 并不能返回數據 * @return */ @POST("refresh_token.do") Observable<RefreshTokenResponseBean> refreshToken(@Body RefreshTokenRequest request); @Multipart @POST("upload/uploadFile.do") Observable<BasicResponse> uploadFiles(@Part List<MultipartBody.Part> partList); }
2.定義一個RetrofitHelper 類,通過IdeaApi來獲取IdeaApiService的實例。
public class RetrofitHelper { private static IdeaApiService mIdeaApiService; public static IdeaApiService getApiService(){ return mIdeaApiService; } static { mIdeaApiService= IdeaApi.getApiService(IdeaApiService.class, Constants.API_SERVER_URL); } }
3.在Activity或者Fragment中發起網絡請求
/** * Get請求 * @param view */ public void getData(View view) { RetrofitHelper.getApiService() .getMezi() .compose(this.<List<MeiZi>>bindToLifecycle()) .compose(ProgressUtils.<List<MeiZi>>applyProgressBar(this)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new DefaultObserver<List<MeiZi>>() { @Override public void onSuccess(List<MeiZi> response) { showToast("請求成功,妹子個數為" + response.size()); } }); }
八.小結
本篇文章主要講解了Rxjava和Retrofit的二次封裝。以上內容也是筆者參考多方面的資料經過長時間的改動優化而來。但鑒于本人能力有限,其中也避免不了出現不當之處。還請大家多多包涵。另外,在投稿郭神公眾號時文章可能還存在很多處理不優雅的地方,比如對響應數據的處理以及對Loading的處理。在投稿被推送后收到了很多小伙伴的建議,因此筆者也參考了大家的意見并做了優化,在此感謝大家。最后如果有疑問歡迎在文章留言評論。