Retrofit2,基于android的app开发平台综述

//同步请求方式

call.request();

//异步请求方式

call.enqueue(new Callback() {

@Override

public void onResponse(Call call, Response response) {

//请求成功回调

}

@Override

public void onFailure(Call call, Throwable t) {

//请求与失败回调

}

});

至此,retrofit的一次网络请求示例已经结束,基于对okhttp的封装,让网络请求已经简化了很多。当然retrofit最适合的还是REST API类型的接口,方便简洁。

下面我们就看看retrofit的核心工作是如何完成的!


retrofit初始化

retrofit的初始化采用了链式调用的设计

Retrofit retrofit = new Retrofit.Builder()
Retrofit2,基于android的app开发平台综述

.baseUrl(“https://api.github.com/”)

.build();

很明显这个方法是在传一些需要的参数,我们简单的跟踪一下:

首先看看Builder()的源码:

public Builder() {

this(Platform.get());

}

这句代码很简单就是调用了自己的另一个构造函数:

Builder(Platform platform) {

this.platform = platform;

}

这个构造函数也很简单,就是一个赋值,我们把之前的Platform.get()点开,看看里面做在什么:

private static final Platform PLATFORM = findPlatform();

static Platform get() {

return PLATFORM;

}

我们发现这里使用使用了一个饿汉式单例,使用Platform.get()返回一个实例,这样写的好处是简单,线程安全,效率高,不会生成多个实例!

我们再看看findPlatform() 里做了什么:

private static Platform findPlatform() {

try {

Class.forName(“android.os.Build”);

if (Build.VERSION.SDK_INT != 0) {

return new Android();

}

} catch (ClassNotFoundException ignored) {

}

…省略部分代码…

}

所以是判断了一下系统,然后根据系统实例化一个对象。这里面应该做了一些和Android平台相关的事情,属于细节,我们追究,感兴趣的可以只看看。

再看看baseUrl(url)的源码

public Builder baseUrl(String baseUrl) {

checkNotNull(baseUrl, “baseUrl == null”);

HttpUrl httpUrl = HttpUrl.parse(baseUrl);

return baseUrl(httpUrl);

}

public Builder baseUrl(HttpUrl baseUrl) {

checkNotNull(baseUrl, “baseUrl == null”);

this.baseUrl = baseUrl;

return this;

}

这两段代码也很简单,校验URL,生成httpUrl对象,然后赋值给baseUrl

看看build() 方法在做什么

参数基本设置完了,最后就要看看build() 这个方法在做什么:

public Retrofit build() {

if (baseUrl == null) {

throw new IllegalStateException(“Base URL required.”);

}

okhttp3.Call.Factory callFactory = this.callFactory;

if (callFactory == null) {

callFactory = new OkHttpClient();

}

return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),

unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);

}

}

}

代码中有大量的参数校验,有些复杂的参数我们没有传,所以我就把那些代码删除了。简单看一下也能知道,这段代码就是做一些参数校验,baseUrl不能为空否则会抛异常,至于其他的参数如果为null则会创建默认的对象。其中callFactory就是okhttp的工厂实例,用于网络请求的。

最后我们看到,这个方法最终返回的是一个Retrofit的对象,初始化完成。

生成接口实现类

刚才我们就讲过retrofit.create这个方法很重要,它帮我们生成了接口实现类,并完成了方法体的创建,省去了我们很多工作量。那我们来看看它是如何帮我们实现接口的。

public T create(final Class 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 okHttpCall = new OkHttpCall<>(serviceMethod, args);

return serviceMethod.adapt(okHttpCall);

}

});

}

这段代码实际上是使用了动态代理的设计模式,而且这个方法封装的非常好,我们只需要调用 方法就可以获得我们需要的实现类,遵循了迪米特法则(最少知道原则)。

了解动态代理的人都知道我们要重写Object invoke(Object proxy, Method method, [@Nullable]( ) Object[] args) 方法,这个方法会传入我们需要的实现的方法,和参数,并返回我们需要的返回值。

