一. retrofit的简单使用
先看下涉及到的类:
首先定义个接口ApiService,所有的请求方法的定义.
public interface ApiService {
@POST("doPost")
Call<ResponseBody> doPost(@Query("name") String name,
@Query("email") String e
);
@GET("doGet")
Call<ResponseBody> doGet(@Query("name") String name,
@Query("age") int age
);
}
对retrofit的配置类,不建议每次都去创建retrofit的实例. OkHttpClient 也要保证整个工程只有一个实例,官方推荐也是这么做的.
/**
* Created by CaiRR on 2017-10-25.
*/
public class ApiClient {
private static OkHttpClient mOkHttpClient = null;
private static Retrofit mRetrofit;
private static ApiService mApiService;
private static final boolean isDebug = true;
private static final String HTTP_BASE_URL = "http://192.168.6.21:9090";
public static ApiClient getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final ApiClient INSTANCE = new ApiClient();
}
private ApiClient() {
initOkHttpClient();
mRetrofit = new Retrofit.Builder()
.baseUrl(HTTP_BASE_URL)
//设置 Json 转换器
.addConverterFactory(GsonConverterFactory.create())
.client(mOkHttpClient)
.build();
mApiService = mRetrofit.create(ApiService.class);
}
private void initOkHttpClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (isDebug) {
// Log信息拦截器
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
//设置 Debug Log 模式
builder.addInterceptor(loggingInterceptor);
}
//设置超时
builder.connectTimeout(15, TimeUnit.SECONDS);
builder.readTimeout(60, TimeUnit.SECONDS);
builder.writeTimeout(60, TimeUnit.SECONDS);
//错误重连
builder.retryOnConnectionFailure(true);
mOkHttpClient = builder.build();
}
public ApiService getApiService() {
return mApiService;
}
public OkHttpClient getOkhttpClient() {
if (mOkHttpClient == null) {
initOkHttpClient();
}
return mOkHttpClient;
}
}
在这里简单的使用 post 和 get 请求,分别用同步和异步的形式.
public class TestRetrofitActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_retrofit);
findViewById(R.id.btn_get).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doGet();
}
});
findViewById(R.id.btn_post).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doPost();
}
});
}
private void doPost() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Response<ResponseBody> response = ApiClient.getInstance().getApiService().doPost("xiaocai", "xiaocai@163.com").execute();
String data = response.body().string();
Log.d("xiaocai", data);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private void doGet() {
ApiClient.getInstance().getApiService().doGet("xiaocai", 25).enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
String data = response.body().string();
Log.d("xiaocai", data);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});
}
}
在spring boot中简单的做下处理:
@RestController
public class HelloController {
private final static org.slf4j.Logger log = LoggerFactory.getLogger("xiaocai");
@RequestMapping("/sayHello")
public String sayHello() {
return JsonHelper.success("hello world");
}
@GetMapping("/doGet")
public String testGet(@RequestParam(value = "name") String name,
@RequestParam(value = "age", required = false) int age) {
String data = "name:" + name + " age:" + age;
log.debug(data);
return JsonHelper.success(data);
}
@PostMapping("/doPost")
public String testPost(@RequestParam(value = "name") String name,
@RequestParam(value = "email", required = false) String email) {
String data = "name:" + name + " email:" + email;
log.debug(data);
return JsonHelper.success(data);
}
}
日志输出:
// post 请求
D/OkHttp: --> POST http://192.168.6.21:9090/post?name=xiaocai&email=xiaocai@163.com http/1.1
D/OkHttp: Content-Length: 0
D/OkHttp: --> END POST (0-byte body)
D/OkHttp: <-- 200 http://192.168.6.21:9090/post?name=xiaocai&email=xiaocai@163.com (31ms)
D/OkHttp: Content-Type: text/plain;charset=UTF-8
D/OkHttp: Content-Length: 82
D/OkHttp: Date: Wed, 25 Oct 2017 07:43:47 GMT
D/OkHttp: {"msg":"成功","code":0,"data":"name:xiaocai email:xiaocai@163.com","status":"0"}
D/OkHttp: <-- END HTTP (82-byte body)
D/xiaocai: {"msg":"成功","code":0,"data":"name:xiaocai email:xiaocai@163.com","status":"0"}
// get 请求
D/OkHttp: --> GET http://192.168.6.21:9090/get?name=xiaocai&age=25 http/1.1
D/OkHttp: --> END GET
D/OkHttp: <-- 200 http://192.168.6.21:9090/get?name=xiaocai&age=25 (18ms)
D/OkHttp: Content-Type: text/plain;charset=UTF-8
D/OkHttp: Content-Length: 67
D/OkHttp: Date: Wed, 25 Oct 2017 07:44:13 GMT
D/OkHttp: {"msg":"成功","code":0,"data":"name:xiaocai age:25","status":"0"}
D/OkHttp: <-- END HTTP (67-byte body)
D/xiaocai: {"msg":"成功","code":0,"data":"name:xiaocai age:25","status":"0"}
整个过程还是比较清爽的.
其他的使用方式可以直接看官网的说明:
retrofit的使用
二. 源码分析
我们在使用 retrofit 的时候,基本上都会:
mRetrofit = new Retrofit.Builder()
.baseUrl(HTTP_BASE_URL)
//设置 Json 转换器
.addConverterFactory(GsonConverterFactory.create())
.client(mOkHttpClient)
.build();
mApiService = mRetrofit.create(ApiService.class);
从 create 方法作为入口进行分析:
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解析注解
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
// args 是具体的参数值
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
// 里面涉及解析接口中定义的单个方法
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(); // 创建 ServiceMethod 类
serviceMethodCache.put(method, result); // 加入到缓存中
}
}
return result;
}
1. ServiceMethod 分析
ServiceMethod 的主要职责是将接口中的单个方法解析为 HTTP call ,里面涉及到解析请求方式,请求参数.
需要注意的是 ServiceMethod 处理的单个接口中的方法.``````
ServiceMethod 是通过 builder 模式创建的
public Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations(); // 获取方法上的注解
this.parameterTypes = method.getGenericParameterTypes(); // 参数类型
this.parameterAnnotationsArray = method.getParameterAnnotations(); // 获取参数中的注解及注解中的内容
}
以下方法为例,主要研究方法上的注解及其值和解析方法参数的注解和值
@POST("post")
Call<ResponseBody> doPost(@Query("name") String name,
@Query("email") String email
);
methodAnnotations 中会得到 @retrofit2.http.POST(value=doPost) ,如果多个修饰这里也会得到多个注解类
parameterTypes 会得到 java.lang.String 和 java.lang.String 分别对应两个参数的类型
parameterAnnotationsArray 中会得到 类型是 Query 其值是 name,类型是 Query 其值是 email,这里的值是注解内的值.
在 build 中会去解析注解:
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); // 解析方法上的注解及注解值
}
解析方法上的注解:
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);
} else if (annotation instanceof HEAD) {
parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
if (!Void.class.equals(responseType)) {
throw methodError("HEAD method must use Void as response type.");
}
} else if (annotation instanceof PATCH) {
parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
} else if (annotation instanceof POST) {
parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
} else if (annotation instanceof PUT) {
parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
} else if (annotation instanceof OPTIONS) {
parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
} else if (annotation instanceof HTTP) {
HTTP http = (HTTP) annotation;
parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
} else if (annotation instanceof retrofit2.http.Headers) {
String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
if (headersToParse.length == 0) {
throw methodError("@Headers annotation is empty.");
}
headers = parseHeaders(headersToParse);
} else if (annotation instanceof Multipart) {
if (isFormEncoded) {
throw methodError("Only one encoding annotation is allowed.");
}
isMultipart = true;
} else if (annotation instanceof FormUrlEncoded) {
if (isMultipart) {
throw methodError("Only one encoding annotation is allowed.");
}
isFormEncoded = true;
}
}
// 解析方法上的注解参数值 value:"doPost"
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
if (this.httpMethod != null) {
throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
this.httpMethod, httpMethod);
}
this.httpMethod = httpMethod;
this.hasBody = hasBody;
if (value.isEmpty()) {
return;
}
// 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:"favorite/add"
}
解析这个之后会用到 toRequest 方法中,在 OkHttpCall 中被调用.
在 parseParameterAnnotation 中会解析参数的注解,并构建响应的注解类型,在这个例子中会创建两个 Query 类. parseParameterAnnotation 中主要做了构建对应请求类型的类,这里就不贴代码了.
简单看下其中一个请求类型的类 Field :
static final class Query<T> extends ParameterHandler<T> {
private final String name;// 参数名
private final Converter<T, String> valueConverter;
private final boolean encoded;
Query(String name, Converter<T, String> valueConverter, boolean encoded) {
this.name = checkNotNull(name, "name == null");
this.valueConverter = valueConverter;
this.encoded = encoded;
}
@Override void apply(RequestBuilder builder, T value) throws IOException {
if (value == null) return; // Skip null values.
// value:参数值,例如得到的是"xiaocai"
builder.addQueryParam(name, valueConverter.convert(value), encoded);
}
}
我们再来看下ServiceMethod对外提供的方法
toRequest 构建请求体
/** Builds an HTTP request from method arguments. */
Request toRequest(Object... args) throws IOException {
// 创建请求体
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
contentType, hasBody, isFormEncoded, isMultipart);
@SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
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();
}
toResponse 解析响应体
/** Builds a method return value from an HTTP response body. */
T toResponse(ResponseBody body) throws IOException {
return responseConverter.convert(body);
}
这里就交给转换器去解析请求体,可以看下一个最简单的解析 StringConverter:
static final class StringConverter implements Converter<String, String> {
static final StringConverter INSTANCE = new StringConverter();
@Override public String convert(String value) throws IOException {
return value;
}
}
parsePathParameters 解析参数
* Gets the set of unique path parameters used in the given URI. If a parameter is used twice
* in the URI, it will only show up once in the set.
*/
static Set<String> parsePathParameters(String path) {
Matcher m = PARAM_URL_REGEX.matcher(path);
Set<String> patterns = new LinkedHashSet<>();
while (m.find()) {
patterns.add(m.group(1));
}
return patterns;
}
到此我们已经已经分析完解析接口的操作,其中使用动态代理解析接口,ServiceMethod 中解析单个接口中的方法的注解.
回到动态代理中,ServiceMethod主要是给OkHttpCall调用.所以接下来分析OkHttpCall的作用.
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
这里调用了callAdapter中的adapt方法,而 CallAdapter 只是个接口,我的工程中使用到了rxjava,可以看下其实现.
挑个简单的来看 SimpleCallAdapter:
static final class SimpleCallAdapter implements CallAdapter<Observable<?>> {
private final Type responseType;
private final Scheduler scheduler;
SimpleCallAdapter(Type responseType, Scheduler scheduler) {
this.responseType = responseType;
this.scheduler = scheduler;
}
@Override public Type responseType() {
return responseType;
}
@Override public <R> Observable<R> adapt(Call<R> call) {
Observable<R> observable = Observable.create(new CallOnSubscribe<>(call)) //
.lift(OperatorMapResponseToBodyOrError.<R>instance());
if (scheduler != null) {
return observable.subscribeOn(scheduler);
}
return observable;
}
}
具体的执行就到 rxjava 中了,这部分以后在进行分析.还是继续解析 OkHttpCall 吧.
2. OkHttpCall 解析
OkHttpCall 其实是对 okhttp 中的 Call 进行包装,真正实现 Call 的还是 okhttp 中的 RealCall.
OkHttpCall 发起请求的方法:
@Override public synchronized Request request() {
okhttp3.Call call = rawCall;
if (call != null) { // 相当于有缓存
return call.request();
}
if (creationFailure != null) { // 创建失败的情况
if (creationFailure instanceof IOException) {
throw new RuntimeException("Unable to create request.", creationFailure);
} else {
throw (RuntimeException) creationFailure;
}
}
try {
// 创建 okhttp3.Call 对象并发起请求
return (rawCall = createRawCall()).request();
} catch (RuntimeException e) {
creationFailure = e;
throw e;
} catch (IOException e) {
creationFailure = e;
throw new RuntimeException("Unable to create request.", e);
}
}
创建 okhttp3.Call 对象
// 创建 okhttp3.Call 对象
private okhttp3.Call createRawCall() throws IOException {
Request request = serviceMethod.toRequest(args); // 获取请求体
// serviceMethod.callFactory 里保存的就是 okhttpclient
okhttp3.Call call = serviceMethod.callFactory.newCall(request);
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
构建请求体并发起请求,其实就是调用 okhttpclient 中的响应的方法,也就体现了 OkHttpCall 是个包装类.
OkHttpCall也分为同步和异步.先看同步请求方法:
@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()); // 执行请求并解析响应体
}
里面大多是校验判断工作,createRawCall 也就是上面的创建请求体.
最后一行执行请求,调用的是 okhttp3.Call 中的执行方法,然后进行解析响应体:
Response<T> parseResponse(okhttp3.Response rawResponsy's source (the only stateful object) so we can pass the response along.
rawResponse = rawRe) throws IOException {
ResponseBody rawBody = rawResponse.body();
// Remove the bodesponse.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
int code = rawResponse.code();
if (code < 200 || code >= 300) { // 请求失败
try {
// Buffer the entire body to avoid future I/O.
ResponseBody bufferedBody = Utils.buffer(rawBody);
return Response.error(bufferedBody, rawResponse); // 返回失败状态的响应体
} finally {
rawBody.close();
}
}
// 请求成功,但没有响应体.可以了解下这两个状态具体代表什么:
// HTTP 204(no content)表示响应执行成功,但没有数据返回,浏览器不用刷新,不用导向新页面。
// HTTP 205(reset content) 表示响应执行成功,重置页面(Form表单),方便用户下次输入。
if (code == 204 || code == 205) {
return Response.success(null, rawResponse);
}
ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
try {
T body = serviceMethod.toResponse(catchingBody); // 交给 serviceMethod 处理响应体
return Response.success(body, rawResponse); // 返回成功时的响应体
} catch (RuntimeException e) {
// If the underlying source threw an exception, propagate that rather than indicating it was
// a runtime exception.
catchingBody.throwIfCaught();
throw e;
}
}
还是那句话,OkHttpCall其实没干什么大事,包装类.
异步请求:
@Override public void enqueue(final Callback<T> callback) {
if (callback == null) throw new NullPointerException("callback == null");
okhttp3.Call call;
Throwable failure;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
call = rawCall;
failure = creationFailure;
if (call == null && failure == null) {
try {
call = rawCall = createRawCall(); // 构建 okhttp3.Call 对象
} catch (Throwable t) {
failure = creationFailure = t;
}
}
}
if (failure != null) {
callback.onFailure(this, failure);
return;
}
if (canceled) {
call.cancel();
}
// 调用 okhttp3.Call 对象的异步请求方法
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
throws IOException {
Response<T> response;
try {
response = parseResponse(rawResponse); // 解析响应体
} catch (Throwable e) {
callFailure(e);
return;
}
callSuccess(response); // 回调响应成功的方法
}
@Override public void onFailure(okhttp3.Call call, IOException e) {
try {
callback.onFailure(OkHttpCall.this, e); // 回调响应失败的方法
} catch (Throwable t) {
t.printStackTrace();
}
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e); // 回调响应失败的方法
} catch (Throwable t) {
t.printStackTrace();
}
}
private void callSuccess(Response<T> response) {
try {
callback.onResponse(OkHttpCall.this, response); // 回调响应失败的方法
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}
到此 OkHttpCall 整体已经分析完成.
在整体看下 retrofit,涉及的类并不多
整体都是对 okhttp 进行封装,是的调用起来更加清爽,而且扩展性高,其中的转换器可以自己添加.配合 rxjava 效果更佳.
看源码时不仅理解原理,更重要的是学习代码结构,看看大神们都是怎样做封装/解耦/扩展性的.