1、寫在前面php
相信各位看官對retrofit和rxjava已經耳熟能詳了,最近一直在學習retrofit+rxjava的各類封裝姿式,也結合本身的理解,一步一步的作起來。html
騷年,若是你尚未掌握retrofit和rx兩大框架,那你是真的out了!java
若是你對Rxjava不熟悉,請先看扔物線的給 Android 開發者的 RxJava 詳解,超詳細;react
若是你只是想了解retrofit的簡單使用,你能夠看我另一篇博客(僅僅是簡單使用),android快捷開發之Retrofit網絡加載框架的簡單使用,詳情請查看官網:Retrofit官網。android
2、基本使用git
好了,就很少BB了,直接進入正題。下面是我用retrofit和rxjava步步深刻的封裝方法。github
一、首先依賴,別忘了添加網絡訪問權限。json
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' }
二、先來一個retrofit的簡單訪問請求,這裏採用本人開源的畢業設計【開源畢設】一款精美的家校互動APP分享——愛吖校推 [你關注的,咱們才推](持續開源更新3)附高效動態壓縮Bitmap上的一個api接口作測試,返回數據格式爲:也就是一個code,msg,data的方式。api
三、按照retrofit的官方方式,寫上一個接口,用於定義子目錄和方法網絡
/** * 使用普通的retrofit方式獲取數據 * @return */ @GET("ezSQL/get_user.php") Call<BaseResponse<List<UserModel>>> getUsers();
四、而後直接訪問以下
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()); } });
五、若是用rx的方式爲:
/** * 使用rx+retrofit獲取數據 * <p> * 【subscribeOn和observeOn區別】 * 一、subscribeOn用於切換以前的線程 * 二、observeOn用於切換以後的線程 * 三、observeOn以後,不可再調用subscribeOn切換線程 * <p> * ———————— 下面是來自扔物線的額外總結 ———————————— * 一、下面提到的「操做」包括產生事件、用操做符操做事件以及最終的經過 subscriber 消費事件 * 二、只有第一subscribeOn() 起做用(因此多個 subscribeOn() 毛意義) * 三、這個 subscribeOn() 控制從流程開始的第一個操做,直到遇到第一個 observeOn() * 四、observeOn() 可使用屢次,每一個 observeOn() 將致使一次線程切換(),此次切換開始於此次 observeOn() 的下一個操做 * 五、不管是 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()); } }); }
六、其中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 }
3、開始封裝
能夠看到傳統的retrofit,無論採用callback,仍是rx方式,重複代碼都太多。若是不作一個封裝,那每一次網絡訪問都要作四步操做的話,那必須得折騰死人。若是你不嫌麻煩,那沒事,你能夠下班了,當我沒說。
我相信大多數人都是想作一下封裝的,因此咱們仔細觀察一下上面,的確有不少地方是能夠直接封裝的。好比初始化retrofit,rx的線程切換等等。
別急,咱們一步一步來!
一、首先寫一個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); } }
二、能夠看到有了這個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()); } });
三、能夠看到的確讓整個過程簡單一些了,但彷佛咱們的確常常關注的是返回的數據,也就是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));
4、寫在最後
本次retrofit和rxjava的簡單封裝就到這裏了,雖然講解質量有待提升,不過樓主依賴打算分享給你們,代碼地址:https://github.com/nanchen2251/RetrofitRxUtil
另外樓主近期在開源android各類實用開源控件,但願你們多多支持:https://github.com/nanchen2251
最後的最後,樓主若是空出了足夠的時間,必定會出一款retrofit的封裝框架的,望你們持續關注~