簡介
retrofit是square出品的一個優秀的網路框架,注意,不是一個網路引擎。它的定位和Volley是一樣的。
它完成了封裝請求,執行緒切換,資料裝換等一系列工作,如果自己有能力也可以封裝一個這種框架,本質上是沒有區別的。
retrofit使用的網路引擎是OkHttp.
而OKHttp和HTTPClient,HttpUrlConnection是一個級別的。
使用
//1 建立網路請求介面類
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
//2 建立Retrofit例項物件
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
//3 通過動態代理建立網路介面代理物件
GitHubService service = retrofit.create(GitHubService.class);
//4 獲取Call物件
Call<List<Repo>> repos = service.listRepos("octocat");
//5 執行同步請求或非同步請求
repos.execute();
repos.enqueue(callback)
Retrofit
Retrofit也是使用Build模式建立的。
builder類有這些方法。從圖表可以看出,我們可以呼叫client方法傳入一個我們自定義的OkhttpClient,
呼叫baseUrl方法傳入Host,最後調動build方法生成一個Retrofit 物件
public Retrofit build() {
//baseUrl是必須的
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
//如果沒有設定callFactory物件,系統自動生成一個OkhttpClient物件.因為OKHttpclient實現了 Call.Factory介面
// public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}
//如果沒有設定callbackExecutor,系統自動生成一個,platform.defaultCallbackExecutor,這個platform是無參構造方法裡呼叫Platform.get()方法得到的。
/**
public Builder() {
this(Platform.get());
}**/
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
// Make a defensive copy of the converters.
List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);
}
Platform
class Platform {
private static final Platform PLATFORM = findPlatform();
static Platform get() {
return PLATFORM;
}
private static Platform findPlatform() {
try {
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0) {
return new Android();
}
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName("java.util.Optional");
return new Java8();
} catch (ClassNotFoundException ignored) {
}
try {
//怎麼還有IOS程式碼呢?
Class.forName("org.robovm.apple.foundation.NSObject");
return new IOS();
} catch (ClassNotFoundException ignored) {
}
return new Platform();
}
Executor defaultCallbackExecutor() {
return null;
}
CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
if (callbackExecutor != null) {
return new ExecutorCallAdapterFactory(callbackExecutor);
}
return DefaultCallAdapterFactory.INSTANCE;
}
boolean isDefaultMethod(Method method) {
return false;
}
Object invokeDefaultMethod(Method method, Class<?> declaringClass, Object object, Object... args)
throws Throwable {
throw new UnsupportedOperationException();
}
static class Android extends Platform {
@Override public Executor defaultCallbackExecutor() {
return new MainThreadExecutor();
}
@Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
return new ExecutorCallAdapterFactory(callbackExecutor);
}
static class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override public void execute(Runnable r) {
handler.post(r);
}
}
}
}
Retrofit 要求必須將請求API寫到一個interface介面檔案裡,這是動態代理特性要求的。
從介面檔案裡我們可以看到,我們將每個請求用這種形式表達
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
從介面檔案我們可以看出,一個請求介面被各種註解所表示。
我們知道一個方法有一下關鍵欄位組成
首先一個方法必須有描述符,返回值,方法名,引數型別,引數構成。
那我們用一個方法表示一個http請求需要哪些東西呢?
Http請求,首先我們得知道是GET請求還是POST請求,
然後就是請求頭資訊,請求路徑,查詢引數等等。
POST請求還需要Body。
Retrofit 已經提供了足夠的註解來表示一個方法。
Retrofit的核心思想AOP,面向切面變成,通過動態代理的反射,將介面檔案裡的每個方法記性處理,也就是分析該方法的註解生成一個ServiceMethod類。
Retrofit 裡有個關鍵的類,ServiceMethod
@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, 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 serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
從第3步我們可以看出create方法的實現就是使用了動態代理,在執行時生成了GitHubService物件。
//建立ServiceMethod物件
ServiceMethod serviceMethod = loadServiceMethod(method);
ServiceMethod loadServiceMethod(Method method) {
ServiceMethod result;
synchronized (serviceMethodCache) {
//先從換從中取改方法對應的ServiceMethod物件,如果為null就構建一個ServiceMethod物件並存入到map中,如果不為null直接返回
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
我們可以看到loadServiceMethod(Method method)方法返回了一個ServiceMethod物件
這個serviceMethodCache物件是Retrofit的一個欄位,是一個Map集合。
private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
將介面檔案裡每個方法轉換為一個ServiceMethod物件後放入改map中作為快取,下次呼叫該方法後就不用再次解析改方法物件了,直接從改map裡去以方法為key去取對應的ServiceMethod就行了。666
接下來看一下ServiceMethod物件的構造
ServiceMethod
final class ServiceMethod<T> {
// Upper and lower characters, digits, underscores, and hyphens, starting with a character.
static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
static final Pattern PARAM_URL_REGEX = Pattern.compile("\{(" + PARAM + ")\}");
static final Pattern PARAM_NAME_REGEX = Pattern.compile(PARAM);
final okhttp3.Call.Factory callFactory;
final CallAdapter<?> callAdapter;
private final HttpUrl baseUrl; 主機地址
private final Converter<ResponseBody, T> responseConverter;
private final String httpMethod;
private final String relativeUrl; 相對路徑
private final Headers headers; 請求頭部資訊
private final MediaType contentType; 請求引數型別
private final boolean hasBody; 是否有請求體
private final boolean isFormEncoded; 是否是格式化的表單
private final boolean isMultipart; 是不是分塊
private final ParameterHandler<?>[] parameterHandlers;
ServiceMethod(Builder<T> builder) {
this.callFactory = builder.retrofit.callFactory();
this.callAdapter = builder.callAdapter;
this.baseUrl = builder.retrofit.baseUrl();
this.responseConverter = builder.responseConverter;
this.httpMethod = builder.httpMethod;
this.relativeUrl = builder.relativeUrl;
this.headers = builder.headers;
this.contentType = builder.contentType;
this.hasBody = builder.hasBody;
this.isFormEncoded = builder.isFormEncoded;
this.isMultipart = builder.isMultipart;
this.parameterHandlers = builder.parameterHandlers;
}
}
ServiceMethod是採用Builder模式建立的。
static final class Builder<T> {
final Retrofit retrofit;
final Method method; //介面裡生命的方法
final Annotation[] methodAnnotations; //方法的註解,get/post/header之類的
final Annotation[][] parameterAnnotationsArray; //方法的引數註解陣列,二維陣列
final Type[] parameterTypes; //方法的引數陣列
Type responseType;
boolean gotField;
boolean gotPart;
boolean gotBody;
boolean gotPath;
boolean gotQuery;
boolean gotUrl;
String httpMethod;
boolean hasBody;
boolean isFormEncoded;
boolean isMultipart;
String relativeUrl;
Headers headers;
MediaType contentType;
Set<String> relativeUrlParamNames;
ParameterHandler<?>[] parameterHandlers;
Converter<ResponseBody, T> responseConverter;
CallAdapter<?> callAdapter;
public Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations(); //獲取方法的註解
this.parameterTypes = method.getGenericParameterTypes(); //獲取被註解修飾的方法,一個陣列
this.parameterAnnotationsArray = method.getParameterAnnotations(); //獲取方法的引數註解資訊,是一個二維陣列
}
Builder的構造引數需要一個Retrofit物件和一個Method物件。
首先解析方法物件,將其註解和引數註解放到對應的陣列裡。
首先在構造方法裡獲取該方法的註解,方法的引數,以及每個引數的註解。
關鍵就在build方法,在build方法裡對方法做了一個徹底的分解
public ServiceMethod build() {
//1 處理返回結果,做一定的轉換
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();
//2提取方法的註解
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
//如果httpMethod為null,即沒有使用方法型別註解修飾,丟擲異常進行提示
if (httpMethod == null) {
throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
//如果沒有請求體,即使用了GET,HEAD,DELETE,OPTIONS等所修飾,即不涉及到表單的提交,但是同時使用了Multipart,或者FormUrlEncoded所修飾,就報錯
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).");
}
}
//3提取方法的引數
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);
}
//相對路徑為null且gotURL為false的話,丟擲異常,因為沒有相對路徑無法請求。
if (relativeUrl == null && !gotUrl) {
throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
}
//沒有使用@FormUrlEncoded,@Multipart主機並且hasBody為false,但是gotBody為true,丟擲異常,提示
Non-Body型別的HTTP method 不能引數不能使用@Body註解
if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
throw methodError("Non-body HTTP method cannot contain @Body.");
}
//使用@FormUrlEncoded修飾的方法中的引數至少有一個引數被@Field註解修飾
if (isFormEncoded && !gotField) {
throw methodError("Form-encoded method must contain at least one @Field.");
}
//使用@Multipart修飾的方法中的引數至少有一個引數被@Part註解修飾
if (isMultipart && !gotPart) {
throw methodError("Multipart method must contain at least one @Part.");
}
//4 當前Builder物件初始化完畢,可以用來夠著ServiceMethod物件。
return new ServiceMethod<>(this);
}
處理返回結果
private CallAdapter<?> createCallAdapter() {
//獲取方法的返回結果,如果有不能解析的型別則丟擲異常,也就是說介面中定義的方法的返回值不能使用泛型
Type returnType = method.getGenericReturnType();
if (Utils.hasUnresolvableType(returnType)) {
throw methodError(
"Method return type must not include a type variable or wildcard: %s", returnType);
}
//介面裡的方法不能返回void
if (returnType == void.class) {
throw methodError("Service methods cannot return void.");
}
Annotation[] annotations = method.getAnnotations();
try {
return retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
//使用者自定義的Adapter可能不能正確的處理返回結果,這時候丟擲異常
throw methodError(e, "Unable to create call adapter for %s", returnType);
}
}
解析方法註解
1處處理方法的註解,就是先處理GET/POST/Header等註解資訊
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) {
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) {//如果是Multipart註解
if (isFormEncoded) {
//如果同時使用了FormUrlEncoded註解報錯
throw methodError("Only one encoding annotation is allowed.");
}
isMultipart = true;
} else if (annotation instanceof FormUrlEncoded) {
if (isMultipart) {
//如果同時使用了Multipart註解報錯,從這我們可以看出一個方法不能同時被Multipart和FormUrlEncoded所修飾
throw methodError("Only one encoding annotation is allowed.");
}
isFormEncoded = true;
}
}
然後根據具體的註解型別,在做進一步的處理,這裡主要分析GET/POST/HEADER/ 等註解
@GET
else if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
}
get型別的請求,沒有請求體
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
//如果該Builder已經有HTTPMethod了就不能改變了,直接拋異常
if (this.httpMethod != null) {
throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
this.httpMethod, httpMethod);
}
//將HTTPMethod賦值給httpMethod物件,Get、Post、Delete等
this.httpMethod = httpMethod;
this.hasBody = hasBody;//是否有請求體
//如果value為null,返回,因為value引數的值其實就是relativeURL。所以不能為null
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()) {
//如果在value裡面找到裡查詢引數的話,丟擲異常。因為查詢引數可以使用@Query註解來動態配置。
throw methodError("URL query string "%s" must not have replace block. "
+ "For dynamic query parameters use @Query.", queryParams);
}
}
this.relativeUrl = value; //將value賦值給relativeUrl
this.relativeUrlParamNames = parsePathParameters(value); //獲取value裡面的path佔位符,如果有的話
}
再來看下解析value裡的path佔位符的方法。
/**
獲取已知URI裡面的路徑集合,如果一個引數被使用了兩次,它只會在set中出現一次,好拗口啊,使用LinkedHashSet來儲存path引數集合,保證了路徑引數的順序。
* 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;
}
至此,GET方法的相關的註解分析完畢
@POST
else if (annotation instanceof POST) {
parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
}
POST型別的請求,沒有請求體。所以hasBody引數為true。
parseHttpMethodAndPath()方法已將在GET方法裡面分析過了,這裡面都一樣。
其他的請求型別也是大同小異。
然後接著分析方法的Header註解
@Headers
else if (annotation instanceof retrofit2.http.Headers) {
// 首先獲取Headers註解的值,是一個字串陣列。
String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
如果header註解長度為0,丟擲異常,所以使用了header註解必須設定值,不能存在空的header
if (headersToParse.length == 0) {
throw methodError("@Headers annotation is empty.");
}
處理header資訊,我猜肯定是一個map
headers = parseHeaders(headersToParse);
啊,居然不是,666.因為header不是KV結構的資料型別,而是一個key可以對應多個值。理論上可以使用Map<String,Set<String>>表示。
private Headers parseHeaders(String[] headers) {
Headers.Builder builder = new Headers.Builder();
for (String header : headers) {
// header以“:"分割,前面是key,後面是value
int colon = header.indexOf(`:`);
if (colon == -1 || colon == 0 || colon == header.length() - 1) {
//header必須是key:value格式表示,不然報錯
throw methodError(
"@Headers value must be in the form "Name: Value". Found: "%s"", header);
}
String headerName = header.substring(0, colon); //key值
String headerValue = header.substring(colon + 1).trim(); //value值,必須是一個陣列,艹,又猜錯了。
if ("Content-Type".equalsIgnoreCase(headerName)) {
//遇到"Content-Type"欄位。還需要獲得具體的MediaType。
MediaType type = MediaType.parse(headerValue);
if (type == null) {
//如果mediaType為null。丟擲一個type畸形的錯誤。
throw methodError("Malformed content type: %s", headerValue);
}
contentType = type;
} else {
將header的key和value加入到Builder裡面。
builder.add(headerName, headerValue);
}
}
最後呼叫build方法生成一個Header對愛。
return builder.build();
}
/**
* Add a header with the specified name and value. Does validation of header names and values.
*/
public Builder add(String name, String value) {
checkNameAndValue(name, value);
return addLenient(name, value);
}
Builder addLenient(String name, String value) {
namesAndValues.add(name);
namesAndValues.add(value.trim());
return this;
}
final List<String> namesAndValues = new ArrayList<>(20);
namesAndValues是Header.Builder類的一種子段。可見在Builder內部header資訊是按照key/value異常放到一個String集合裡面的。為什麼不放到一個Map裡面呢,不懂。
總之,最後就是講方法的Headers註解資訊提取完畢。
處理方法引數
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);
}
//獲取第p個引數的註解陣列,如果沒有註解丟擲異常,可見,使用了Retrofit,介面方法中每個引數都必須使用註解進行修飾。
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
if (parameterAnnotations == null) {
throw parameterError(p, "No Retrofit annotation found.");
}
//解析方法中的引數,存入parameterHandlers[]陣列中。
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
引數校驗
Utils.hasUnresolvableType(parameterType),這個方法是對引數的型別做個校驗。
static boolean hasUnresolvableType(Type type) {
//如果引數是引用資料型別,返回false,可見,介面定義中方法的引數只能是基本資料型別
if (type instanceof Class<?>) {
return false;
}
//如果引數是泛型
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
//去除泛型類中的實際型別,遍歷
for (Type typeArgument : parameterizedType.getActualTypeArguments()) {
//如果有一個泛型引數是基本資料型別,返回true,都不是返回false
if (hasUnresolvableType(typeArgument)) {
return true;
}
}
return false;
}
//如果引數是泛型陣列型別
if (type instanceof GenericArrayType) {
return hasUnresolvableType(((GenericArrayType) type).getGenericComponentType());
}
if (type instanceof TypeVariable) {
return true;
}
if (type instanceof WildcardType) {
return true;
}
String className = type == null ? "null" : type.getClass().getName();
throw new IllegalArgumentException("Expected a Class, ParameterizedType, or "
+ "GenericArrayType, but <" + type + "> is of type " + className);
}
解析引數
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
private ParameterHandler<?> parseParameter(
int p, Type parameterType, Annotation[] annotations) {
ParameterHandler<?> result = null;
//遍歷引數的註解陣列,呼叫parseParameterAnnotation()
for (Annotation annotation : annotations) {
ParameterHandler<?> annotationAction = parseParameterAnnotation(
p, parameterType, annotations, annotation);
//如果該註解沒有返回,則解析下一個註解
if (annotationAction == null) {
continue;
}
if (result != null) {
throw parameterError(p, "Multiple Retrofit annotations found, only one allowed.");
}
result = annotationAction; //將解析的結果賦值給Result
}
//如果註解為null,丟擲異常。這個地方永遠不會呼叫,因為在獲取註解陣列之前就做過判斷了,如果註解陣列為null,直接拋異常,Line197-Line200 in ServiceMethod.Builder中
if (result == null) {
throw parameterError(p, "No Retrofit annotation found.");
}
return result;
}
獲取引數註解資訊
再來看看parseParameterAnnotation()方法,內容略多
private ParameterHandler<?> parseParameterAnnotation(
int p, Type type, Annotation[] annotations, Annotation annotation) {
if (annotation instanceof Url) {
//如果使用了Url註解,
if (gotUrl) {
//如果gotUrl為true,因為gotURL預設為false,說明之前處理過Url註解了,丟擲多個@Url註解異常
throw parameterError(p, "Multiple @Url method annotations found.");
}
if (gotPath) {
//如果gotPath為true,丟擲異常,說明@Path註解不能和@Url註解一起使用
throw parameterError(p, "@Path parameters may not be used with @Url.");
}
if (gotQuery) {
//如果gotQuery為true,丟擲異常,說明@Url註解不能用在@Query註解後面
throw parameterError(p, "A @Url parameter must not come after a @Query");
}
if (relativeUrl != null) {
//如果relativeUrl不為null,丟擲異常,說明使用了@Url註解,relativeUrl必須為null
throw parameterError(p, "@Url cannot be used with @%s URL", httpMethod);
}
gotUrl = true;
----------------------------------------------------------------------------------------
//如果引數型別是HttpURL,String,URI或者引數型別是“android.net.Uri",返回ParameterHandler.RelativeUrl(),實際是交由這個類處理
if (type == HttpUrl.class
|| type == String.class
|| type == URI.class
|| (type instanceof Class && "android.net.Uri".equals(((Class<?>) type).getName()))) {
return new ParameterHandler.RelativeUrl();
} else {
//不然就丟擲異常,也就是說@Url註解必須使用在okhttp3.HttpUrl, String, java.net.URI, or android.net.Uri 這幾種型別的引數上。
throw parameterError(p,
"@Url must be okhttp3.HttpUrl, String, java.net.URI, or android.net.Uri type.");
}
------------------------------------------------------------------------------------------
} else if (annotation instanceof Path) { //@Path註解
//如果gotQuery為true。丟擲異常,因為@Path修飾的引數是路徑的佔位符。不是查詢引數,不能使用@Query註解修飾
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.");
}
//如果相對路徑為null,那@path註解也就無意義了。
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(); //獲取@Path註解的值
validatePathName(p, name); //對改值進行校驗,1該value必須是合法字元,2:該相對路徑必須包含相應的佔位符
//然後將改引數的所有註解進行處理,最終呼叫ParameterHandler.Path進行處理。
Converter<?, String> converter = retrofit.stringConverter(type, annotations);
return new ParameterHandler.Path<>(name, converter, path.encoded());
} else if (annotation instanceof Query) { //Query註解,看不太懂,最後也是呼叫ParameterHandler.Query進行處理
Query query = (Query) annotation;
String name = query.value();
boolean encoded = query.encoded();
Class<?> rawParameterType = Utils.getRawType(type);
gotQuery = true;
if (Iterable.class.isAssignableFrom(rawParameterType)) {
if (!(type instanceof ParameterizedType)) {
throw parameterError(p, rawParameterType.getSimpleName()
+ " must include generic type (e.g., "
+ rawParameterType.getSimpleName()
+ "<String>)");
}
ParameterizedType parameterizedType = (ParameterizedType) type;
Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
Converter<?, String> converter =
retrofit.stringConverter(iterableType, annotations);
return new ParameterHandler.Query<>(name, converter, encoded).iterable();
} else if (rawParameterType.isArray()) {
Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
Converter<?, String> converter =
retrofit.stringConverter(arrayComponentType, annotations);
return new ParameterHandler.Query<>(name, converter, encoded).array();
} else {
Converter<?, String> converter =
retrofit.stringConverter(type, annotations);
return new ParameterHandler.Query<>(name, converter, encoded);
}
} else if (annotation instanceof QueryMap) {
Class<?> rawParameterType = Utils.getRawType(type);
if (!Map.class.isAssignableFrom(rawParameterType)) {
throw parameterError(p, "@QueryMap parameter type must be Map.");
}
Type mapType = Utils.getSupertype(type, rawParameterType, Map.class);
if (!(mapType instanceof ParameterizedType)) {
throw parameterError(p, "Map must include generic types (e.g., Map<String, String>)");
}
ParameterizedType parameterizedType = (ParameterizedType) mapType;
Type keyType = Utils.getParameterUpperBound(0, parameterizedType);
if (String.class != keyType) {
throw parameterError(p, "@QueryMap keys must be of type String: " + keyType);
}
Type valueType = Utils.getParameterUpperBound(1, parameterizedType);
Converter<?, String> valueConverter =
retrofit.stringConverter(valueType, annotations);
return new ParameterHandler.QueryMap<>(valueConverter, ((QueryMap) annotation).encoded());
} else if (annotation instanceof Header) {
Header header = (Header) annotation;
String name = header.value();
Class<?> rawParameterType = Utils.getRawType(type);
if (Iterable.class.isAssignableFrom(rawParameterType)) {
if (!(type instanceof ParameterizedType)) {
throw parameterError(p, rawParameterType.getSimpleName()
+ " must include generic type (e.g., "
+ rawParameterType.getSimpleName()
+ "<String>)");
}
ParameterizedType parameterizedType = (ParameterizedType) type;
Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
Converter<?, String> converter =
retrofit.stringConverter(iterableType, annotations);
return new ParameterHandler.Header<>(name, converter).iterable();
} else if (rawParameterType.isArray()) {
Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
Converter<?, String> converter =
retrofit.stringConverter(arrayComponentType, annotations);
return new ParameterHandler.Header<>(name, converter).array();
} else {
Converter<?, String> converter =
retrofit.stringConverter(type, annotations);
return new ParameterHandler.Header<>(name, converter);
}
} else if (annotation instanceof HeaderMap) {
Class<?> rawParameterType = Utils.getRawType(type);
if (!Map.class.isAssignableFrom(rawParameterType)) {
throw parameterError(p, "@HeaderMap parameter type must be Map.");
}
Type mapType = Utils.getSupertype(type, rawParameterType, Map.class);
if (!(mapType instanceof ParameterizedType)) {
throw parameterError(p, "Map must include generic types (e.g., Map<String, String>)");
}
ParameterizedType parameterizedType = (ParameterizedType) mapType;
Type keyType = Utils.getParameterUpperBound(0, parameterizedType);
if (String.class != keyType) {
throw parameterError(p, "@HeaderMap keys must be of type String: " + keyType);
}
Type valueType = Utils.getParameterUpperBound(1, parameterizedType);
Converter<?, String> valueConverter =
retrofit.stringConverter(valueType, annotations);
return new ParameterHandler.HeaderMap<>(valueConverter);
} else if (annotation instanceof Field) {
if (!isFormEncoded) {
throw parameterError(p, "@Field parameters can only be used with form encoding.");
}
Field field = (Field) annotation;
String name = field.value();
boolean encoded = field.encoded();
gotField = true;
Class<?> rawParameterType = Utils.getRawType(type);
if (Iterable.class.isAssignableFrom(rawParameterType)) {
if (!(type instanceof ParameterizedType)) {
throw parameterError(p, rawParameterType.getSimpleName()
+ " must include generic type (e.g., "
+ rawParameterType.getSimpleName()
+ "<String>)");
}
ParameterizedType parameterizedType = (ParameterizedType) type;
Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
Converter<?, String> converter =
retrofit.stringConverter(iterableType, annotations);
return new ParameterHandler.Field<>(name, converter, encoded).iterable();
} else if (rawParameterType.isArray()) {
Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
Converter<?, String> converter =
retrofit.stringConverter(arrayComponentType, annotations);
return new ParameterHandler.Field<>(name, converter, encoded).array();
} else {
Converter<?, String> converter =
retrofit.stringConverter(type, annotations);
return new ParameterHandler.Field<>(name, converter, encoded);
}
} else if (annotation instanceof FieldMap) {
if (!isFormEncoded) {
throw parameterError(p, "@FieldMap parameters can only be used with form encoding.");
}
Class<?> rawParameterType = Utils.getRawType(type);
if (!Map.class.isAssignableFrom(rawParameterType)) {
throw parameterError(p, "@FieldMap parameter type must be Map.");
}
Type mapType = Utils.getSupertype(type, rawParameterType, Map.class);
if (!(mapType instanceof ParameterizedType)) {
throw parameterError(p,
"Map must include generic types (e.g., Map<String, String>)");
}
ParameterizedType parameterizedType = (ParameterizedType) mapType;
Type keyType = Utils.getParameterUpperBound(0, parameterizedType);
if (String.class != keyType) {
throw parameterError(p, "@FieldMap keys must be of type String: " + keyType);
}
Type valueType = Utils.getParameterUpperBound(1, parameterizedType);
Converter<?, String> valueConverter =
retrofit.stringConverter(valueType, annotations);
gotField = true;
return new ParameterHandler.FieldMap<>(valueConverter, ((FieldMap) annotation).encoded());
} else if (annotation instanceof Part) {
if (!isMultipart) {
throw parameterError(p, "@Part parameters can only be used with multipart encoding.");
}
Part part = (Part) annotation;
gotPart = true;
String partName = part.value();
Class<?> rawParameterType = Utils.getRawType(type);
if (partName.isEmpty()) {
if (Iterable.class.isAssignableFrom(rawParameterType)) {
if (!(type instanceof ParameterizedType)) {
throw parameterError(p, rawParameterType.getSimpleName()
+ " must include generic type (e.g., "
+ rawParameterType.getSimpleName()
+ "<String>)");
}
ParameterizedType parameterizedType = (ParameterizedType) type;
Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
if (!MultipartBody.Part.class.isAssignableFrom(Utils.getRawType(iterableType))) {
throw parameterError(p,
"@Part annotation must supply a name or use MultipartBody.Part parameter type.");
}
return ParameterHandler.RawPart.INSTANCE.iterable();
} else if (rawParameterType.isArray()) {
Class<?> arrayComponentType = rawParameterType.getComponentType();
if (!MultipartBody.Part.class.isAssignableFrom(arrayComponentType)) {
throw parameterError(p,
"@Part annotation must supply a name or use MultipartBody.Part parameter type.");
}
return ParameterHandler.RawPart.INSTANCE.array();
} else if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) {
return ParameterHandler.RawPart.INSTANCE;
} else {
throw parameterError(p,
"@Part annotation must supply a name or use MultipartBody.Part parameter type.");
}
} else {
Headers headers =
Headers.of("Content-Disposition", "form-data; name="" + partName + """,
"Content-Transfer-Encoding", part.encoding());
if (Iterable.class.isAssignableFrom(rawParameterType)) {
if (!(type instanceof ParameterizedType)) {
throw parameterError(p, rawParameterType.getSimpleName()
+ " must include generic type (e.g., "
+ rawParameterType.getSimpleName()
+ "<String>)");
}
ParameterizedType parameterizedType = (ParameterizedType) type;
Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);
if (MultipartBody.Part.class.isAssignableFrom(Utils.getRawType(iterableType))) {
throw parameterError(p, "@Part parameters using the MultipartBody.Part must not "
+ "include a part name in the annotation.");
}
Converter<?, RequestBody> converter =
retrofit.requestBodyConverter(iterableType, annotations, methodAnnotations);
return new ParameterHandler.Part<>(headers, converter).iterable();
} else if (rawParameterType.isArray()) {
Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());
if (MultipartBody.Part.class.isAssignableFrom(arrayComponentType)) {
throw parameterError(p, "@Part parameters using the MultipartBody.Part must not "
+ "include a part name in the annotation.");
}
Converter<?, RequestBody> converter =
retrofit.requestBodyConverter(arrayComponentType, annotations, methodAnnotations);
return new ParameterHandler.Part<>(headers, converter).array();
} else if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) {
throw parameterError(p, "@Part parameters using the MultipartBody.Part must not "
+ "include a part name in the annotation.");
} else {
Converter<?, RequestBody> converter =
retrofit.requestBodyConverter(type, annotations, methodAnnotations);
return new ParameterHandler.Part<>(headers, converter);
}
}
} else if (annotation instanceof PartMap) {
if (!isMultipart) {
throw parameterError(p, "@PartMap parameters can only be used with multipart encoding.");
}
gotPart = true;
Class<?> rawParameterType = Utils.getRawType(type);
if (!Map.class.isAssignableFrom(rawParameterType)) {
throw parameterError(p, "@PartMap parameter type must be Map.");
}
Type mapType = Utils.getSupertype(type, rawParameterType, Map.class);
if (!(mapType instanceof ParameterizedType)) {
throw parameterError(p, "Map must include generic types (e.g., Map<String, String>)");
}
ParameterizedType parameterizedType = (ParameterizedType) mapType;
Type keyType = Utils.getParameterUpperBound(0, parameterizedType);
if (String.class != keyType) {
throw parameterError(p, "@PartMap keys must be of type String: " + keyType);
}
Type valueType = Utils.getParameterUpperBound(1, parameterizedType);
if (MultipartBody.Part.class.isAssignableFrom(Utils.getRawType(valueType))) {
throw parameterError(p, "@PartMap values cannot be MultipartBody.Part. "
+ "Use @Part List<Part> or a different value type instead.");
}
Converter<?, RequestBody> valueConverter =
retrofit.requestBodyConverter(valueType, annotations, methodAnnotations);
PartMap partMap = (PartMap) annotation;
return new ParameterHandler.PartMap<>(valueConverter, partMap.encoding());
} else if (annotation instanceof Body) {
if (isFormEncoded || isMultipart) {
throw parameterError(p,
"@Body parameters cannot be used with form or multi-part encoding.");
}
if (gotBody) {
throw parameterError(p, "Multiple @Body method annotations found.");
}
Converter<?, RequestBody> converter;
try {
converter = retrofit.requestBodyConverter(type, annotations, methodAnnotations);
} catch (RuntimeException e) {
// Wide exception range because factories are user code.
throw parameterError(e, p, "Unable to create @Body converter for %s", type);
}
gotBody = true;
return new ParameterHandler.Body<>(converter);
}
return null; // Not a Retrofit annotation.找不到該註解
}
從上面可以看出,改立引數註解的套路就是:先判斷該註解的型別,然後使用策略模式分別呼叫ParameterHandler裡對應的子類來處理
寫到這裡我已經暈了。暈暈乎乎好舒服
@Header
使用場景
有時候我們需要動態的設定請求header中的某個請求頭的值,這個時候就可以使用@Header來修飾個引數。
最終都是講header裡的資訊提取到Request裡面
static final class Header<T> extends ParameterHandler<T> {
private final String name;
private final Converter<T, String> valueConverter;
Header(String name, Converter<T, String> valueConverter) {
this.name = checkNotNull(name, "name == null");
this.valueConverter = valueConverter;
}
@Override void apply(RequestBuilder builder, T value) throws IOException {
if (value == null) return; // Skip null values.
builder.addHeader(name, valueConverter.convert(value));
}
}
void addHeader(String name, String value) {
if ("Content-Type".equalsIgnoreCase(name)) {
MediaType type = MediaType.parse(value);
if (type == null) {
throw new IllegalArgumentException("Malformed content type: " + value);
}
contentType = type;
} else {
requestBuilder.addHeader(name, value);
}
}
呼叫requestBuilder.addHeader()方法。
這個requestBuilder是OKHttp中Request的內部靜態類Builder類的一個物件。
private final Request.Builder requestBuilder;
從中我們可以看出最後將@Header註釋的引數的值解析後新增到Request物件中的Header資訊裡。
@Path
使用場景
有時候請求路徑是不定的,即請求路徑裡的某個segment是變化的,也就是需要我們使用引數來動態的改變,這個時候我們就需要使用@Path 來修飾這個引數
static final class Path<T> extends ParameterHandler<T> {
private final String name; //引數名,佔位符
private final Converter<T, String> valueConverter;
private final boolean encoded; //是否編碼
Path(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) {
throw new IllegalArgumentException(
"Path parameter "" + name + "" value must not be null.");
}
builder.addPathParam(name, valueConverter.convert(value), encoded);
}
}
void addPathParam(String name, String value, boolean encoded) {
if (relativeUrl == null) {
// The relative URL is cleared when the first query parameter is set.
throw new AssertionError();
}
//將佔位符”{name}”使用value替換
relativeUrl = relativeUrl.replace("{" + name + "}", canonicalizeForPath(value, encoded));
}
@Query
使用場景
@Query用來修飾介面方法中的查詢欄位
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.
builder.addQueryParam(name, valueConverter.convert(value), encoded);
}
}
//將查詢引數組合到相對路徑上。
void addQueryParam(String name, String value, boolean encoded) {
if (relativeUrl != null) {
// Do a one-time combination of the built relative URL and the base URL.
urlBuilder = baseUrl.newBuilder(relativeUrl);
if (urlBuilder == null) {
throw new IllegalArgumentException(
"Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl);
}
relativeUrl = null;
}
if (encoded) {
urlBuilder.addEncodedQueryParameter(name, value);
} else {
urlBuilder.addQueryParameter(name, value);
}
}
@QueryMap
使用場景
當介面中的一個 方法有比較多的查詢欄位時,全部定義到方法中時比較麻煩且容易出錯,這個使用我們完全可以將所有的查詢引數放到一個Map裡面。
可想而知,其內部實現必定是遍歷map ,然後像處理@Query引數一樣呼叫addQueryParam()處理每個查詢引數。
static final class FieldMap<T> extends ParameterHandler<Map<String, T>> {
private final Converter<T, String> valueConverter;
private final boolean encoded;
FieldMap(Converter<T, String> valueConverter, boolean encoded) {
this.valueConverter = valueConverter;
this.encoded = encoded;
}
@Override void apply(RequestBuilder builder, Map<String, T> value) throws IOException {
if (value == null) {
throw new IllegalArgumentException("Field map was null.");
}
for (Map.Entry<String, T> entry : value.entrySet()) {
String entryKey = entry.getKey();
if (entryKey == null) {
throw new IllegalArgumentException("Field map contained null key.");
}
T entryValue = entry.getValue();
if (entryValue == null) {
throw new IllegalArgumentException(
"Field map contained null value for key `" + entryKey + "`.");
}
//果然不假
builder.addFormField(entryKey, valueConverter.convert(entryValue), encoded);
}
}
}
@Field
使用場景
@Field註解一般用在表單引數的提交上
static final class Field<T> extends ParameterHandler<T> {
private final String name; //引數名字
private final Converter<T, String> valueConverter; //引數值轉換器
private final boolean encoded; //是否編碼
Field(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. 所以使用@Field修飾的欄位,是不會上傳到伺服器的。
//呼叫ResuestBuilder物件的具體想法來處理@Field修飾的表單欄位
builder.addFormField(name, valueConverter.convert(value), encoded);
}
}
void addFormField(String name, String value, boolean encoded) {
//根據引數值是否被編碼,呼叫不同的方法。formBuilder是OKHttp中的一個類。也是使用Builder模式建立的。
if (encoded) {
formBuilder.addEncoded(name, value);
} else {
formBuilder.add(name, value);
}
}
@FieldMap
@FieldMap
使用場景
假如表單引數有很多個,我們可以使用一個Map<String,String>來表示,然後使用@FieldMap註解來修飾該引數就行了。可想而知,如同@QueryMap一樣,其內部實現肯定是遍歷Map,然後像處理@Field引數一樣呼叫
builder.addFormField(name, valueConverter.convert(value), encoded);
@Body
使用場景
在以下需要提交表單的請求裡,我們可以使用@Field,@FieldMap,我們還可以使用@Body來修飾我們提交的表單資料,這個時候我們需要定義一個Bean類,Bean類的各個Field必須和表單欄位的key一樣
static final class Body<T> extends ParameterHandler<T> {
private final Converter<T, RequestBody> converter;
Body(Converter<T, RequestBody> converter) {
this.converter = converter;
}
@Override void apply(RequestBuilder builder, T value) {
if (value == null) {
throw new IllegalArgumentException("Body parameter value must not be null.");
}
RequestBody body;
try {
body = converter.convert(value);
} catch (IOException e) {
throw new RuntimeException("Unable to convert " + value + " to RequestBody", e);
}
builder.setBody(body);
}
}
這裡Retrofit並沒有像@Field一樣處理表單引數。仔細想想也對,因為凡是提交的表單資料都需要放到請求體裡面,即使使用@Field,@FieldMap提交的資料,最終還是需要放到請求體裡面。
@Part
@RawPart
@PartMap
以上三個註解都是使用修飾上傳檔案的引數的,
結論
從對上面的分析可以知道,我們在提取使用註解修飾的引數後將值存放到RequestBuilder物件裡。
這裡又引入了RequestBuilder類
RequestBuilder
final class RequestBuilder {
private static final char[] HEX_DIGITS =
{ `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `A`, `B`, `C`, `D`, `E`, `F` };
private static final String PATH_SEGMENT_ALWAYS_ENCODE_SET = " "<>^`{}|\?#";
private final String method; //方法型別
private final HttpUrl baseUrl; //scheme+host
private String relativeUrl; //相對路徑
private HttpUrl.Builder urlBuilder; //URL構造器
private final Request.Builder requestBuilder; //OkHttp中Request構造器
private MediaType contentType; //提交表單的資料型別
private final boolean hasBody; //是否有請求體
private MultipartBody.Builder multipartBuilder; //上傳檔案的構造器
private FormBody.Builder formBuilder; //表單資料的構造器
private RequestBody body; //請求體
RequestBuilder(String method, HttpUrl baseUrl, String relativeUrl, Headers headers,
MediaType contentType, boolean hasBody, boolean isFormEncoded, boolean isMultipart) {
this.method = method;
this.baseUrl = baseUrl;
this.relativeUrl = relativeUrl;
this.requestBuilder = new Request.Builder();
this.contentType = contentType;
this.hasBody = hasBody;
if (headers != null) {
requestBuilder.headers(headers);
}
if (isFormEncoded) {
// Will be set to `body` in `build`.
formBuilder = new FormBody.Builder();
} else if (isMultipart) {
// Will be set to `body` in `build`.
multipartBuilder = new MultipartBody.Builder();
multipartBuilder.setType(MultipartBody.FORM);
}
}
void setRelativeUrl(Object relativeUrl) {
if (relativeUrl == null) throw new NullPointerException("@Url parameter is null.");
this.relativeUrl = relativeUrl.toString();
}
void addHeader(String name, String value) {
if ("Content-Type".equalsIgnoreCase(name)) {
MediaType type = MediaType.parse(value);
if (type == null) {
throw new IllegalArgumentException("Malformed content type: " + value);
}
contentType = type;
} else {
requestBuilder.addHeader(name, value);
}
}
void addPathParam(String name, String value, boolean encoded) {
if (relativeUrl == null) {
// The relative URL is cleared when the first query parameter is set.
throw new AssertionError();
}
relativeUrl = relativeUrl.replace("{" + name + "}", canonicalizeForPath(value, encoded));
}
private static String canonicalizeForPath(String input, boolean alreadyEncoded) {
int codePoint;
for (int i = 0, limit = input.length(); i < limit; i += Character.charCount(codePoint)) {
codePoint = input.codePointAt(i);
if (codePoint < 0x20 || codePoint >= 0x7f
|| PATH_SEGMENT_ALWAYS_ENCODE_SET.indexOf(codePoint) != -1
|| (!alreadyEncoded && (codePoint == `/` || codePoint == `%`))) {
// Slow path: the character at i requires encoding!
Buffer out = new Buffer();
out.writeUtf8(input, 0, i);
canonicalizeForPath(out, input, i, limit, alreadyEncoded);
return out.readUtf8();
}
}
// Fast path: no characters required encoding.
return input;
}
private static void canonicalizeForPath(Buffer out, String input, int pos, int limit,
boolean alreadyEncoded) {
Buffer utf8Buffer = null; // Lazily allocated.
int codePoint;
for (int i = pos; i < limit; i += Character.charCount(codePoint)) {
codePoint = input.codePointAt(i);
if (alreadyEncoded
&& (codePoint == ` ` || codePoint == `
` || codePoint == `f` || codePoint == `
`)) {
// Skip this character.
} else if (codePoint < 0x20 || codePoint >= 0x7f
|| PATH_SEGMENT_ALWAYS_ENCODE_SET.indexOf(codePoint) != -1
|| (!alreadyEncoded && (codePoint == `/` || codePoint == `%`))) {
// Percent encode this character.
if (utf8Buffer == null) {
utf8Buffer = new Buffer();
}
utf8Buffer.writeUtf8CodePoint(codePoint);
while (!utf8Buffer.exhausted()) {
int b = utf8Buffer.readByte() & 0xff;
out.writeByte(`%`);
out.writeByte(HEX_DIGITS[(b >> 4) & 0xf]);
out.writeByte(HEX_DIGITS[b & 0xf]);
}
} else {
// This character doesn`t need encoding. Just copy it over.
out.writeUtf8CodePoint(codePoint);
}
}
}
void addQueryParam(String name, String value, boolean encoded) {
if (relativeUrl != null) {
// Do a one-time combination of the built relative URL and the base URL.
urlBuilder = baseUrl.newBuilder(relativeUrl);
if (urlBuilder == null) {
throw new IllegalArgumentException(
"Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl);
}
relativeUrl = null;
}
if (encoded) {
urlBuilder.addEncodedQueryParameter(name, value);
} else {
urlBuilder.addQueryParameter(name, value);
}
}
void addFormField(String name, String value, boolean encoded) {
if (encoded) {
formBuilder.addEncoded(name, value);
} else {
formBuilder.add(name, value);
}
}
void addPart(Headers headers, RequestBody body) {
multipartBuilder.addPart(headers, body);
}
void addPart(MultipartBody.Part part) {
multipartBuilder.addPart(part);
}
void setBody(RequestBody body) {
this.body = body;
}
Request build() {
HttpUrl url;
HttpUrl.Builder urlBuilder = this.urlBuilder;
if (urlBuilder != null) {
url = urlBuilder.build();
} else {
// No query parameters triggered builder creation, just combine the relative URL and base URL.
url = baseUrl.resolve(relativeUrl);
if (url == null) {
throw new IllegalArgumentException(
"Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl);
}
}
RequestBody body = this.body;
if (body == null) {
// Try to pull from one of the builders.
if (formBuilder != null) {
body = formBuilder.build();
} else if (multipartBuilder != null) {
body = multipartBuilder.build();
} else if (hasBody) {
// Body is absent, make an empty body.
body = RequestBody.create(null, new byte[0]);
}
}
MediaType contentType = this.contentType;
if (contentType != null) {
if (body != null) {
body = new ContentTypeOverridingRequestBody(body, contentType);
} else {
requestBuilder.addHeader("Content-Type", contentType.toString());
}
}
//生成一個Request物件
return requestBuilder
.url(url)
.method(method, body)
.build();
}
private static class ContentTypeOverridingRequestBody extends RequestBody {
private final RequestBody delegate;
private final MediaType contentType;
ContentTypeOverridingRequestBody(RequestBody delegate, MediaType contentType) {
this.delegate = delegate;
this.contentType = contentType;
}
@Override public MediaType contentType() {
return contentType;
}
@Override public long contentLength() throws IOException {
return delegate.contentLength();
}
@Override public void writeTo(BufferedSink sink) throws IOException {
delegate.writeTo(sink);
}
}
}
OkHttpCall
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
在建立了ServiceMethod物件後,使用該ServiceMethod物件和其引數建立一個OKHttPCall物件
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
在合適的時候呼叫ServiceMethod物件的toRequest方法生成一個Request物件,toReques()的內部實現就是呼叫RequestBuilder物件的build方法。
/** 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();
}
OkHttpCall 實現了Call介面,這個Call介面和OkHttp中的Call介面一樣,畢竟一家公司嘛。
其實就是對OkHttpCall 做了一層包裝。
最後方法的執行時通過呼叫
return serviceMethod.callAdapter.adapt(okHttpCall);
返回介面中方法定義的返回值。
這塊的流程就是構造一個OKHttp物件需要使用ServiceMethod物件和相應的引數。
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
最後建立具體的Call物件時
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;
}
呼叫了ServiceMethod物件的toRequest方法,然後使用這個request物件建立了一個Call物件。
/** 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]);
}
//生成一個Request物件
return requestBuilder.build();
}