1. 前言 根据Retrofit官网的定义:A type-safe HTTP client for Android and Java,意即Retrofit是一个类型安全的HTTP网络请求框架。本文先介绍Retrofit2的基本使用方法,再逐步分析Retrofit代码基本实现,最后对Retrofit优缺点进行总结。
2. 基本使用 使用非常简单,可以参考Retrofit官网
2.1 定义接口 1 2 3 4 public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos (@Path("user") String user) ; }
2.2 构建实例 1 2 3 4 5 Retrofit retrofit = new Retrofit .Builder() .baseUrl("https://api.github.com/" ) .addConverterFactory(GsonConverterFactory.create()) .build(); GitHubService service = retrofit.create(GitHubService.class);
2.3 转换 1 2 Call<List<Repo>> call = service.listRepos("octocat" ); List<Repo> repos = call.execute().body();
3. 代码分析 Retrofit严格意义上来说,不算一个网络请求框架,因为Retrofit2的主要工作是把请求用清晰、简洁的方式描述出来,并把一个HTTP API转换成Java接口。具体的网络请求工作其实是由OkHttp来完成的。下面我们从请求描述和请求发起和网络请求管理三个方面来分析。
3.1 如何描述请求 我们关心:Retrofit是怎么把Java接口转成一个Call的,又是怎么把一个Call转成一个可以发送请求的Request的。
对于定义的接口,使用动态代理,每一个接口方法调用时,都会调用==invoke==方法
3.1.1 Retrofit.create 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 (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.callAdapter.adapt(okHttpCall); } }); }
此处使用了缓存,对于加载过的方法,直接从cache中拿。ServiceMethod主要作用是把一个Java接口映射到Http请求上.泛型R表示返回请求值,T表示Java接口返回值(Demo中Call>)
问题:为何有了Converter还要有CallAdapter? 二者有和区别?
Converter作用主要是提供不同数据类型的转换,比如把ResponseBody转成List,把RequestBody数据转换成Json格式,等等。抽象成一个接口便于扩展,增加灵活性;
CallAdapter主要作用是把Call>转成Java对象(List)
3.1.2 loadServiceMethod 1 2 3 4 5 6 7 8 9 10 11 12 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; }
每一个Retrofit2中Call(此处拿OkHttpCall举例,不一定是Call,如果是RxJava,则为Obserable,原理一致)调用execute()方法之后,返回的是一个Retrofit2的Response。有个比较有趣的地方是Retrofit2中的OkHttpCall不是真正发起请求的,而是调用OkHttp3.Call。而Retrofit2中的Response也不是真相的返回结果,而是利用OkHttp3.Response作为返回。这种组合的方式有个好处就是解耦,以后若不想用OkHttp3改用其他框架,工作量也会小很多。
上面已经把Java接口转成Call部分说完了,接下来看如何把Call转成Request.
3.1.3 ServiceMethod.Builder.build() 在build()方法中主要是解析方法注解、方法参数注解,从而得到一个Http请求的完整信息(如httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody, isFormEncoded, isMultipart等)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 static final class Builder <T, R> { public ServiceMethod build () { callAdapter = createCallAdapter(); responseType = callAdapter.responseType(); responseConverter = createResponseConverter(); for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); } int parameterCount = parameterAnnotationsArray.length; parameterHandlers = new ParameterHandler <?>[parameterCount]; for (int p = 0 ; p < parameterCount; p++) { Type parameterType = parameterTypes[p]; Annotation[] parameterAnnotations = parameterAnnotationsArray[p]; parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations); } return new ServiceMethod <>(this ); }
3.1.4 ServiceMethod.toRequest() 之后,再通过toRequest()把步骤3.1.4中的信息(httpMethod, baseUrl, relativeUrl, headers,contentType, hasBody, isFormEncoded, isMultipart)保存到==OkHttp3.Request==中。具体参数替换和赋值(如替换Url、修改relativeUrl)等操作则放在ParameterHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Request toRequest (@Nullable Object... args) throws IOException { RequestBuilder requestBuilder = new RequestBuilder (httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody, isFormEncoded, isMultipart); @SuppressWarnings("unchecked") ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers; int argumentCount = args != null ? args.length : 0 ; if (argumentCount != handlers.length) { throw new IllegalArgumentException ("Argument count (" + argumentCount + ") doesn't match expected count (" + handlers.length + ")" ); } for (int p = 0 ; p < argumentCount; p++) { handlers[p].apply(requestBuilder, args[p]); } return requestBuilder.build(); }
至此,把Call转换成一个OkHttp3.Request 类的过程分析完毕。
3.2 发起请求 实际上发送请求的是OkHttp3而不是Retrofit。因此发送请求过程十分简单,使用一个包装类OkHttpCall包装OkHttp3.Call请求,返回结果。
3.2.1 Retrfit.OkHttpCall 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Override public Response<T> execute () throws IOException { okhttp3.Call call; synchronized (this ) { if (executed) throw new IllegalStateException ("Already executed." ); executed = true ; if (creationFailure != null ) { if (creationFailure instanceof IOException) { throw (IOException) creationFailure; } else { throw (RuntimeException) creationFailure; } } call = rawCall; if (call == null ) { try { call = rawCall = createRawCall(); } catch (IOException | RuntimeException e) { creationFailure = e; throw e; } } } if (canceled) { call.cancel(); } return parseResponse(call.execute()); }
3.2.2 OkHttpCall.createRawCall && parseResponse() 使用createRawCall创建OkHttp3.Call 请求,并通过parseResponse()解析返回结果并包装成Retrofit.Response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 private okhttp3.Call createRawCall () throws IOException { Request request = serviceMethod.toRequest(args); okhttp3.Call call = serviceMethod.callFactory.newCall(request); if (call == null ) { throw new NullPointerException ("Call.Factory returned null." ); } return call; } Response<T> parseResponse (okhttp3.Response rawResponse) throws IOException { ResponseBody rawBody = rawResponse.body(); rawResponse = rawResponse.newBuilder() .body(new NoContentResponseBody (rawBody.contentType(), rawBody.contentLength())) .build(); int code = rawResponse.code(); if (code < 200 || code >= 300 ) { try { ResponseBody bufferedBody = Utils.buffer(rawBody); return Response.error(bufferedBody, rawResponse); } finally { rawBody.close(); } } if (code == 204 || code == 205 ) { rawBody.close(); return Response.success(null , rawResponse); } ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody (rawBody); try { T body = serviceMethod.toResponse(catchingBody); return Response.success(body, rawResponse); } catch (RuntimeException e) { catchingBody.throwIfCaught(); throw e; } }
至此,一个接口从发起到返回结果,并包装成Java对象的过程分析完毕。
3.2.3 大致流程
创建Retrofit实例 (retrofit),设置一些全局参数如baseUrl、converer等
创建Java接口实例 (service)
调用Java接口实例具体方法 (service.listRepos(“octocat”)),生成ServiceMethod实例,用来管理接口
由ServiceMethod解析请求参数
由代理Call发出的请求放到OkHttp3请求队列中请求
将请求结果回调
最终将数据转化成目标类型
3.3 请求管理 Retrofit2的请求其实是借用的OkHttp3的网络请求。这部分内容将在下篇文章进行介绍。
总结 Retrofit2写得非常短小精悍,其巧妙地利用动态代理、泛型、缓存等技术,引入了Builder模式、适配器模式等设计模式,为Retrofit扩展性提供了强大的保证,代码可读性高的同时也不失效率。就网络框架上,个人可能更倾向于使用Volley,但是Retrofit2使用注解来描述请求的这个思想包括使用到的技术都可以借鉴,扩展开来,数据库或许是另一个使用注解描述一个Query的场景。