RxJava2+Retrofit2实现网络请求和解析封装
2016-10-02 13:40
447 查看
半年多前写过一篇用Retrofit2请求网络和解析的博客,Retrofit2的简单应用与封装,不过当时其实还是遗留了不少细节问题没有处理,比如如果有公共参数放Header里面怎么处理,请求过程中想显示进度框怎么处理,退出时要退出网络请求怎么处理等等,这两天看了下RxJava,主要是看了这篇文章RxJava详解,虽然并不是说的RxJava2,但原理差不多,讲得非常清楚,觉得有点意思,就想用RxJava来重新改进一下之前的这个封装
首先声明,这篇博客侧重是讲如何使用Retrofit2和RxJava2封装网络请求和解析,并不会重点介绍Retrofit2和RxJava2的知识,如对基础知识不了解,请先查看上面的链接学习,另外,Retrofit和Retrofit2,RxJava和RxJava2还是有一些不同的地方,本篇博客是基于Retrofit2和RxJava2
1. 添加依赖
项目中用到Retrofit2,RxJava2,还用到Jackson,我们首先要在build.gradle中添加相关的依赖
2. 定义Retrofit访问的接口
这里唯一要注意的是方法的返回值,首先如果我们记得的话,在直接使用Retrofit,不使用RxJava时,我们的返回值都是Call,而如果我们想跟RxJava结合,这里的返回值对象就应该为Observable,Observable的泛型这里为BaseEntity<User>,这个在上面的博客中也讲过,这里再说明下
BaseEntity是服务器返回值的通用格式,它由三个部分组成,code表示成功还是失败,0为成功,非0为失败,message是提示内容,而主要的内容都封装在data里面,data为泛型,可以指定为任何内容,我们这个例子中就是一个User对象
3. 对Retrofit2的基本设置
网络请求部分主要使用Retrofit2来实现,我们先看下基础的设置,直接上代码
这里baseUrl就是我们服务器的项目运行的地址,我是在我本机启动了一个Tomcat,192.168.0.107是我本机的IP,8082是Tomcat的端口号,MyWeb是我服务器项目的名称。
接着看,Retrofit2内部是使用OkHttp3的,我们对网络访问的一些设置都可以通过OkHttp来进行,这里首先调用了一个addInterceptor方法,用来增加一个拦截器,而拦截器的内容是给每个网络请求增加了一个通用的Header字段,名为token,值为abc,这在实际项目中是非常常见的,每个请求都通过token来识别是否是有效的请求,防止恶意请求,当然,实际token应该是动态生成的,我这里只是演示如何在Header中添加通用内容,就直接赋值为abc了。
下面接着设置了connectTimeout和readTimeout的超时时间为30秒,实际网络访问中,存在各种异常情况,掉线,不通,时断时续等等,那设置超时时间就非常必要了,肯定不能无限等待,其中connectTimeout是连接超时时间,在指定时间内还没有连接到服务器就会报SocketTimeout异常,而readTimeout是读取超时时间,是连接后在指定时间还没有获取到数据就超时。
设置为OkHttp,我们再来看Retrofit本身的设置,这里baseUrl就是我们上面讲的公用的链接,addConverterFactory是指定使用Jackson来解析Json数据,当然,你也可以使用Gson或者FastJson,不过数据量大的时候,Gson的效率不高,推荐使用Jackson和FastJson,而addCallAdapterFactory,通过这个转换,才能将服务器的返回值从Retrofit默认的Call变为Observable
最后,提供一个方法返回RetrofitService对象,这整个其实就是一个懒汉式单例模式
4. 定义网络请求Activity的公共基类
这里的ProgressDialog是一个简单的进度框,因为有的网络请求可能耗时较长,如果界面不提供任何互动的话,用户会误以为程序卡死,用户体验较差,提供一个进度框就可以解决这个问题。Function是对Observable的一些基础设置,等会再具体看,RETRY_TIMES,顾名思义,就是重试的次数,网络环境较差或出现其它异常情况的时候,我们希望程序可以自动进行重试,最后一个showLoading用来设置是否显示进度框,原则上一般的请求都要显示,所以默认值是true,但也有例外,举个例子,进应用的时候需要检测是否有版本更新,这个操作我们肯定是希望在后台进行而用户感知不到的,如果这里出现一个进度框就会莫名其妙,所以我们提供这样一个设置。
下面我们具体看下这个composeFunction,retry方法就是刚才说到的重试次数,不指定默认为0,subscribeOn用来指定网络请求所在的线程,这里用IO线程,doOnSubscribe是在事件发送前进行的操作,所以我们可以做一些初始化的工作,isNetworkAvailable用来检测网络是否是连接的
这都是套路,不多说,如果网络连接正常,而且showLoading也为true,我们就显示一个进度框,否则提示用户网络连接异常。
observeOn是用来指定Observer操作的线程,也就是我们得到服务器返回的结果后的线程,因为我们需要操作控件,所以只能在UI线程进行。这里多说一句,doOnSubscribe的线程是什么呢?它既不是在subscribeOn指定的线程,更不是在observeOn指定的线程,而是执行subscribe时所在的线程,subscribe我们现在还没用到,后面会看到,本程序subscribe是在主线程执行,所以doOnSubscribe也就是在主线程了,我们这里显示进度框就是要在主线程进行,所以不用特意去指定,如果subscribe不在主线程,那可以在doOnSubscribe后通过subscribeOn指定它所在的线程。
setLoadingFlag方法就是提供一个设置是否显示进度框的途径。
最后的onStop,保险起见,我们判断一下,进度框是否关闭,如果没关闭要关掉,后面我们还会看到,进度框关闭时我们也会取消订阅,防止已经退出后还在处理请求。
5. 封装Observer
上面我们对Observable进行了封装,那现在我们再来封装Observer
onSubscribe提供了一个Disposable参数,我们调用它的dispose方法就可以终止订阅,在dialog的setOnCancelListener中,我们调用它来取消订阅,这样如果用户在请求的过程中觉得等待时间过长,点击返回键关闭进度框或者退出应用时,我们就可以取消订阅而不继续进行处理了。不过这里有一点要注意的是,这个dispose方法并不会影响到服务器端,如果请求已经发送到服务器端,那就算客户端调用了dispose方法,服务器端的代码依然会继续执行,在一些服务端接口涉及插入数据库的操作时,就要特别注意,考虑客户端在dispose方法后,调用服务器端的方法去执行一个类似回滚的操作,否则客户端取消了订阅,服务器端依然会执行完成,这个要根据项目的实际情况来具体对待。
onNext方法,是我们要处理的主要方法,在这之中,我们通过判断返回值中的code,来判断要做的操作,如果code为0,也就是成功,我们就执行onHandleSuccess方法,如果code不为0,也就是失败,我们就执行onHandleError方法,注意,一般情况下,成功是要单独处理,而失败只给用户提示就可以了,所以这里我将onHandleSuccess声明为抽象的,也就是子类必须要实现,而onHandleError不是抽象的,子类可以选择实现或就用默认的实现即可。
最后,不管是进入了onComplete还是onError方法,都要记得关闭进度框
6. 定义调用网络请求的Activity
这里功能很简单,就是假设传了2个参数,id和name,完后接收服务器返回的数据,显示姓名和年龄。可以看到,经过我们前面几步的封装,Activity的实现已经非常干净了,短短几行代码就实现了网络的请求和返回数据的解析
7.服务器端的实现
整个封装过程就是这样,还是比较基础的,当然,不管是Retrofit还是RxJava,能做的工作远远不止这些,我个人了解的也比较少和浅,大家可以自己继续深入学习,根据项目的实际需要来不断优化自己的框架。
源码下载
首先声明,这篇博客侧重是讲如何使用Retrofit2和RxJava2封装网络请求和解析,并不会重点介绍Retrofit2和RxJava2的知识,如对基础知识不了解,请先查看上面的链接学习,另外,Retrofit和Retrofit2,RxJava和RxJava2还是有一些不同的地方,本篇博客是基于Retrofit2和RxJava2
1. 添加依赖
项目中用到Retrofit2,RxJava2,还用到Jackson,我们首先要在build.gradle中添加相关的依赖
compile 'com.squareup.retrofit2:retrofit:2.0.0' compile 'com.squareup.retrofit2:converter-jackson:2.0.0' compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0-RC3' compile 'io.reactivex.rxjava2:rxjava:2.0.0-RC3' compile 'io.reactivex.rxjava2:rxandroid:2.0.0-RC1'
2. 定义Retrofit访问的接口
public interface RetrofitService { @FormUrlEncoded @POST("getUser") Observable<BaseEntity<User>> getUser(@FieldMap Map<String, String> map); }指定使用POST方式,访问服务器方法名为getUser,参数@FieldMap可以用来传递多个参数,当然如果参数较少,也可以直接使用@Field
这里唯一要注意的是方法的返回值,首先如果我们记得的话,在直接使用Retrofit,不使用RxJava时,我们的返回值都是Call,而如果我们想跟RxJava结合,这里的返回值对象就应该为Observable,Observable的泛型这里为BaseEntity<User>,这个在上面的博客中也讲过,这里再说明下
public class BaseEntity<E> implements Serializable { private int code; private String message; private E data; public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public E getData() { return data; } public void setData(E data) { this.data = data; } }
BaseEntity是服务器返回值的通用格式,它由三个部分组成,code表示成功还是失败,0为成功,非0为失败,message是提示内容,而主要的内容都封装在data里面,data为泛型,可以指定为任何内容,我们这个例子中就是一个User对象
public class User implements Serializable{ private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }就是一个简单的实体类,这个根据实际情况大家可以随意定义
3. 对Retrofit2的基本设置
网络请求部分主要使用Retrofit2来实现,我们先看下基础的设置,直接上代码
public class RetroFactory { private static String baseUrl = "http://192.168.0.107:8082/MyWeb/"; private RetroFactory() { } private static OkHttpClient httpClient = new OkHttpClient.Builder() .addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request.Builder builder = chain.request().newBuilder(); builder.addHeader("token", "abc"); return chain.proceed(builder.build()); } }).connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build(); private static RetrofitService retrofitService = new Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(JacksonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(httpClient) .build() .create(RetrofitService.class); public static RetrofitService getInstance() { return retrofitService; } }
这里baseUrl就是我们服务器的项目运行的地址,我是在我本机启动了一个Tomcat,192.168.0.107是我本机的IP,8082是Tomcat的端口号,MyWeb是我服务器项目的名称。
接着看,Retrofit2内部是使用OkHttp3的,我们对网络访问的一些设置都可以通过OkHttp来进行,这里首先调用了一个addInterceptor方法,用来增加一个拦截器,而拦截器的内容是给每个网络请求增加了一个通用的Header字段,名为token,值为abc,这在实际项目中是非常常见的,每个请求都通过token来识别是否是有效的请求,防止恶意请求,当然,实际token应该是动态生成的,我这里只是演示如何在Header中添加通用内容,就直接赋值为abc了。
下面接着设置了connectTimeout和readTimeout的超时时间为30秒,实际网络访问中,存在各种异常情况,掉线,不通,时断时续等等,那设置超时时间就非常必要了,肯定不能无限等待,其中connectTimeout是连接超时时间,在指定时间内还没有连接到服务器就会报SocketTimeout异常,而readTimeout是读取超时时间,是连接后在指定时间还没有获取到数据就超时。
设置为OkHttp,我们再来看Retrofit本身的设置,这里baseUrl就是我们上面讲的公用的链接,addConverterFactory是指定使用Jackson来解析Json数据,当然,你也可以使用Gson或者FastJson,不过数据量大的时候,Gson的效率不高,推荐使用Jackson和FastJson,而addCallAdapterFactory,通过这个转换,才能将服务器的返回值从Retrofit默认的Call变为Observable
最后,提供一个方法返回RetrofitService对象,这整个其实就是一个懒汉式单例模式
4. 定义网络请求Activity的公共基类
public class NetworkBaseActivity extends AppCompatActivity { public ProgressDialog pd; public Function<Observable, ObservableSource> composeFunction; private final long RETRY_TIMES = 1; private boolean showLoading = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); init(); } private void init() { pd = new ProgressDialog(this); composeFunction = new Function<Observable, ObservableSource>() { @Override public ObservableSource apply(Observable observable) throws Exception { return observable.retry(RETRY_TIMES) .subscribeOn(Schedulers.io()) .doOnSubscribe(new Consumer<Disposable>() { @Override public void accept(Disposable disposable) throws Exception { if (NetworkUtil.isNetworkAvailable(NetworkBaseActivity.this)) { if (showLoading) { if(pd != null && !pd.isShowing()){ pd.show(); } } } else { Toast.makeText(NetworkBaseActivity.this, "网络连接异常,请检查网络", Toast.LENGTH_LONG).show(); } } }) .observeOn(AndroidSchedulers.mainThread()); } }; } public void setLoadingFlag(boolean show) { showLoading = show; } @Override protected void onStop() { super.onStop(); if (pd != null && pd.isShowing()) { pd.dismiss(); } } }
这里的ProgressDialog是一个简单的进度框,因为有的网络请求可能耗时较长,如果界面不提供任何互动的话,用户会误以为程序卡死,用户体验较差,提供一个进度框就可以解决这个问题。Function是对Observable的一些基础设置,等会再具体看,RETRY_TIMES,顾名思义,就是重试的次数,网络环境较差或出现其它异常情况的时候,我们希望程序可以自动进行重试,最后一个showLoading用来设置是否显示进度框,原则上一般的请求都要显示,所以默认值是true,但也有例外,举个例子,进应用的时候需要检测是否有版本更新,这个操作我们肯定是希望在后台进行而用户感知不到的,如果这里出现一个进度框就会莫名其妙,所以我们提供这样一个设置。
下面我们具体看下这个composeFunction,retry方法就是刚才说到的重试次数,不指定默认为0,subscribeOn用来指定网络请求所在的线程,这里用IO线程,doOnSubscribe是在事件发送前进行的操作,所以我们可以做一些初始化的工作,isNetworkAvailable用来检测网络是否是连接的
public class NetworkUtil { public static boolean isNetworkAvailable(Activity activity) { Context context = activity.getApplicationContext(); ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (connectivityManager == null) { return false; } else { NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo(); if (networkInfo != null && networkInfo.length > 0) { for (int i = 0; i < networkInfo.length; i++) { if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) { return true; } } } } return false; } }
这都是套路,不多说,如果网络连接正常,而且showLoading也为true,我们就显示一个进度框,否则提示用户网络连接异常。
observeOn是用来指定Observer操作的线程,也就是我们得到服务器返回的结果后的线程,因为我们需要操作控件,所以只能在UI线程进行。这里多说一句,doOnSubscribe的线程是什么呢?它既不是在subscribeOn指定的线程,更不是在observeOn指定的线程,而是执行subscribe时所在的线程,subscribe我们现在还没用到,后面会看到,本程序subscribe是在主线程执行,所以doOnSubscribe也就是在主线程了,我们这里显示进度框就是要在主线程进行,所以不用特意去指定,如果subscribe不在主线程,那可以在doOnSubscribe后通过subscribeOn指定它所在的线程。
setLoadingFlag方法就是提供一个设置是否显示进度框的途径。
最后的onStop,保险起见,我们判断一下,进度框是否关闭,如果没关闭要关掉,后面我们还会看到,进度框关闭时我们也会取消订阅,防止已经退出后还在处理请求。
5. 封装Observer
上面我们对Observable进行了封装,那现在我们再来封装Observer
public abstract class BaseObserver<T> implements Observer<BaseEntity<T>> { private Context mContext; private ProgressDialog mDialog; private Disposable mDisposable; private final int SUCCESS_CODE = 0; public BaseObserver(Context context, ProgressDialog dialog) { mContext = context; mDialog = dialog; mDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { mDisposable.dispose(); } }); } @Override public void onSubscribe(Disposable d) { mDisposable = d; } @Override public void onNext(BaseEntity<T> value) { if (value.getCode() == SUCCESS_CODE) { T t = value.getData(); onHandleSuccess(t); } else { onHandleError(value.getCode(), value.getMessage()); } } @Override public void onError(Throwable e) { Log.d("gesanri", "error:" + e.toString()); if(mDialog != null && mDialog.isShowing()){ mDialog.dismiss(); } Toast.makeText(mContext, "网络异常,请稍后再试", Toast.LENGTH_LONG).show(); } @Override public void onComplete() { Log.d("gesanri", "onComplete"); if(mDialog != null && mDialog.isShowing()){ mDialog.dismiss(); } } abstract void onHandleSuccess(T t); void onHandleError(int code, String message) { Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); } }Observer是一个接口,它提供了4个方法,onSubscribe用来随时取消和Observable的连接,onNext用来处理Observable的返回,也就是网络连接的返回,onComplete在onNext后被调用,表示完成,onError表示发生了错误,onComplete和onError两个方法中只会并且肯定会有一个方法被调用。
onSubscribe提供了一个Disposable参数,我们调用它的dispose方法就可以终止订阅,在dialog的setOnCancelListener中,我们调用它来取消订阅,这样如果用户在请求的过程中觉得等待时间过长,点击返回键关闭进度框或者退出应用时,我们就可以取消订阅而不继续进行处理了。不过这里有一点要注意的是,这个dispose方法并不会影响到服务器端,如果请求已经发送到服务器端,那就算客户端调用了dispose方法,服务器端的代码依然会继续执行,在一些服务端接口涉及插入数据库的操作时,就要特别注意,考虑客户端在dispose方法后,调用服务器端的方法去执行一个类似回滚的操作,否则客户端取消了订阅,服务器端依然会执行完成,这个要根据项目的实际情况来具体对待。
onNext方法,是我们要处理的主要方法,在这之中,我们通过判断返回值中的code,来判断要做的操作,如果code为0,也就是成功,我们就执行onHandleSuccess方法,如果code不为0,也就是失败,我们就执行onHandleError方法,注意,一般情况下,成功是要单独处理,而失败只给用户提示就可以了,所以这里我将onHandleSuccess声明为抽象的,也就是子类必须要实现,而onHandleError不是抽象的,子类可以选择实现或就用默认的实现即可。
最后,不管是进入了onComplete还是onError方法,都要记得关闭进度框
6. 定义调用网络请求的Activity
public class MainActivity extends NetworkBaseActivity { private TextView name; private TextView age; private Observable observable; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); name = (TextView) findViewById(R.id.name); age = (TextView) findViewById(R.id.age); getUsers(); } private void getUsers() { Map<String, String> map = new HashMap<String, String>(); map.put("id", "123"); map.put("name", "gesanri"); observable = RetroFactory.getInstance().getUser(map); observable.compose(composeFunction).subscribe(new BaseObserver<User>(MainActivity.this, pd) { @Override void onHandleSuccess(User user) { name.setText("姓名:" + user.getName()); age.setText("年龄:" + user.getAge()); } }); } }这个类继承了我们之前第4步中定义的公共基类,布局文件也很简单
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/age" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
这里功能很简单,就是假设传了2个参数,id和name,完后接收服务器返回的数据,显示姓名和年龄。可以看到,经过我们前面几步的封装,Activity的实现已经非常干净了,短短几行代码就实现了网络的请求和返回数据的解析
7.服务器端的实现
@WebServlet(name = "getUser", value = "/getUser") public class GetUserServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("token:" + req.getHeader("token")); System.out.println("id:" + req.getParameter("id")); System.out.println("name:" + req.getParameter("name")); resp.setCharacterEncoding("utf-8"); if (req.getHeader("token").equals("abc")) { try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } resp.getWriter() .write("{\"code\":0, \"message\":\"获取用户成功!\", \"data\":{\"name\":\"张三\", \"age\":23}}"); } else { resp.getWriter() .write("{\"code\":1, \"message\":\"获取用户失败!\", \"data\":\"\"}"); } } }最后我们简单看下服务器端的实现,我们接收了三个参数,一个是Header里面的Token, 这个是每个请求都有的,另外两个是这个getUser请求特定的参数,我们判断如果token不为abc,就返回错误,如果是abc,就返回成功,实际项目肯定是要从数据库获取,这里主要是演示客户端,就偷懒直接返回数据了。这里为了在客户端演示进度框,就休眠了5秒。
整个封装过程就是这样,还是比较基础的,当然,不管是Retrofit还是RxJava,能做的工作远远不止这些,我个人了解的也比较少和浅,大家可以自己继续深入学习,根据项目的实际需要来不断优化自己的框架。
源码下载
相关文章推荐
- 使用retrofit2和rxjava封装的网络框架RNet:(二)RNet的源码解析
- retrofit+RxJava+okhttp简便封装实现网络请求(详解)
- Android项目开发全程(三)-- 项目的前期搭建、网络请求封装是怎样实现的
- 同步网络请求 类封装,包括get请求和post请求,可选择是否进行JSON解析
- RxJava+Retrofit+OkHttp深入浅出-终极封装二(网络请求)
- Retrofit+okhttp3的简单封装实现网络请求和拦截
- RxJava+Retrofit+OkHttp深入浅出-终极封装二(网络请求)
- iOS 自己封装的网络请求,json解析的类
- JS 和 ajax 实现网络请求 和 对应的类封装 回调函数实现
- Android通用网络请求解析框架.3(代码实现,公共部分)
- Retrofit+Rxjava 网络请求的简单封装(一)(观察者模式)
- 使用MVP+Retrofit+RxJava实现的的Android Demo (下)使用Retrofit+RxJava处理网络请求
- iOS项目中网络层实现自动转为对象的网络请求工具封装
- Android通用网络请求解析框架.4(代码实现,分支部分)
- iOS 自己封装的网络请求,json解析的类
- Volley二次封装,实现网络请求缓存
- iOS 自己封装的网络请求,json解析的类
- RxJava+Retrofit+OkHttp深入浅出-终极封装二(网络请求)
- Retrofit和RxJava网络请求二次封装
- Android网络请求框架----Okhttp3完全解析(2),封装框架