一、写在前面
相信各位看官对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>> 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>>() { @Override public void onResponse(Call >> call, Response >> 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 >> call, Throwable t) { showToast("失败:" + t.getMessage()); Log.e(TAG, "失败:" + t.getMessage()); mTextView.setText("失败:" + t.getMessage()); } });
5、如果用rx的方式为:
/** * 使用rx+retrofit获取数据 ** 【subscribeOn和observeOn区别】 * 1、subscribeOn用于切换之前的线程 * 2、observeOn用于切换之后的线程 * 3、observeOn之后,不可再调用subscribeOn切换线程 *
* ———————— 下面是来自扔物线的额外总结 ———————————— * 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>> observable = apiService.getUsersByRx(); observable .subscribeOn(Schedulers.io()) // 网络请求切换在io线程中调用 .unsubscribeOn(Schedulers.io())// 取消网络请求放在io线程 .observeOn(AndroidSchedulers.mainThread())// 观察后放在主线程调用 .subscribe(new Subscriber >>() { @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 > 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 BaseResponseimplements 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>> subscriber){ mApiService.getUsersByRx() .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(subscriber); } }
2、可以看到有了这个Util,那我们在Activity中调用就简单了。
RetrofitUtil.getInstance().getUsers(new Subscriber>>() { @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 > 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{ 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 ProgressSubscriberextends Subscriber implements ProgressCancelListener{ 20 21 private SubscriberOnNextListener mListener; 22 private Context mContext; 23 private ProgressDialogHandler mHandler; 24 25 public ProgressSubscriber(SubscriberOnNextListener 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 ProgressSubscriberextends Subscriber implements ProgressCancelListener{ 20 21 private SubscriberOnNextListener mListener; 22 private Context mContext; 23 private ProgressDialogHandler mHandler; 24 25 public ProgressSubscriber(SubscriberOnNextListener 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>>() { @Override public void onNext(TngouResponse > listTngouResponse) { mTextView.setText(listTngouResponse.tngou.toString()); showToast(listTngouResponse.tngou.toString()); } };
RetrofitUtil.getInstance().getCookList(2,5,new ProgressSubscriber>>(mListener,this));
四、写在最后
本次retrofit和rxjava的简单封装就到这里了,虽然讲解质量有待提高,不过楼主依赖打算分享给大家,代码地址:https://github.com/nanchen2251/RetrofitRxUtil
另外楼主近期在开源android各种实用开源控件,希望大家多多支持:https://github.com/nanchen2251
最后的最后,楼主如果空出了足够的时间,一定会出一款retrofit的封装框架的,望大家持续关注~