一、寫在前面
相信各位看官對retrofit和rxjava已經耳熟能詳了,最近一直在學習retrofit+rxjava的各種封裝姿勢,也結合自己的理解,一步一步的做起來。
騷年,如果你還沒有掌握retrofit和rx兩大框架,那你是真的out了!
如果你對Rxjava不熟悉,請先看扔物線的給 Android 開發者的 RxJava 詳解,超詳細;
如果你只是想了解retrofit的簡單使用,你可以看我另外一篇博客(僅僅是簡單使用),android快捷開發之Retrofit網絡加載框架的簡單使用,詳情請查看官網:Retrofit官網。
二、基本使用
好了,就不多BB了,直接進入正題。下面是我用retrofit和rxjava步步深入的封裝方法。
1、首先依賴,別忘了添加網絡訪問權限。
dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.0.1' testCompile 'junit:junit:4.12' compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'io.reactivex:rxandroid:1.2.1' // Because RxAndroid releases are few and far between, it is recommended you also // explicitly depend on RxJava's latest version for bug fixes and new features. compile 'io.reactivex:rxjava:1.1.6' compile 'com.google.code.gson:gson:2.8.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0' compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' }
2、先來一個retrofit的簡單訪問請求,這里采用本人開源的畢業設計【開源畢設】一款精美的家校互動APP分享——愛吖校推 [你關注的,我們才推](持續開源更新3)附高效動態壓縮Bitmap上的一個api接口做測試,返回數據格式為:也就是一個code,msg,data的方式。
3、按照retrofit的官方方式,寫上一個接口,用於定義子目錄和方法
/** * 使用普通的retrofit方式獲取數據 * @return */ @GET("ezSQL/get_user.php") Call<BaseResponse<List<UserModel>>> getUsers();
4、然后直接訪問如下
Retrofit retrofit = new Retrofit.Builder() .baseUrl(Consts.APP_HOST) .addConverterFactory(GsonConverterFactory.create()) .build(); ApiService apiService = retrofit.create(ApiService.class); apiService.getUsers().enqueue(new Callback<BaseResponse<List<UserModel>>>() { @Override public void onResponse(Call<BaseResponse<List<UserModel>>> call, Response<BaseResponse<List<UserModel>>> response) { showToast("成功:" + response.body().data.toString()); Log.e(TAG, "成功:" + response.body().data.toString()); Log.e(TAG, "retCode:" + response.code() + ",msg:" + response.message()); mTextView.setText("成功:" + response.body().data.toString()); } @Override public void onFailure(Call<BaseResponse<List<UserModel>>> call, Throwable t) { showToast("失敗:" + t.getMessage()); Log.e(TAG, "失敗:" + t.getMessage()); mTextView.setText("失敗:" + t.getMessage()); } });
5、如果用rx的方式為:
/** * 使用rx+retrofit獲取數據 * <p> * 【subscribeOn和observeOn區別】 * 1、subscribeOn用於切換之前的線程 * 2、observeOn用於切換之后的線程 * 3、observeOn之后,不可再調用subscribeOn切換線程 * <p> * ———————— 下面是來自扔物線的額外總結 ———————————— * 1、下面提到的“操作”包括產生事件、用操作符操作事件以及最終的通過 subscriber 消費事件 * 2、只有第一subscribeOn() 起作用(所以多個 subscribeOn() 毛意義) * 3、這個 subscribeOn() 控制從流程開始的第一個操作,直到遇到第一個 observeOn() * 4、observeOn() 可以使用多次,每個 observeOn() 將導致一次線程切換(),這次切換開始於這次 observeOn() 的下一個操作 * 5、不論是 subscribeOn() 還是 observeOn(),每次線程切換如果不受到下一個 observeOn() 的干預,線程將不再改變,不會自動切換到其他線程 * * @param view */ public void btnClick1(View view) { Retrofit retrofit = new Retrofit.Builder() .baseUrl(Consts.APP_HOST) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); ApiService apiService = retrofit.create(ApiService.class); Observable<BaseResponse<List<UserModel>>> observable = apiService.getUsersByRx(); observable .subscribeOn(Schedulers.io()) // 網絡請求切換在io線程中調用 .unsubscribeOn(Schedulers.io())// 取消網絡請求放在io線程 .observeOn(AndroidSchedulers.mainThread())// 觀察后放在主線程調用 .subscribe(new Subscriber<BaseResponse<List<UserModel>>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { showToast("rx失敗:" + e.getMessage()); Log.e(TAG, "rx失敗:" + e.getMessage()); mTextView.setText("rx失敗:" + e.getMessage()); } @Override public void onNext(BaseResponse<List<UserModel>> listBaseResponse) { showToast("rx成功:" + listBaseResponse.data.toString()); mTextView.setText("rx成功:" + listBaseResponse.data.toString()); Log.e(TAG, "rx成功:" + listBaseResponse.data.toString()); } }); }
6、其中BaseResponse是我們的統一的返回格式封裝,采用泛型。
1 package com.nanchen.retrofitrxdemoo.model; 2 3 import com.google.gson.annotations.SerializedName; 4 5 import java.io.Serializable; 6 7 /** 8 * 獲取json數據基類 9 * 10 * @author nanchen 11 * @fileName RetrofitRxDemoo 12 * @packageName com.nanchen.retrofitrxdemoo 13 * @date 2016/12/09 17:05 14 */ 15 16 public class BaseResponse<T> implements Serializable{ 17 @SerializedName("code") 18 public int code; 19 @SerializedName("msg") 20 public String msg; 21 @SerializedName("data") 22 public T data; 23 }
1 package com.nanchen.retrofitrxdemoo.model; 2 3 4 import java.io.Serializable; 5 6 /** 7 * @author nanchen 8 * @fileName RetrofitRxDemoo 9 * @packageName com.nanchen.retrofitrxdemoo 10 * @date 2016/12/10 09:05 11 */ 12 13 public class UserModel implements Serializable { 14 public String username; 15 public String password; 16 17 @Override 18 public String toString() { 19 return "UserModel{" + 20 "username='" + username + '\'' + 21 ", password='" + password + '\'' + 22 '}'; 23 } 24 }
三、開始封裝
可以看到傳統的retrofit,不管采用callback,還是rx方式,重復代碼都太多。如果不做一個封裝,那每一次網絡訪問都要做四步操作的話,那必須得折騰死人。如果你不嫌麻煩,那沒事,你可以下班了,當我沒說。
我相信大多數人都是想做一下封裝的,所以我們仔細觀察一下上面,的確有很多地方是可以直接封裝的。比如初始化retrofit,rx的線程切換等等。
別急,我們一步一步來!
1、首先寫一個util類,這里我們采用單例方式;
public class RetrofitUtil { public static final int DEFAULT_TIMEOUT = 5; private Retrofit mRetrofit; private ApiService mApiService; private static RetrofitUtil mInstance; /** * 私有構造方法 */ private RetrofitUtil(){ OkHttpClient.Builder builder = new OkHttpClient.Builder(); builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS); mRetrofit = new Retrofit.Builder() .client(builder.build()) .baseUrl(Consts.APP_HOST) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); mApiService = mRetrofit.create(ApiService.class); } public static RetrofitUtil getInstance(){ if (mInstance == null){ synchronized (RetrofitUtil.class){ mInstance = new RetrofitUtil(); } } return mInstance; } /** * 用於獲取用戶信息 * @param subscriber */ public void getUsers(Subscriber<BaseResponse<List<UserModel>>> subscriber){ mApiService.getUsersByRx() .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(subscriber); } }
2、可以看到有了這個Util,那我們在Activity中調用就簡單了。
RetrofitUtil.getInstance().getUsers(new Subscriber<BaseResponse<List<UserModel>>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { showToast("rx失敗:" + e.getMessage()); Log.e(TAG, "rx失敗:" + e.getMessage()); mTextView.setText("rx失敗:" + e.getMessage()); } @Override public void onNext(BaseResponse<List<UserModel>> listBaseResponse) { showToast("rx成功:" + listBaseResponse.data.toString()); mTextView.setText("rx成功:" + listBaseResponse.data.toString()); Log.e(TAG, "rx成功:" + listBaseResponse.data.toString()); } });
3、可以看到的確讓整個過程簡單一些了,但似乎我們的確經常關注的是返回的數據,也就是onNext方法,而onCompleted和onError兩個方法則調用較少,那么我們再把這兩個方法做統一處理,並且我們一般網絡訪問都會用到加載對話框,加強用戶交互性,遇到error的時候直接顯示message即可。所以我們不妨把對話框顯示也封裝一下。
a) 首先寫一個對話框退出接口,供后面調用
1 package com.nanchen.retrofitrxdemoo; 2 3 /** 4 * @author nanchen 5 * @fileName RetrofitRxDemoo 6 * @packageName com.nanchen.retrofitrxdemoo 7 * @date 2016/12/12 15:00 8 */ 9 10 public interface ProgressCancelListener { 11 void onCancelProgress(); 12 }
b) 我們這里采用Handler的方式顯示和隱藏加載對話框
1 package com.nanchen.retrofitrxdemoo; 2 3 import android.app.ProgressDialog; 4 import android.content.Context; 5 import android.content.DialogInterface; 6 import android.os.Handler; 7 import android.os.Message; 8 9 /** 10 * @author nanchen 11 * @fileName RetrofitRxDemoo 12 * @packageName com.nanchen.retrofitrxdemoo 13 * @date 2016/12/12 15:02 14 */ 15 16 public class ProgressDialogHandler extends Handler { 17 public static final int SHOW_PROGRESS_DIALOG = 1; 18 public static final int DISMISS_PROGRESS_DIALOG = 2; 19 20 private ProgressDialog pd; 21 22 private Context context; 23 private boolean cancelable; 24 private ProgressCancelListener mProgressCancelListener; 25 26 public ProgressDialogHandler(Context context, ProgressCancelListener mProgressCancelListener, 27 boolean cancelable) { 28 super(); 29 this.context = context; 30 this.mProgressCancelListener = mProgressCancelListener; 31 this.cancelable = cancelable; 32 } 33 34 private void initProgressDialog(){ 35 if (pd == null) { 36 pd = new ProgressDialog(context); 37 38 pd.setCancelable(cancelable); 39 40 if (cancelable) { 41 pd.setOnCancelListener(new DialogInterface.OnCancelListener() { 42 @Override 43 public void onCancel(DialogInterface dialogInterface) { 44 mProgressCancelListener.onCancelProgress(); 45 } 46 }); 47 } 48 49 if (!pd.isShowing()) { 50 pd.show(); 51 } 52 } 53 } 54 55 private void dismissProgressDialog(){ 56 if (pd != null) { 57 pd.dismiss(); 58 pd = null; 59 } 60 } 61 62 @Override 63 public void handleMessage(Message msg) { 64 switch (msg.what) { 65 case SHOW_PROGRESS_DIALOG: 66 initProgressDialog(); 67 break; 68 case DISMISS_PROGRESS_DIALOG: 69 dismissProgressDialog(); 70 break; 71 } 72 } 73 }
c) 既然我們只關注onNext數據,所以把它提取出來,做成一個接口,以便於我們在Activity或者fragment中對數據進行操作,由於我們數據類型未知,所以這里也傳入一個泛型。
1 package com.nanchen.retrofitrxdemoo; 2 3 /** 4 * @author nanchen 5 * @fileName RetrofitRxDemoo 6 * @packageName com.nanchen.retrofitrxdemoo 7 * @date 2016/12/12 14:48 8 */ 9 10 public interface SubscriberOnNextListener<T> { 11 void onNext(T t); 12 }
d) 來到重頭戲,我們為Subscriber寫一個子類,讓其實現對話框的退出方法。因為Subscriber比Observer(正常情況下都會被轉換為Subscriber,詳情請看源代碼)會多一個onStart方法,我們可以在onStart中調用對話框顯示,在onComplete方法中調用對話框的隱藏方法。
1 package com.nanchen.retrofitrxdemoo; 2 3 import android.content.Context; 4 import android.widget.Toast; 5 6 import java.net.ConnectException; 7 import java.net.SocketTimeoutException; 8 9 import rx.Subscriber; 10 11 12 /** 13 * @author nanchen 14 * @fileName RetrofitRxDemoo 15 * @packageName com.nanchen.retrofitrxdemoo 16 * @date 2016/12/12 14:48 17 */ 18 19 public class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener{ 20 21 private SubscriberOnNextListener<T> mListener; 22 private Context mContext; 23 private ProgressDialogHandler mHandler; 24 25 public ProgressSubscriber(SubscriberOnNextListener<T> listener, Context context){ 26 this.mListener = listener; 27 this.mContext = context; 28 mHandler = new ProgressDialogHandler(context,this,true); 29 } 30 31 private void showProgressDialog(){ 32 if (mHandler != null) { 33 mHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget(); 34 } 35 } 36 37 private void dismissProgressDialog(){ 38 if (mHandler != null) { 39 mHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget(); 40 mHandler = null; 41 } 42 } 43 44 45 /** 46 * 訂閱開始時調用 47 * 顯示ProgressDialog 48 */ 49 @Override 50 public void onStart() { 51 super.onStart(); 52 showProgressDialog(); 53 } 54 55 @Override 56 public void onCompleted() { 57 dismissProgressDialog(); 58 Toast.makeText(DemoApplication.getAppContext(),"獲取數據完成!",Toast.LENGTH_SHORT).show(); 59 } 60 61 @Override 62 public void onError(Throwable e) { 63 if (e instanceof SocketTimeoutException) { 64 Toast.makeText(DemoApplication.getAppContext(), "網絡中斷,請檢查您的網絡狀態", Toast.LENGTH_SHORT).show(); 65 } else if (e instanceof ConnectException) { 66 Toast.makeText(DemoApplication.getAppContext(), "網絡中斷,請檢查您的網絡狀態", Toast.LENGTH_SHORT).show(); 67 } else { 68 Toast.makeText(DemoApplication.getAppContext(), "error:" + e.getMessage(), Toast.LENGTH_SHORT).show(); 69 } 70 dismissProgressDialog(); 71 } 72 73 @Override 74 public void onNext(T t) { 75 if (mListener != null){ 76 mListener.onNext(t); 77 } 78 } 79 80 @Override 81 public void onCancelProgress() { 82 if (!this.isUnsubscribed()){ 83 this.unsubscribe(); 84 } 85 } 86 }
e) 為了讓大家可以測試代碼,所以我這里把api接口換為天狗網的健康菜譜。返回格式為
f) 由於這個返回格式和我的畢設的返回格式不一樣,所以,我們重寫一個TngouResponse類,這里就不貼代碼了。
g) 我們仔細觀察上面的封裝Util,發現我們的線程切換其實也是可以封裝成一個單獨的方法的,這樣又可以降低代碼的耦合了。所以完整的ProgressSubscriber類為:
1 package com.nanchen.retrofitrxdemoo; 2 3 import android.content.Context; 4 import android.widget.Toast; 5 6 import java.net.ConnectException; 7 import java.net.SocketTimeoutException; 8 9 import rx.Subscriber; 10 11 12 /** 13 * @author nanchen 14 * @fileName RetrofitRxDemoo 15 * @packageName com.nanchen.retrofitrxdemoo 16 * @date 2016/12/12 14:48 17 */ 18 19 public class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener{ 20 21 private SubscriberOnNextListener<T> mListener; 22 private Context mContext; 23 private ProgressDialogHandler mHandler; 24 25 public ProgressSubscriber(SubscriberOnNextListener<T> listener, Context context){ 26 this.mListener = listener; 27 this.mContext = context; 28 mHandler = new ProgressDialogHandler(context,this,true); 29 } 30 31 private void showProgressDialog(){ 32 if (mHandler != null) { 33 mHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget(); 34 } 35 } 36 37 private void dismissProgressDialog(){ 38 if (mHandler != null) { 39 mHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget(); 40 mHandler = null; 41 } 42 } 43 44 45 /** 46 * 訂閱開始時調用 47 * 顯示ProgressDialog 48 */ 49 @Override 50 public void onStart() { 51 super.onStart(); 52 showProgressDialog(); 53 } 54 55 @Override 56 public void onCompleted() { 57 dismissProgressDialog(); 58 Toast.makeText(DemoApplication.getAppContext(),"獲取數據完成!",Toast.LENGTH_SHORT).show(); 59 } 60 61 @Override 62 public void onError(Throwable e) { 63 if (e instanceof SocketTimeoutException) { 64 Toast.makeText(DemoApplication.getAppContext(), "網絡中斷,請檢查您的網絡狀態", Toast.LENGTH_SHORT).show(); 65 } else if (e instanceof ConnectException) { 66 Toast.makeText(DemoApplication.getAppContext(), "網絡中斷,請檢查您的網絡狀態", Toast.LENGTH_SHORT).show(); 67 } else { 68 Toast.makeText(DemoApplication.getAppContext(), "error:" + e.getMessage(), Toast.LENGTH_SHORT).show(); 69 } 70 dismissProgressDialog(); 71 } 72 73 @Override 74 public void onNext(T t) { 75 if (mListener != null){ 76 mListener.onNext(t); 77 } 78 } 79 80 @Override 81 public void onCancelProgress() { 82 if (!this.isUnsubscribed()){ 83 this.unsubscribe(); 84 } 85 } 86 }
h) 現在,我們發現在activity或fragment中調用網絡數據其實就是這么簡單!
SubscriberOnNextListener mListener = new SubscriberOnNextListener<TngouResponse<List<Cook>>>() { @Override public void onNext(TngouResponse<List<Cook>> listTngouResponse) { mTextView.setText(listTngouResponse.tngou.toString()); showToast(listTngouResponse.tngou.toString()); } };
RetrofitUtil.getInstance().getCookList(2,5,new ProgressSubscriber<TngouResponse<List<Cook>>>(mListener,this));
四、寫在最后
本次retrofit和rxjava的簡單封裝就到這里了,雖然講解質量有待提高,不過樓主依賴打算分享給大家,代碼地址:https://github.com/nanchen2251/RetrofitRxUtil
另外樓主近期在開源android各種實用開源控件,希望大家多多支持:https://github.com/nanchen2251
最后的最后,樓主如果空出了足夠的時間,一定會出一款retrofit的封裝框架的,望大家持續關注~