上篇文章我们主要介绍了okhttp的使用及实现原理,这篇文章我们来解析下Retrofit
前言
Retrofit其实我们可以理解为OkHttp的加强版,它也是一个网络加载框架,和OkHttp同样出自Square公司。Retrofit内部依赖于OkHttp,但是功能上做了更多的扩展,比如返回结果的转换功能,可以直接对返回数据进行处理。准确来说,网络请求的工作本质上是OkHttp完成,而 Retrofit 仅负责网络请求接口的封装。
Retrofit特点是包含了特别多注解,方便简化你的代码量
这里我们借用三张图来简单说明下上面用到的注解:
第一类:网络请求方法
第二类:标记
第三类:网络请求参数
Retrofit基本用法
配置
在build.grale添加如下依赖:
// Okhttp库
compile 'com.squareup.okhttp3:okhttp:3.10.0'
// Retrofit库
compile 'com.squareup.retrofit2:retrofit:2.4.0
创建用于描述网络请求的接口
@POST("passport/api/auth/login")
Observable<TokenInfoBean> mobileLogin(@Body RequestBody body);
@GET("platform/api/user/get_user_info/{key}")
Observable<AccountInfoBean> getUserInfo(@Path("key") int key);
创建Retrofit对象
@Override
public <T> T obtainRetrofitService(Class<T> service) {
//添加拦截器
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(new ParamInterceptor(mApplication))
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.build();
T t = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create()) ////添加一个转换器,将gson数据转换为bean类
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) ////添加一个适配器,与RxJava配合使用
.client(okHttpClient)
.baseUrl("xxxxxxx")
.build()
.create(service);
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new ProxyHandler(t,this));
}
此处特意说明一下Retrofit的网络请求的完整Url = 创建Retrofit实例时通过.baseUrl()设置的url
+网络请求接口的注解设置的
完成请求进行调用
@Override
public void login(String json, OnDataCallback<TokenInfoBean> dataCallback) {
apiHelper.obtainRetrofitService(ILoginApiService.class)
.mobileLogin(RequestUtils.getRequestBody(json))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(dataCallback.getView().bindLifeycle())
.subscribe(new HandleObserver<TokenInfoBean>(dataCallback));
}
@Override
public void readAccountInfo(int roleType, OnDataCallback<AccountInfoBean> dataCallback) {
apiHelper.obtainRetrofitService(ILoginApiService.class)
.getUserInfo(roleType)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(dataCallback.getView().<AccountInfoBean>bindLifeycle())
.subscribe(new HandleObserver<AccountInfoBean>(dataCallback));
}
这就是我们完成一次请求的流程,下面我们来分析下Retrofit是怎样实现这些功能的。
首先创建Retrofit实例
new Retrofit.Builder()
.baseUrl("xxxxxxx")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build()
Retrofit实例是使用建造者模式通过Builder类进行创建的,我们来看看Retrofit类(下面只是部分代码)
public final class Retrofit {
private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
// 网络请求配置对象(对网络请求接口中方法注解进行解析后得到的对象)
// 作用:存储网络请求相关的配置,如网络请求的方法、数据转换器、网络请求适配器、网络请求工厂、基地址等
private final HttpUrl baseUrl;
// 网络请求的url地址
private final okhttp3.Call.Factory callFactory;
// 网络请求器的工厂
// 作用:生产网络请求器(Call)
// Retrofit是默认使用okhttp
private final List<CallAdapter.Factory> adapterFactories;
// 网络请求适配器工厂的集合
// 作用:放置网络请求适配器工厂
// 网络请求适配器工厂作用:生产网络请求适配器(CallAdapter)
// 下面会详细说明
private final List<Converter.Factory> converterFactories;
// 数据转换器工厂的集合
// 作用:放置数据转换器工厂
// 数据转换器工厂作用:生产数据转换器(converter)
private final Executor callbackExecutor;
// 回调方法执行器
private final boolean validateEagerly;
// 标志位
// 作用:是否提前对业务接口中的注解进行验证转换的标志位
Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories,
@Nullable Executor callbackExecutor, boolean validateEagerly) {
this.callFactory = callFactory;
this.baseUrl = baseUrl;
this.converterFactories = unmodifiableList(converterFactories);
this.adapterFactories = unmodifiableList(adapterFactories);
// unmodifiableList(list)近似于UnmodifiableList<E>(list)
// 作用:创建的新对象能够对list数据进行访问,但不可通过该对象对list集合中的元素进行修改
this.callbackExecutor = callbackExecutor;
this.validateEagerly = validateEagerly;
}
}
我们可以看到构建一个Retrofit需要:
serviceMethod:包含所有网络请求信息的对象
baseUrl:网络请求的url地址
callFactory:网络请求工厂
adapterFactories:网络请求适配器工厂的集合
converterFactories:数据转换器工厂的集合
callbackExecutor:回调方法执行器
所谓xxxFactory、“xxx工厂”其实是设计模式中工厂模式的体现:将“类实例化的操作”与“使用对象的操作”分开,使得使用者不用知道具体参数就可以实例化出所需要的“产品”类。
提一下callFactory,她是网络请求工厂,我们可以看看他
interface Factory {
Call newCall(Request request);
}
我们可以看到callFactory主要是生产网络请求执行器Call,Call在Retrofit里默认是OkHttp,在这里我们重新设置了下client,主要是为了添加一些拦截器和设置一些timeout时间
像上面的参数我们都可以通过Builder来进行构建,这里我们着重介绍下converterFactories(数据转换器工厂的集合),顾名思义这个肯定是我们用来处理数据的,Retrofit默认使用Gson进行解析
若使用其他解析方式(如Json、XML或Protocobuf),也可通过自定义数据解析器来实现(必须继承 Converter.Factory),在这里我们可以处理请求参数和返回响应数据,请求参数我们是通过Okhttp里面的拦截器进行处理的,上篇文章已经介绍了,这里我们就不做处理,我们主要是来讲讲返回数据的处理,因为很多时候我们会遇到一个这样的场景,token过期问题,我们可以在每个请求返回处处理,但是太过繁琐和冗余,Retrofit就给我们提供了一个地方统一处理我们的服务器返回的数据:
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final TypeAdapter<T> adapter;
GsonResponseBodyConverter(TypeAdapter<T> adapter) {
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
try {
String json = value.string();
Log.e("","======ResponseBody==data==="+json);
BaseResponse response = JSON.parseObject(json,BaseResponse.class);
if (response.getCode() == ErrorCode.CODE_TOKEN_TIMEOUT) {
throw new TokenNotExistException();
} else if (response.getCode() == ErrorCode.CODE_TOKEN_STRING_ERROR) {
throw new TokenInvalidException();
} else if (response.getCode() == 200) {
return adapter.fromJson(response.getData());
} else {
// 特定 API 的错误,在相应的 Subscriber 的 onError 的方法中进行处理
throw new ApiException(response.getCode(),response.getMessage());
}
} finally {
value.close();
}
}
我们可以看到当我们检测到服务器返回数据标明使用的token已经过期或者不存在,我们就可以相应的异常进行处理,其他的异常统一处理为业务异常,正常的我们就返回相应的数据就行了。
我们再来看看构建Retrofit实例中的build()方法:
public Retrofit build() {
<-- 配置网络请求执行器(callFactory)-->
okhttp3.Call.Factory callFactory = this.callFactory;
// 如果没指定,则默认使用okhttp
// 所以Retrofit默认使用okhttp进行网络请求
if (callFactory == null) {
callFactory = new OkHttpClient();
}
<-- 配置回调方法执行器(callbackExecutor)-->
Executor callbackExecutor = this.callbackExecutor;
// 如果没指定,则默认使用Platform检测环境时的默认callbackExecutor
// 即Android默认的callbackExecutor
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
<-- 配置网络请求适配器工厂(CallAdapterFactory)-->
List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
// 向该集合中添加了步骤2中创建的CallAdapter.Factory请求适配器(添加在集合器末尾)
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
// 请求适配器工厂集合存储顺序:自定义1适配器工厂、自定义2适配器工厂...默认适配器工厂(ExecutorCallAdapterFactory)
<-- 配置数据转换器工厂:converterFactory -->
// 在步骤2中已经添加了内置的数据转换器BuiltInConverters()(添加到集合器的首位)
// 在步骤4中又插入了一个Gson的转换器 - GsonConverterFactory(添加到集合器的首二位)
List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
// 数据转换器工厂集合存储的是:默认数据转换器工厂( BuiltInConverters)、自定义1数据转换器工厂(GsonConverterFactory)、自定义2数据转换器工厂....
// 注:
//1. 获取合适的网络请求适配器和数据转换器都是从adapterFactories和converterFactories集合的首位-末位开始遍历
// 因此集合中的工厂位置越靠前就拥有越高的使用权限
// 最终返回一个Retrofit的对象,并传入上述已经配置好的成员变量
return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);
}
在最后一步中,通过前面步骤设置的变量,将Retrofit类的所有成员变量都配置完毕。如果前面没有配置的,都是用Retrofit默认的。自此一个Retrofit就构建完了,由于使用了建造者模式,所以开发者并不需要关心配置细节就可以创建好Retrofit实例。在创建Retrofit对象时,你可以通过更多更灵活的方式去处理你的需求,如使用不同的Converter、使用不同的CallAdapter,这也就提供了你使用RxJava来调用Retrofit的可能。
创建Call (网络请求接口的创建)##
T t = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.baseUrl("xxxxxxx")
.build()
.create(service);
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new ProxyHandler(t,this));
我们构建完Retrofit后就会调用create(service)方法,进行创建接口实例,我们先看看create方法源码:
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
}
});
}
这里使用了动态代理,在这里和以往的动态代理和静态代理使用的场景是类似的,都想在delegate调用方法前后做一些操作。简而言之,动态代理就是拦截调用的那个方法,在方法前后来做一些操作。Retrofit里的动态代理比较巧妙。实际上它根本就没有delegate。因为这个方法没有真正的实现。使用动态代理,只是单纯的为了拿到这个method上所有的注解。所有的工作都是由proxy做了。比起我们总说代理就是打log要高明多了。
我们主要看Proxy.newProxyInstance方法,它接收三个参数,第一个是一个类加载器,其实哪个类的加载器都无所谓,这里为了方便就选择了我们所定义的接口的类加载器,第二个参数是我们定义的接口的class对象,第三个则是一个InvocationHandler匿名内部类。
那大家应该会有疑问了,这个newProxyInstance到底有什么用呢?其实他就是通过动态代理生成了网络请求接口的代理类,代理类生成之后,接下来我们就可以使用apiHelper.obtainRetrofitService(ILoginApiService.class).login(xxx),这样的语句去调用login方法,当我们调用这个方法的时候就会被动态代理拦截,直接进入InvocationHandler的invoke方法。下面就来讲讲它。
invoke方法
它接收三个参数,第一个是动态代理,第二个是我们要调用的方法,这里就是指login方法,第三个是一个参数数组,同样的这里就是指我们要传的参数,收到方法名和参数之后,紧接着会调用loadServiceMethod方法来生产过一个ServiceMethod对象,这里的一个ServiceMethod对象就对应我们在网络接口里定义的一个方法,相当于做了一层封装。接下来重点来看loadServiceMethod方法:
ServiceMethod<?, ?> loadServiceMethod(Method method) {
ServiceMethod<?, ?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder<>(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
它调用了ServiceMethod类,而ServiceMethod也使用了Builder模式,直接先看Builder方法。
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
//获取接口中的方法名
this.method = method;
//获取方法里的注解
this.methodAnnotations = method.getAnnotations();
//获取方法里的参数类型
this.parameterTypes = method.getGenericParameterTypes();
//获取接口方法里的注解内容
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
从上面可以看出,因为Retrofit使用了大量的注解,我们在这里就可以通过ServiceMethod获取到相应的参数,我们再来看下build()方法:
public ServiceMethod build() {
callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
if (responseType == Response.class || responseType == okhttp3.Response.class) {
throw methodError("'"
+ Utils.getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
responseConverter = createResponseConverter();
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
if (httpMethod == null) {
throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
if (!hasBody) {
if (isMultipart) {
throw methodError(
"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
}
if (isFormEncoded) {
throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
+ "request body (e.g., @POST).");
}
}
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
Type parameterType = parameterTypes[p];
if (Utils.hasUnresolvableType(parameterType)) {
throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
parameterType);
}
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
if (parameterAnnotations == null) {
throw parameterError(p, "No Retrofit annotation found.");
}
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
if (relativeUrl == null && !gotUrl) {
throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
}
if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
throw methodError("Non-body HTTP method cannot contain @Body.");
}
if (isFormEncoded && !gotField) {
throw methodError("Form-encoded method must contain at least one @Field.");
}
if (isMultipart && !gotPart) {
throw methodError("Multipart method must contain at least one @Part.");
}
return new ServiceMethod<>(this);
}
我们可以理一下,这里主要做了以下工作:
1、首先对注解的合法性进行检验,例如,HTTP的请求方法是GET还是POST,如果不是就会抛出异常;
2、根据方法的返回值类型和方法注解从Retrofit对象的的callAdapter列表和Converter列表中分别获取到该方法对应的callAdapter和Converter;
3、将传递进来的参数与注解封装在parameterHandlers中,为后面的网络请求做准备。
进行到这里我们只有差发起网络请求了,回到crete方法里面的invoke方法最后两行:
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
这里将serviceMethod与我们实际传入的参数传递给了OkHttpCall,接下来就来瞧瞧这个类做了些什么:
final class OkHttpCall<T> implements Call<T> {
private final ServiceMethod<T, ?> serviceMethod; // 含有所有网络请求参数信息的对象
private final @Nullable Object[] args; // 网络请求接口的参数
private volatile boolean canceled;
@GuardedBy("this")
private @Nullable okhttp3.Call rawCall; //实际进行网络访问的类
@GuardedBy("this") // Either a RuntimeException, non-fatal Error, or IOException.
private @Nullable Throwable creationFailure; //几个状态标志位
@GuardedBy("this")
private boolean executed;
OkHttpCall(ServiceMethod<T, ?> serviceMethod, @Nullable Object[] args) {
// 传入了配置好的ServiceMethod对象和输入的请求参数
this.serviceMethod = serviceMethod;
this.args = args;
}
。。。。。。
}
配置好这些,接着调用serviceMethod.adapt(okHttpCall),这里将创建的OkHttpCall对象传给上面创建的serviceMethod对象中对应的网络请求适配器工厂的adapt()
T adapt(Call<R> call) {
return callAdapter.adapt(call);
}
点击进方法我们可以看到 callAdapter.adapt(call),构建Retrofit我们build传入的是.addCallAdapterFactory(RxJava2CallAdapterFactory.create()),看下里面实现的adapt():
@Override public Object adapt(Call<R> call) {
Observable<Response<R>> responseObservable = isAsync
? new CallEnqueueObservable<>(call)
: new CallExecuteObservable<>(call);
Observable<?> observable;
if (isResult) {
observable = new ResultObservable<>(responseObservable);
} else if (isBody) {
observable = new BodyObservable<>(responseObservable);
} else {
observable = responseObservable;
}
if (scheduler != null) {
observable = observable.subscribeOn(scheduler);
}
if (isFlowable) {
return observable.toFlowable(BackpressureStrategy.LATEST);
}
if (isSingle) {
return observable.singleOrError();
}
if (isMaybe) {
return observable.singleElement();
}
if (isCompletable) {
return observable.ignoreElements();
}
return observable;
}
首先在adapt方法中会先判断是同步请求还是异步请求,这里我们以同步请求为例,直接看CallExecuteObservable:
@Override protected void subscribeActual(Observer<? super Response<T>> observer) {
// Since Call is a one-shot type, clone it for each new observer.
Call<T> call = originalCall.clone();
CallDisposable disposable = new CallDisposable(call);
observer.onSubscribe(disposable);
boolean terminated = false;
try {
Response<T> response = call.execute();
if (!disposable.isDisposed()) {
observer.onNext(response);
}
if (!disposable.isDisposed()) {
terminated = true;
observer.onComplete();
}
} catch (Throwable t) {
Exceptions.throwIfFatal(t);
if (terminated) {
RxJavaPlugins.onError(t);
} else if (!disposable.isDisposed()) {
try {
observer.onError(t);
} catch (Throwable inner) {
Exceptions.throwIfFatal(inner);
RxJavaPlugins.onError(new CompositeException(t, inner));
}
}
}
}
万里长征终于走到了最后一步,在subscribeActual方法中去调用了OKHttpCall的execute方法开始进行网络请求,具体OKHttpCall是怎么发起网络请求的可以看下我上一篇文章。网络请求完毕之后,会通过RxJava的操作符对返回来的数据进行转换,并进行线程的切换,至此,Retrofit的一次使用也就结束了。
在最后在解释下在创建网络请求接口的时候我又使用了代理:
return (T) Proxy.newProxyInstance(service.getClassLoader(),
new Class<?>[] { service }, new ProxyHandler(t,this));
我们可以看看我的ProxyHandler做了什么事:
public class ProxyHandler implements InvocationHandler {
private final static String TAG = "Token_Proxy";
private final static String TOKEN = "token";
private final static int REFRESH_TOKEN_VALID_TIME = 30;
private static long tokenChangedTime = 0;
private Throwable mRefreshTokenError = null;
private Object mProxyObject;
RHttpHelper helper;
public ProxyHandler(Object proxyObject,RHttpHelper helper) {
mProxyObject = proxyObject;
this.helper = helper;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
return Observable.just("sxt").flatMap(new Function<Object, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Object o) throws Exception {
try {
try {
return (Observable<?>) method.invoke(mProxyObject, args);
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}).retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Throwable throwable) throws Exception {
if (throwable instanceof TokenInvalidException) {
//刷新token
return refreshTokenWhenTokenInvalid();
} else if (throwable instanceof TokenNotExistException) {
// Token 不存在,执行退出登录的操作。(为了防止多个请求,都出现 Token 不存在的问题,
// 这里需要取消当前所有的网络请求)
return Observable.error(throwable);
}
return Observable.error(throwable);
}
});
}
});
}
前面提到了在GsonResponseBodyConverter我们捕获了异常信息(token过期),并且抛出了异常,我们就在这里捕获了该异常,比如token过期我们就可以进行刷新token处理,如果是token不存在就执行退出登录操作。里面涉及到的Rxjava的会在下一篇文章解析。
总结
从上面的分析,我们队Retrofit应该有了一个大概的了解,其实retrofit就是一个负责调度的controller。你给它一个方法调用,它就在内部开始运转。通过动态代理,用一个ServiceMethod来解析它。解析后完成后配置一个request请求。但它自己搞不定这事啊,所以需要给它一个转接头callAdapter,通过转接头来使用okhttp。使用okhttp发起请求后,获取响应数据response,但是它又不认识,所以又请来GsonConverterFactory来帮忙,转换完毕之后才给出一个我们最终要的那个对象。
与其说它是一个网络请求框架不如说他做了一层封装,通过大量的设计模式 ,使得我们能够更方便的间接使用RxJava与OkHttp,整个框架的耦合度大大降低,调用者也使用得更加简洁。
下一篇文章我们在继续介绍Rxjava。