关于Retrofit和Rxjava的教程往上已经有很多,但大多都是1.x版本,很多方法和类已经改变,而且封装也说的不太多.这里我做了一个简单的封装,可以说一句话代码完成封装也不错.如果你有时间,那么下面的文章会帮助你理解工具类代码的作用.如果你没有时间那么请直接滑到最后下载demo,参照我写的例子直接用到项目里吧.
封装的功能:请求显示进度框; 对返回参数统一处理 cookie保持 缓存策略 公共参数 日志打印
首先说下retrofit这个库,它其实是对okhttp的再次封装,单用okhttp其实也能完成网络请求,但是会比较麻烦.而使用retrofit代码就会十分简化.首先引入类库
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
这是当前的最新版,如果单独使用它的话返回的结果是Call,和Rxjava没有关系,所以要引入rxjava支持
implementation 'com.squareup.retrofit2:converter-gson:2.3.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' compile "io.reactivex.rxjava2:rxjava:2.x.y" compile 'io.reactivex.rxjava2:rxandroid:2.0.0'
因为涉及到线程切换,需要再安卓里切换ui线程,所以要引入rxandroid
最后是okhttp的一些工具库:日志打印等
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1' compile 'com.squareup.okhttp3:okhttp-urlconnection:3.4.1'
首先是创建ApiService,
public interface ApiService { //post请求 @POST("login/index") @FormUrlEncoded Flowable<Login> postLogin(@Field("account") String username, @Field("pwd") String pwd, @Field("accessPort") int prot, @Field("version") String version); //get请求 @GET("appcms/cmsunlogin") Flowable<CmsEntity> getInfo(); }
基本上除了参数名称和方法名称其他都是固定写法,如果你对retrofit完全不了解,那么可以看看其他简单的教程,这里不多做介绍.请求方法写完之后,开始构建retrofit实例,类似这样
retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create(getGsonInstance())) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(getOkHttpInstance()) .baseUrl("http://app.ddshenbian.com/").build();
在官方的示例代码中每次请求都要获取一次实例,这意味着每次都要写出这么多重复的代码,不用多说,直接用单利模式放到util里面.变成这样
public static ApiService getApiService() { if (retrofit == null) { retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create(getGsonInstance())) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(getOkHttpInstance()) .baseUrl("http://app.ddshenbian.com/").build(); } return retrofit.create(ApiService.class); }
addConverterFactory是添加gson转换支持,如果你不是用json格式,那么请换别的转换器,retrofit官网有支持
addCallAdapterFactory是今天的主角,添加rxjava2支持,他将原来的返回值Call变成了Flowable,也就是被观察者,完美衔接rxjava
client 是添加okhttp,不添加也行,之所以添加是为了对okhttp进行一些列的设置.如下:
/** * 实例化okhttp,log打印,添加公共参数,cookie保持,缓存策略,超时和重连 * * @return */ public static OkHttpClient getOkHttpInstance() { if (client == null) { client = new OkHttpClient.Builder(); /** * log信息拦截器 */ //log信息拦截器 HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(); httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); //设置Debug Log模式 client.addInterceptor(httpLoggingInterceptor); /** * 缓存 */ final File chachefile = new File(AppAplication.getContext().getExternalCacheDir(), "HttpCache"); final Cache cache = new Cache(chachefile, 1024 * 1024 * 50);//缓存文件 Interceptor cacheInterceptor = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); if (!ApkUtils.isNetworkAvailable(AppAplication.getContext())) { request = request.newBuilder() .cacheControl(CacheControl.FORCE_CACHE) .build(); } Response response = chain.proceed(request); if (ApkUtils.isNetworkAvailable(AppAplication.getContext())) { int maxAge = 0; // 有网络时 设置缓存超时时间0个小时 response.newBuilder() .header("Cache-Control", "public, max-age=" + maxAge) .removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效 .build(); } else { //无网络时,设置超时为4周 int maxStale = 60 * 60 * 24 * 28; response.newBuilder() .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) .removeHeader("Pragma") .build(); } return response; } }; // client.cache(cache).addInterceptor(cacheInterceptor); /** * 公共参数 */ Interceptor addQueryParameterInterceptor = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request request; String method = originalRequest.method(); Headers headers = originalRequest.headers(); HttpUrl modifiedUrl = originalRequest.url().newBuilder() // Provide your custom parameter here .addQueryParameter("accessPort", "2") .addQueryParameter("version", "1.0") .build(); request = originalRequest.newBuilder().url(modifiedUrl).build(); return chain.proceed(request); } }; //公共参数 client.addInterceptor(addQueryParameterInterceptor); /** * 设置头 */ Interceptor headerInterceptor = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request orignaRequest = chain.request(); Request request = orignaRequest.newBuilder() .header("AppType", "TPOS") .header("Content-Type", "application/json") .header("Accept", "application/json") .method(orignaRequest.method(), orignaRequest.body()) .build(); return chain.proceed(request); } }; client.addInterceptor(headerInterceptor); /** * 设置cookie */ CookieManager cookieManager = new CookieManager(); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); client.cookieJar(new JavaNetCookieJar(cookieManager)); /** * 设置超时和重连 */ //设置超时 client.connectTimeout(15, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .writeTimeout(20, TimeUnit.SECONDS) //错误重连 .retryOnConnectionFailure(true); } return client.build(); }
上面的代码每一段都有注释,这个就是okhttp的一些设置,跟retrofit没什么关系,不过功能还是很重要的.
最后说一下封装的重点,首先来看一个封装后的请求:
/** * 一个基本的请求 */ private void getinfo() { RetrofitUtil.getApiService() .getInfo() .compose(RetrofitUtil.getComposer()) .subscribe(login -> { Log.i(TAG, "login: 接收到结果"); textview.setText(login.toString()); }); }
可以看到用RetrofitUtil.getApiService来获取到retrofit实例,然后调用自己写好的请求方法getInfo,getInfo返回Flowable,从此开始进入rajava2,(Flowable就是原来的Obserable),然后下面的一句就是我们的封装了
.compose(RetrofitUtil.getComposer())
通过rajava的compose操作符,它在传入请求结果的同时允许我们用flowabletransformer返回一个新的Flowable,这意味着在最终的subscribe之前我们可以对数据和线程随意处理.最基本来说,如果没有封装的话你每次请求都要增加两句线程指定的代码,这很明显是无用功.另外还有一些对接口返回值的统一处理,比如返回code=-1,就是请求失败,这种情况下很明显不需要我们每次都写一样的代码去处理,我们需要对这种普遍的情况统一处理.
/** * 这个方法里可以进行返回信息的统一处理 * @param <T> * @return */ public static <T extends BaseBean> FlowableTransformer<T, T> getComposer() { FlowableTransformer flowableTransformer = (FlowableTransformer<T, T>) upstream -> upstream .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .filter(t -> { if (t.code == -99) { //TODO 打开登录页面 Log.i(TAG, "test: 打开登录页面"); return false; } if (t.code != 1) { // TODO 返回错误信息 Log.i(TAG, "test: code" + t.code); return false; } //返回flse代表拦截,返回结果不会传递到subScribe中去,true代表不拦截 return true; }) .doOnSubscribe(subscription -> { // TODO 在这里弹出加载进度dialog if (context != null) { context.showDialog(subscription); } }) .doOnComplete(() -> { if (context != null) { context. dismissDialog(); } Log.i(TAG, "onComplete: "); }) .doOnError(throwable -> { Log.i(TAG, "获取失败: "); if (context != null) { context. dismissDialog(); } }) ; return flowableTransformer; }
解释一下上面的代码:
首先是线程设置:subscribeOn,指定请求网络在io线程,oberveOn,指定观察者也就是回调在主线程
然后是filter:这个操作符的意思是过滤,接收到数据,如果返回false代表拦截,返回true不拦截.BaseBean是所有实体类的父类,他只包含两个字段,code和msg.其中code就是我们拦截判断的标准.比如这里判断code=-99,代表用户token过期,我们需要打开登陆页面,code=-1,代表请求有问题,我们直接用Toast提示失败信息msg.这样就实现了请求结果的统一拦截.
最后是doOnSubscribe,这个方法会在请求开始的时候最先执行,这里最适合弹出加载对话框
doOncomplete和doOnError是在最后执行,区别是一个成功一个失败.如果成功的话就是先执行subscribe最后执行doOncomplete,如果失败就直接执行doOnError.这里加上让加载框消失.
这样,整个封装完成 ,在不破环rajava结构的情况下完成了封装,所以你可看到第二个请求例子:
/** * 示范了对返回数据的简单处理 */ public void get2() { ArrayList<String> imageUrlList = new ArrayList<>(); RetrofitUtil.getApiService(this)//如果需要显示加载对话框就传入当前activity,否则不传 .getInfo() .compose(RetrofitUtil.getComposer()) //这里可以进行rxjava的各种操作比如返回list的时候我们进行遍历,将所有图片的地址添加到一个集合里面 //flatMap遍历发射 .flatMap(cmsEntity -> Flowable.fromIterable(cmsEntity.obj.bannerList)) //收到之后添加 .doOnNext(imageVo -> imageUrlList.add(imageVo.imageSrc)) //最后显示 .subscribe(image ->textview.setText(imageUrlList.toString())); }
我们可以通过rajava的各种操作符对返回的数据直接处理,不必再回调里面各种嵌套或者循环.如果你对rxjava的操作符不太了解,可以多看看教程.
demo请自行查看
https://github.com/itwangyu/RetrofitDemo
demo是用studio3.0构建的,并支持了java8.用2.x版本很大可能会构建失败,如果你没有3.0,建议安装一个.3.0和2.x可以共存的