retrofit在重写这个方法的时候做了三件事:

  • 1、先判断了这个方法的类是不是一个Object.class),就直接返回方法原有的返回值。

  • 2、判断这个方法是不是DefaultMethod,大家都知道这个方法是Java 8出来的新属性,表示接口的方法体。

  • 3、构建一个ServiceMethod<Object, Object>对象和OkHttpCall<Object>对象,并调用

serviceMethod.adapt(okHttpCall)方法将二者绑定。

我们看看这个方法的源码:

T adapt(Call call) {

return callAdapter.adapt(call);

}

这个callAdapter我们在初始化retrofit的时候没有使用:

addCallAdapterFactory(CallAdapterFactory)传值,所以这里是默认的DefaultCallAdapterFactory

那我们再看看DefaultCallAdapterFactory里的adapt(call)方法:

@Override public Call adapt(Call call) {

return call;

}

直接返回参数,也就是OkHttpCall<Object>的对象。所以如果没有自定义callAdapter的时候,我们定义接口的时候返回值类型应该是个Call类型的。

那么,至此这个create方法已经帮我们实现了我们定义的接口,并返回我们需要的值。

请求参数整理

我们定义的接口已经被实现,但是我们还是不知道我们注解的请求方式,参数类型等是如何发起网络请求的呢?

这时我们可能应该关注一下ServiceMethod<Object, Object>对象的构建了:

ServiceMethod<Object, Object> serviceMethod =

(ServiceMethod<Object, Object>) loadServiceMethod(method);

主要的逻辑都在这个loadServiceMethod(method)里面,我们看看方法体:

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;

}

逻辑很简单,就是先从一个 serviceMethodCache中取ServiceMethod<?, ?>对象,如果没有,则构建ServiceMethod<?, ?>对象,然后放进去serviceMethodCache中,这个serviceMethodCache是一个HashMap:

private final Map<Method, ServiceMethod<?, ?>> serviceMethodCache = new ConcurrentHashMap<>();

所以构建ServiceMethod<?, ?>对象的主要逻辑还不在这个方法里,应该在new ServiceMethod.Builder<>(this, method).build();里面。这也是个链式调用,一般都是参数赋值,我们先看看Builder<>(this, method)方法:

Builder(Retrofit retrofit, Method method) {

this.retrofit = retrofit;

this.method = method;

this.methodAnnotations = method.getAnnotations();

this.parameterTypes = method.getGenericParameterTypes();

this.parameterAnnotationsArray = method.getParameterAnnotations();

}

果然,这里获取了几个重要的参数:

  • retrofit实例

  • method,接口方法

  • 接口方法的注解methodAnnotations,在retrofit里一般为请求方式

  • 参数类型parameterTypes

  • 参数注解数组parameterAnnotationsArray,一个参数可能有多个注解

我们再看看build()的方法:

public ServiceMethod build() {

callAdapter = createCallAdapter();

responseType = callAdapter.responseType();

responseConverter = createResponseConverter();

for (Annotation annotation : methodAnnotations) {

parseMethodAnnotation(annotation);

}

if (httpMethod == null) {

throw methodError(“HTTP method annotation is required (e.g., @GET, @POST, etc.).”);

}

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);

}

return new ServiceMethod<>(this);

}

这个方法挺长的,删了些无关紧要的代码还是很长。首先一开始先获取几个重要对象:callAdapterresponseTyperesponseConverter,这三个对象都跟最后的结果有关,我们先不管。

看到一个for循环,遍历方法的注解,然后解析:

for (Annotation annotation : methodAnnotations) {

parseMethodAnnotation(annotation);

}

