本文共 18570 字,大约阅读时间需要 61 分钟。
RxJava +Retrofit2 + okhttp3
好记性不如烂笔头,整理一下之前集成RRO框架时遇到的问题,和集成的过程,希望也能给有找问题的人一些帮助。 这个一套的集成,网上有大把的例子了,所以这里只是做一个代码实现上的介绍以及过程中需要注意的地方。1. 首先引入依赖
dependencies { //OkHttp implementation 'com.squareup.okhttp3:okhttp:3.10.0' //Retrofit implementation 'com.squareup.retrofit2:retrofit:2.3.0'//导入retrofit implementation 'com.squareup.retrofit2:converter-gson:2.3.0'//转换器 implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'//Rxjava支持 //这里我直接引入了Rxbinding,里面已经包含了Rxjava2 implementation 'com.jakewharton.rxbinding3:rxbinding:3.0.0-alpha2' }
2. 创建RetrofitServiceManager Retrofit2管理类
import java.util.List;import java.util.concurrent.TimeUnit;import okhttp3.Interceptor;import okhttp3.OkHttpClient;import retrofit2.Converter;import retrofit2.Retrofit;import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;import retrofit2.converter.gson.GsonConverterFactory;public class RetrofitServiceManager { private static final int DEFAULT_CONNECT_TIME_OUT = 15; private static final int DEFAULT_ACTION_TIME_OUT = 15; //这里先首先声明 private Retrofit retrofit; public RetrofitServiceManager(String baseUrl, Listinterceptors, List factories) { //通过构建器 创建OKHttp客户端 OkHttpClient.Builder builder = new OkHttpClient.Builder(); //链接超时时间 builder.connectTimeout(DEFAULT_CONNECT_TIME_OUT, TimeUnit.SECONDS); //写入超时时间 builder.writeTimeout(DEFAULT_ACTION_TIME_OUT, TimeUnit.SECONDS); //读取超时时间 builder.readTimeout(DEFAULT_ACTION_TIME_OUT, TimeUnit.SECONDS); builder.retryOnConnectionFailure(true); if (null != interceptors) { //添加拦截器 for (Interceptor interceptor : interceptors) { builder.addInterceptor(interceptor); } } Retrofit.Builder myRetrofit = new Retrofit.Builder().client(builder.build()); if (null != factories && factories.size() > 0) { //添加转换器 for (Converter.Factory factory : factories) { myRetrofit.addConverterFactory(factory); } } //这里的NullOnEmptyConverterFactory,因为我所负责项目接口可能会什么都不返回 //我觉得很奇葩,只能这里先做拦截处理了,避免出现转换异常 //如果你不需要,也可以去掉。 myRetrofit.addConverterFactory(new NullOnEmptyConverterFactory()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .baseUrl(baseUrl); retrofit = myRetrofit.build(); } private static RetrofitServiceManager serviceManager; //单例 public static RetrofitServiceManager getInstance(String baseUrl) { return getInstance(baseUrl, null, null); } //单例 public static RetrofitServiceManager getInstance(String baseUrl, List interceptors) { return getInstance(baseUrl, interceptors, null); } public static RetrofitServiceManager getInstance(String baseUrl, List interceptors, List factories) { if (serviceManager == null) { synchronized (RetrofitServiceManager.class) { if (serviceManager == null) { serviceManager = new RetrofitServiceManager(baseUrl, interceptors, factories); } } } return serviceManager; } public T create(Class service) { return retrofit.create(service); }}
3. 首先创建BaseTask,主要目的是将一些重复的代码抽出来,交由父类去实现,子类只关心访问哪个接口,如何访问…
import android.content.Context;import org.json.JSONObject;import java.io.File;import java.util.HashMap;import java.util.Map;import io.reactivex.Observable;import io.reactivex.android.schedulers.AndroidSchedulers;import io.reactivex.schedulers.Schedulers;import okhttp3.MediaType;import okhttp3.RequestBody;public class BaseTask { private String TAG = "BaseTask"; //指定观察者和被观察者的线程 protectedObservable useThread( Observable observable) { return observable //指定被观察者执行的线程 .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) //指定观察者执行的线程(我们这里通常是这样的操作,当然我们还可以在Task中自行切换线程) .observeOn(AndroidSchedulers.mainThread()); } //将键值对的参数转换成JsonBody public RequestBody getJSonBody(Map key) { String json = new JSONObject(key).toString(); return RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json); } /** * 将参数封装成requestBody * @param param 参数 * @return RequestBody */ public RequestBody convertToRequestBody(String param) { RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), param); return requestBody; } /** * 将File封装成RequestBody * * @param param 为文件类型 * @return 返回一个RequestBody */ public RequestBody convertToRequestBody(File param) { RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), param); return requestBody; } /** *同上面的方法大同小异,不过这里我们是处理包含 *File类型参数的键值对转换成 Map *用于文件上传一类的操作 **/ public Map convertToPartMap(Map map) { Map requestBodyMap = new HashMap<>(); for (String key : map.keySet()) { Object value = map.get(key); if (value instanceof File) { //如果是File我们这里进行转换操作 File file = (File) value; //这里需要格式拼接一下 requestBodyMap.put(key + "\";filename=\"" + file.getName(), convertToRequestBody(file)); } else { requestBodyMap.put(key, convertToRequestBody(String.valueOf(value))); } } return requestBodyMap; }
4. 创建我们自己的Task
import android.content.Context;//这里的引用我就只留公共的了import java.util.HashMap;import java.util.Map;import io.reactivex.Observable;import retrofit2.http.FieldMap;import retrofit2.http.FormUrlEncoded;import retrofit2.http.POST;public class LoginTask extends BaseTask { private LoginService loginService; private Context context; public LoginTask(Context context) { this.context = context; //注意我们在这里使用的是单例,但是有些情况下,一个应用可能不止一个接口, //这里baseURl已经在第一次创建时确定了。那么后面的baseurl就不会更改了。 //怎么办呢? //那我们这里可以直接 new RetrofitServiceManager(x,x,x) 来获取管理类的对象。 this.loginService = RetrofitServiceManager.getInstance(HttpConfig.HOST_STRING).create(LoginService.class); } public Observablelogin(Map map) { //注:这里我们进行的操作都必须放在useThread方法中,在BaseTask这里我们进行了线程的切换,否则会抛出异常,原因是因为在主线程中进行网络操作 return useThread(context, loginService.login(map) //这里flatMap函数对Observable进行了对象的转换,在最终的回调,我们就只用关心自己的那个数据了。 //这里使用java8的语法糖lambda表达式,不了解lambda表达式的可以百度一下就理解stringBaseBean 是什么对象了。 .flatMap(stringBaseBean -> { if (!stringBaseBean.isCode()) { //如果这里后台Json返回了非正常状态码,省事一点我们直接抛出自定义异常 //将异常流程交由onError()去处理 //BusinessException 自定义业务异常(英语渣,大噶能看懂我表达的意思就好了) throw new BusinessException(stringBaseBean.getMsg()); } //如果数据是正常的,那么我们就只需要把我们需要的数据, //利用Observable.just()立即发射回去就好了 return Observable.just(stringBaseBean.getData()); })); } //自定义接口 public interface LoginService { @FormUrlEncoded //指定编码格式 @POST(HttpConfig.LOGIN)//指定接口,个人比较喜欢统一写在一个类中管理 Observable > login(@FieldMap Map map); }}
LoginService 这里 Retrofit2的注解我就不多说了,网上很多,我自己也不是记得很清楚,一会儿我会在后面贴一下之前遇到的坑。
这里是ResultException,这里统一处理了一下异常,封装了ResultException
import androidx.annotation.Nullable;import com.futurekang.buildtools.net.retrofit.exception.BusinessException;import com.google.gson.JsonSyntaxException;import java.net.ConnectException;import java.net.SocketException;import java.net.SocketTimeoutException;import java.net.UnknownHostException;import retrofit2.HttpException;public class ResultException extends Exception { //网络异常 public final static int NETWORK_EXCEPTION = 0X001; //系统错误,(指本地客户端解析错误一类的问题) public final static int SYSTEM_EXCEPTION = 0X002; //业务流程异常(自定义异常) public final static int BUSINESS_EXCEPTION = 0X003; //未知的其他异常 public final static int OTHER_EXCEPTION = 0X004; private int exceptionType; private String msg; private Throwable cause; private ResultException(String message) { super(message); } private ResultException(String message, Throwable cause) { super(message, cause); } public ResultException(Throwable cause) { if (cause instanceof UnknownHostException || cause instanceof HttpException || cause instanceof SocketTimeoutException || cause instanceof SocketException) { exceptionType = NETWORK_EXCEPTION; if (cause instanceof ConnectException) { msg = "暂时无法连接到服务器,请稍候再试!"; } else { msg = "网络连接超时,请检查您的网络状态!"; } } else if ( cause instanceof IllegalArgumentException || cause instanceof JsonSyntaxException) { exceptionType = SYSTEM_EXCEPTION; msg = cause.getMessage();//也可以直接写 msg = "解析异常" } else if (cause instanceof BusinessException) { //业务异常 exceptionType = BUSINESS_EXCEPTION; msg = cause.getMessage(); } else { //其他异常 exceptionType = OTHER_EXCEPTION; msg = cause.getMessage(); } this.cause = cause; } public int getExceptionType() { return exceptionType; } public void setExceptionType(int exceptionType) { this.exceptionType = exceptionType; } @Nullable @Override public String getMessage() { return msg; } @Nullable @Override public synchronized Throwable getCause() { return cause; }}
如果你们公司的后台接口格式较为规范一点的,你的工作量能大大减小~,这里是我项目中常见的Json格式代码 这里用到了泛型
BaseBean代码public class BaseBean{ private boolean code; private String msg; private T data; public boolean isCode() { return code; } public void setCode(boolean code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; }}
这里不得不说一下,Rxjava配合lambda表达式真的很好用,不过不熟悉的人还是不太习惯的。
这里重贴一下之前的那段代码,照顾一下还没看习惯的人。 1.一般写法public Observablelogin(Map map) { return useThread(context, loginService.login(map) .flatMap(new Function , ObservableSource >() { @Override public ObservableSource apply(BaseBean loginBeanBaseBean) throws Exception { if (!loginBeanBaseBean.isCode()) { throw new BusinessException(loginBeanBaseBean.getMsg()); } return Observable.just(loginBeanBaseBean.getData()); } })); }
2.语法糖
public Observablelogin(Map map) { return useThread(context, loginService.login(map) .flatMap(stringBaseBean -> { if (!stringBaseBean.isCode()) { throw new BusinessException(stringBaseBean.getMsg()); } return Observable.just(stringBaseBean.getData()); })); }
5. 继承DisposableObserver 实现我们自己的NetworkObserver,
在NetworkObserver中,主要做的就是异常的处理及封装,还有进度条的控制 最后调用的时候只关心具体的数据和几种统一处理后的异常。import java.io.IOException;import java.util.Objects;import io.reactivex.observers.DisposableObserver;import retrofit2.HttpException;/** * @author Futurekang * @createdate 2019/10/10 9:36 * @description 定制化的网络请求Observer * 控制加载动画,并拦截了网络和业务异常流程, * 使用时只关心结果和异常流程 */@MainThreadpublic abstract class NetworkObserverextends DisposableObserver { private Context context;//上下文对象 private boolean showProgress;//是否显示加载动画(默认不显示) private Object requestCode = -1;//请求码 public NetworkObserver(Context context) { this.context = context; } public NetworkObserver(Context context, boolean showProgress) { this.context = context; this.showProgress = showProgress; } public NetworkObserver(Context context, Object requestCode) { this.context = context; this.requestCode = requestCode; } public NetworkObserver(Context context, boolean showProgress, Object requestCode) { this.context = context; this.showProgress = showProgress; this.requestCode = requestCode; } @Override//网络请求开始时(处于订阅时的线程) protected void onStart() { super.onStart(); //网络状态的判断 if (!NetWorkExceptionUtil.isNetworkAvailable(context)) { ToastUtils.ShowToast(context, context.getString(R.string.network_unavailable)); if (!isDisposed()) { //当网络请求不可用时主动取消订阅 dispose(); } //网络是否可用 } else if (!NetworkTools.turnOnTheNetwork(context)) { ToastUtils.ShowToast(context, context.getString(R.string.network_turn)); if (!isDisposed()) { //当网络请求不可用时主动取消订阅 dispose(); } } else { //网络可用显示进度条 showProgress(); } } @Override public void onNext(T t) { hideProgress();//获取到数据后,我们隐藏进度条 onSuccess(t); } @Override /**重点 异常处理**/ public void onError(Throwable e) { hideProgress();// //这里我们利用ResultException ,将可能出现的异常进行了拦截转换成我们统一的异常 ResultException resultException = new ResultException(e); //这一段由你的业务确定是否需要, //这里主要是判断非业务异常直接弹窗提示原因 if (resultException.getExceptionType() != ResultException.BUSINESS_EXCEPTION) { ToastUtils.ShowToast(context, resultException.getMessage()); } //回调 onError(resultException); //打印具体网络错误的具体信息 if (e instanceof HttpException) { HttpException httpException = (HttpException) e; try { String error = Objects.requireNonNull(httpException.response().errorBody()).string(); String TAG = "NetworkObserver"; Log.d(TAG, "onError: " + error); } catch (IOException e1) { e1.printStackTrace(); } } } @Override public void onComplete() { } private BaseDialog progressDialog; @MainThread private void showProgress() { if (!showProgress) { return; } //BaseDialog是我自封装的,这里是我的进度条,用到了LottieAnimationView ,很好用的动画view if (progressDialog == null) { progressDialog = new BaseDialog(context, R.layout.view_progress_anim) { @Override protected void setChildView(View v) { LottieAnimationView lottieAnimation = v.findViewById(R.id.lv_animation_view); lottieAnimation.setAnimation(context.getString(R.string.loading_animator_197)); lottieAnimation.playAnimation(); ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f); valueAnimator.setDuration(1); valueAnimator.addUpdateListener(valueAnimator1 -> lottieAnimation.setProgress((Float) valueAnimator1.getAnimatedValue())); valueAnimator.start(); alterDialog.setOnCancelListener(dialogInterface -> { lottieAnimation.cancelAnimation(); //取消订阅 if (!NetworkObserver.this.isDisposed()) { dispose(); } }); } }; } progressDialog.show(); } @MainThread protected void hideProgress() { if (progressDialog != null) { progressDialog.dismiss(); } } public abstract void onSuccess(T data); public abstract void onError(ResultException message);
6. 使用
Activity中这样使用 伪代码:Mapparams = new HashMap(); params.put("username", username); params.put("password", password); Disposable disposable = loginTask.login(params) .subscribeWith(new NetworkObserver (this) { @Override public void onSuccess(LoginBean loginBean) { ...... } @Override public void onError(ResultException e) { ...... } }); addDisposable(disposable);
这样的网络请求是不是很简洁明了啊。
还有就是使用这个框架时的遇到的坑:
1. Retrofit2动态设置URL遇到的问题@FormUrlEncoded @POST("user/{url}") Observable> login(@Path("url") String url);
上面这种写法是url中只需要替换一个部分的情况,但是有时候我们需要整个POST中的url都替换了,比如说这样
@FormUrlEncoded @POST("{url}") Observable> login(@Path("url") String url);
但是这样写,我们测试就会发现请求时出错了。为什么会这样呢?我通过测试发现最后替换的上面的url中的斜杠 “/” 都被转义成了“%2F”,为此我专门写了一个拦截器
public class URLInterceptor implements Interceptor { private String TAG = "URLInterceptor"; @Override public Response intercept(Chain chain) throws IOException { Request oldRequest = chain.request(); //构建新的请求,代替原来的请求 Request.Builder requestBuilder = oldRequest.newBuilder(); requestBuilder.method(oldRequest.method(), oldRequest.body()); HttpUrl.Builder authorizedUrlBuilder = oldRequest.url().newBuilder(); HttpUrl oldHttpUrl = authorizedUrlBuilder.build(); URL orgUrl = oldHttpUrl.url(); String url = orgUrl.getProtocol() + "://" + orgUrl.getAuthority() + orgUrl.getPath().replace("%2F", "/"); HttpUrl newHttpUrl = HttpUrl.parse(url); requestBuilder.url(newHttpUrl); // 新的请求 Request newRequest = requestBuilder.build(); return chain.proceed(newRequest); }}
结果发现多此一举
我们点开@path这个注解的内部看一看 这里就是说,默认情况下是编码的 我们可以设置 encoded = true 禁用掉这个选项。@FormUrlEncoded @POST("{url}") Observable> login(@Path(value = "url", encoded = true) String url);
这样写,问题就解决了~
其他的问题还会陆续整理上来的…注:参考了这篇文章
https://blog.csdn.net/yangxi_pekin/article/details/72421057。
最后:
这是本人第一次写博客,做了几年的Android开发,一直没总结过实在是失败,可能忙是理由,菜也是理由(还好知道自己菜),如果有什么问题的地方,请下方指正一下。转载地址:https://blog.csdn.net/AdminStar/article/details/102605280 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!