ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-01-24 16:32:20  阅读:168  来源: 互联网

标签:Retrofit2 app retrofit baseUrl method new android 方法 annotation


//同步请求方式

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

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

}

标签:Retrofit2,app,retrofit,baseUrl,method,new,android,方法,annotation
来源: https://blog.csdn.net/m0_66264673/article/details/122670025

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有