private void parseMethodAnnotation(Annotation annotation) {

if (annotation instanceof DELETE) {

parseHttpMethodAndPath(“DELETE”, ((DELETE) annotation).value(), false);

} else if (annotation instanceof GET) {

parseHttpMethodAndPath(“GET”, ((GET) annotation).value(), false);

}

这个方法的方法体我删掉了后面的一部分,因为逻辑都是一样,根据不同的方法注解作不同的解析,得到网络请求的方式httpMethod。但是主要的方法体还是if里面的方法:

private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {

// Get the relative URL path and existing query string, if present.

int question = value.indexOf(’?’);

if (question != -1 && question < value.length() - 1) {

// Ensure the query string does not have any named parameters.

String queryParams = value.substring(question + 1);

Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);

if (queryParamMatcher.find()) {

throw methodError("URL query string “%s” must not have replace block. "

  • “For dynamic query parameters use @Query.”, queryParams);

}

}

this.relativeUrl = value;

this.relativeUrlParamNames = parsePathParameters(value);

}

逻辑不复杂,就是校验这个value的值 是否合法,规则就是不能有“?”如果有则需要使用@Query注解。最后this.relativeUrl = value;。这个relativeUrl就相当于省略域名的URL,一般走到这里我们能得到的是:users/{name}/repos这样的。里面的“{name}”是一会我们需要赋值的变量。

我们继续看刚才的build()方法:

解析完方法的注解之后,需要解析参数的注解数组,这里实例化了一个一维数组:

parameterHandlers = new ParameterHandler<?>[parameterCount];

然后遍历取出参数的类型:

Type parameterType = parameterTypes[p];

取出参数注解:

Annotation[] parameterAnnotations = parameterAnnotationsArray[p];

然后把参数类型、参数注解都放在一起进行解析,解析的结果放到刚才实例化的数组parameterHandlers里面:

parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);

那我们再看看这个方法里做了什么:

private ParameterHandler<?> parseParameter(int p, Type parameterType, Annotation[] annotations) {

ParameterHandler<?> result = null;

for (Annotation annotation : annotations) {

ParameterHandler<?> annotationAction = parseParameterAnnotation(

p, parameterType, annotations, annotation);

}

}

这个方法的主要代码也很简单,解析参数注解,得到一个ParameterHandler<?> annotationAction对象。

那我继续看方法里面的代码。当我们点进parseParameterAnnotation( p, parameterType, annotations, annotation);的源码里面去之后发现这个方法的代码接近500行!但是大部分逻辑类似,都是通过if else判断参数的注解,我们取一段我们刚才的例子相关的代码出来:

if (annotation instanceof Path) {

if (gotQuery) {

throw parameterError(p, “A @Path parameter must not come after a @Query.”);

}

if (gotUrl) {

throw parameterError(p, “@Path parameters may not be used with @Url.”);

}

if (relativeUrl == null) {

throw parameterError(p, “@Path can only be used with relative url on @%s”, httpMethod);

}

gotPath = true;

Path path = (Path) annotation;

String name = path.value();

validatePathName(p, name);

Converter<?, String> converter = retrofit.stringConverter(type, annotations);

return new ParameterHandler.Path<>(name, converter, path.encoded());

}

parameterType, annotations, annotation);的源码里面去之后发现这个方法的代码接近500行!但是大部分逻辑类似,都是通过if else`判断参数的注解,我们取一段我们刚才的例子相关的代码出来:

if (annotation instanceof Path) {

if (gotQuery) {

throw parameterError(p, “A @Path parameter must not come after a @Query.”);

}

if (gotUrl) {

throw parameterError(p, “@Path parameters may not be used with @Url.”);

}

if (relativeUrl == null) {

throw parameterError(p, “@Path can only be used with relative url on @%s”, httpMethod);

}

gotPath = true;

Path path = (Path) annotation;

String name = path.value();

validatePathName(p, name);

Converter<?, String> converter = retrofit.stringConverter(type, annotations);

return new ParameterHandler.Path<>(name, converter, path.encoded());

}

上一篇:vue基础:axios和mockjs


下一篇:Photoshop设计制作出漂亮的橙色塑胶字