Android開發—Retrofit2.9.0版本主流程分析
簡介
Retrofit turns your HTTP API into a Java interface.
Retrofit將HTTP API轉換為Java介面
本文基於Retrofit
2.9.0版本原始碼分析!
依賴新增:
com.squareup.retrofit2:retrofit:2.9.0
使用
我們在這以官網的Demo為例,來看下最簡單的使用:
public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user); } ① Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .build(); ② GitHubService service = retrofit.create(GitHubService.class); ③ Call<List<Repo>> repos = service.listRepos("octocat"); ④ repos.enqueue() ⑤
總體流程分為四步:
- 建立一個介面,內部方法都是介面呼叫的方法,使用註解的方式新增請求型別、引數和回撥資料等;
- 構建一個
Retrofit
物件,並且設定介面的baseUrl
; - 使用
retrofit.create(class)
代理出一個我們第一步建立的介面例項; - 使用介面例項執行具體的介面呼叫,返回
Call
物件; - 使用
call.enqueu()
方法執行網路請求。
第一步不用具體分析,只需要知道如果使用註解來新增我們請求內容即可,具體如何解析這些註解會在第三步呈現出來,下面直接看第二步Retrofit
的建立過程。
建立Retrofit
Retrofit.Builder().baseUrl(string)
retrofit2.Retrofit.Builder#baseUrl(java.lang.String) public Builder baseUrl(String baseUrl) { Objects.requireNonNull(baseUrl, "baseUrl == null"); return baseUrl(HttpUrl.get(baseUrl)); ① } public Builder baseUrl(HttpUrl baseUrl) { Objects.requireNonNull(baseUrl, "baseUrl == null"); List<String> pathSegments = baseUrl.pathSegments(); if (!"".equals(pathSegments.get(pathSegments.size() - 1))) { ② throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl); } this.baseUrl = baseUrl; ③ return this; }
Retrofit.Builder().baseUrl(string)
一共做了三個操作:
- 將傳入的
url
封裝成HttpUrl
物件; - 判斷
url
是否是以/
結尾,不是的話直接丟擲異常; - 將
HttpUrl
賦值給Builder.baseUrl
。
Builder.build()
retrofit2.Retrofit.Builder#build
public Retrofit build() {
// baseUrl不能為空
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
} ①
// 獲取平臺,Java/Android
Platform platform = Platform.get(); ②
// 建立OkHttpClient物件
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
} ③
// 建立回撥執行緒池物件
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
} ④
// 建立介面卡列表,並且新增預設的介面卡
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
List<? extends CallAdapter.Factory> defaultCallAdapterFactories =
platform.createDefaultCallAdapterFactories(callbackExecutor);
callAdapterFactories.addAll(defaultCallAdapterFactories); ⑤
// 建立預設的轉換器列表
List<? extends Converter.Factory> defaultConverterFactories =
platform.createDefaultConverterFactories();
int defaultConverterFactoriesSize = defaultConverterFactories.size();
List<Converter.Factory> converterFactories =
new ArrayList<>(1 + this.converterFactories.size() + defaultConverterFactoriesSize);
// Add the built-in converter factory first. This prevents overriding its behavior but also
// ensures correct behavior when using converters that consume all types.
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(defaultConverterFactories); ⑥
// 建立Retrofit物件
return new Retrofit(
callFactory,
baseUrl,
unmodifiableList(converterFactories),
defaultConverterFactoriesSize,
unmodifiableList(callAdapterFactories),
defaultCallAdapterFactories.size(),
callbackExecutor,
validateEagerly); ⑦
}
}
Builder.build()
方法中主要就是建立或者獲取一些重要的物件,一共包括6個物件,下面一個個分析下具體作用:
- 第一步判斷
baseUrl
是否為空,為空直接拋異常; - 第二步獲取
Platform
平臺物件,因為我們在Android工程中使用,所以獲取的是Android21
或者Android24
其中一個,Platform
定義了回撥執行緒池、呼叫介面卡和轉換介面卡物件等; - 第三步獲取回撥執行緒池物件,會從
Platform
物件中獲取預設的執行緒池; - 第四步獲取
CallAdapter.Factory
列表,並且新增從Platform
中獲取到的預設CallAdapter.Factory
物件。CallAdapter
的主要作用就是將我們介面返回的物件轉換成想要的型別,典型的就是RxJavaCallAdapter
,它就是將預設的Call
轉換成Observable
- 第五步獲取
Converter.Factory
列表,同時也會從Platform
中獲取預設的Converter.Factory
列表。Converter.Factory
的主要作用就是將請求結果通過具體的操作轉換成想要的型別,典型的就是GsonConverter
,它就是將String
轉換成具體的資料類物件。 - 第六步建立
Retrofit
物件,然後設定上面獲取的物件。
Retrofit.cerate()
retrofit2.Retrofit#create
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
return (T)
Proxy.newProxyInstance( ①
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
private final Object[] emptyArgs = new Object[0];
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// 判斷Service是介面還是類,如果是類直接呼叫類的方法
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
args = args != null ? args : emptyArgs;
Platform platform = Platform.get();
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args); ②
}
});
}
Retrofit.create(class)
方法中可以看出主要的有兩步:
- 第一步使用動態代理模式,根據傳入的
class
物件代理最終代理出一個例項; - 第二步先判斷
platform.isDefaultMethod(method)
,進入Android21
類中此方法預設false
,所以直接看loadServiceMethod(method)
方法即可
-
-
loadServiceMethod(method)
會建立一個ServiceMethod
物件; - 再呼叫
ServiceMethod.invoke(args)
方法,這裡傳入的介面方法的引數
-
Retrofit.loadServiceMethod()
retrofit2.Retrofit#loadServiceMethod
ServiceMethod<?> loadServiceMethod(Method method) {
// 檢視是否命中快取
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
// 加鎖並且雙重判斷是否命中快取
result = serviceMethodCache.get(method);
if (result == null) {
// 獲取新的ServiceMethod物件,並存入Map中
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
loadServiceMethod()
方法內部邏輯還是比較簡單,先是從Map
中獲取是否已經存在,不存在再加鎖去獲取新的ServiceMethod
物件,最後放入快取Map
中。具體的獲取流程還需要進入ServiceMethod.parseAnnotations(retrofit,method)
方法中。
ServiceMethod.parseAnnotations()
retrofit2.ServiceMethod#parseAnnotations
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
// 建立RequestFactory物件
RequestFactory requestFactory =
RequestFactory.parseAnnotations(retrofit, method); ①
// 獲取方法返回型別
Type returnType = method.getGenericReturnType();
// 判斷返回型別是否能夠解析
if (Utils.hasUnresolvableType(returnType)) {
throw methodError(
method,
"Method return type must not include a type variable or wildcard: %s",
returnType);
}
// 返回型別不能為void型別
if (returnType == void.class) {
throw methodError(method, "Service methods cannot return void.");
} ②
// 建立HttpServiceMethod物件
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory); ③
}
parseAnnotations()
方法中主要做了三件事:
- 第一件就是建立了
RequestFactory
物件,這個接下來分析下這個物件作用是什麼; - 第二件通過
Method
獲取方法返回值,並判斷可用性; - 第三件轉到
HttpServiceMethod
中建立HttpServiceMethod
物件,此類是ServiceMethod
的子類。
RequestFactory.parseAnnotations(retrofit, method)
retrofit2.RequestFactory#parseAnnotations
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
return new Builder(retrofit, method).build();
}
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();
this.parameterTypes = method.getGenericParameterTypes();
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
RequestFactory build() {
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation); ①
}
// httpMethod不能為空,也就是設定的請求方式GET、POST等
if (httpMethod == null) {
throw methodError(method, "HTTP method annotation is required (e.g., @GET, @POST, etc.).");
} ②
// 判斷請求提是否合規
if (!hasBody) {
if (isMultipart) {
throw methodError(
method,
"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
}
if (isFormEncoded) {
throw methodError(
method,
"FormUrlEncoded can only be specified on HTTP methods with "
+ "request body (e.g., @POST).");
}
} ③
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
parameterHandlers[p] =
parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
} ④
if (relativeUrl == null && !gotUrl) {
throw methodError(method, "Missing either @%s URL or @Url parameter.", httpMethod);
}
if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
throw methodError(method, "Non-body HTTP method cannot contain @Body.");
}
if (isFormEncoded && !gotField) {
throw methodError(method, "Form-encoded method must contain at least one @Field.");
}
if (isMultipart && !gotPart) {
throw methodError(method, "Multipart method must contain at least one @Part.");
}
return new RequestFactory(this); ⑤
}
parseAnnotations()
方法直接呼叫了內部Builder.build()
方法,build()
方法中,可以細分為五步:
- 第一步通過
parseMethodAnnotation()
解析方法上註解,解析的內容包括:請求方式、請求頭、請求地址、是否Multipart
和是否FormUrlEncode
等資訊,下面會詳細分析; - 第二步判斷請求方式,如果為空直接拋異常;
- 第三步判斷
Multipart
和FormUrlEncode
註解,如果有請求體的時候,不可以有前面兩個註解,否則直接拋異常; - 第四步通過
parseParameter
解析引數上註解,包括Body
、Filed
、Header
和Query
等資訊; - 第五步建立
RequestFactory
物件,將Builder
傳入進去。
RequestFactory.parseMethodAnnotation()
retrofit2.RequestFactory.Builder#parseMethodAnnotation
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);
} 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(method, "@Headers annotation is empty.");
}
headers = parseHeaders(headersToParse);
} else if (annotation instanceof Multipart) {
if (isFormEncoded) {
throw methodError(method, "Only one encoding annotation is allowed.");
}
isMultipart = true;
} else if (annotation instanceof FormUrlEncoded) {
if (isMultipart) {
throw methodError(method, "Only one encoding annotation is allowed.");
}
isFormEncoded = true;
}
}
parseMethodAnnotation()
主要就是解析出方法上的註解,比如是GET
還是POST
請求,有沒有額外新增HEADER
,或者是不是Multipart
、FormUrlEncoded
。
RequestFactory.parseParameter()
retrofit2.RequestFactory.Builder#parseParameter
private @Nullable ParameterHandler<?> parseParameter(
int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
ParameterHandler<?> result = null;
if (annotations != null) {
for (Annotation annotation : annotations) {
ParameterHandler<?> annotationAction =
parseParameterAnnotation(p, parameterType, annotations, annotation);
if (annotationAction == null) {
continue;
}
if (result != null) {
throw parameterError(
method, p, "Multiple Retrofit annotations found, only one allowed.");
}
result = annotationAction;
}
}
.....
return result;
}
parseParameter()
具體的解析流程在parseParameterAnnotation()
方法中,由於此方法原始碼過於長,此處就不再貼原始碼了,裡面其實就是根據不同的註解型別來解析成對應的ParameterHandler
物件。
具體的型別如下:
-
@Url
註解,解析出來對應的是ParameterHandler.RelativeUrl()
物件; -
@Path
註解,解析出來對應的是ParameterHandler.Path()
物件; -
@Query
註解,解析出來對應的是ParameterHandler.Query()
物件; -
@QueryName
註解,解析出來對應的是ParameterHandler.QueryName()
物件; -
@QueryMap
註解,解析出來對應的是ParameterHandler.QueryMap()
物件; -
@Header
註解,解析出來對應的是ParameterHandler.Header()
物件; -
@HeaderMap
註解,解析出來對應的是ParameterHandler.HeaderMap()
物件; -
@Field
註解,解析出來對應的是ParameterHandler.Field()
物件; -
@FieldMap
註解,解析出來對應的是ParameterHandler.FieldMap()
物件; -
@Part
註解,解析出來對應的是ParameterHandler.Part()
物件; -
@PartMap
註解,解析出來對應的是ParameterHandler.PartMap()
物件; -
@Body
註解,解析出來對應的是ParameterHandler.Body()
物件; -
@Tag
註解,解析出來對應的是ParameterHandler.Tag()
物件;
這就是所有的方法引數註解型別。
RequestFactory.parseAnnotations()
分析完了之後回到HttpServiceMethod.parseAnnotations()
。
HttpServiceMethod.parseAnnotations()
retrofit2.HttpServiceMethod#parseAnnotations
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;
boolean continuationIsUnit = false;
Annotation[] annotations = method.getAnnotations();
Type adapterType;
if (isKotlinSuspendFunction) { ①
Type[] parameterTypes = method.getGenericParameterTypes();
Type responseType =
Utils.getParameterLowerBound(
0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
// Unwrap the actual body type from Response<T>.
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
continuationWantsResponse = true;
} else {
continuationIsUnit = Utils.isUnit(responseType);
}
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType(); ②
}
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations); ③
Type responseType = callAdapter.responseType();
if (responseType == okhttp3.Response.class) {
throw methodError(
method,
"'"
+ getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
if (responseType == Response.class) {
throw methodError(method, "Response must include generic type (e.g., Response<String>)");
}
// TODO support Unit for Kotlin?
if (requestFactory.httpMethod.equals("HEAD")
&& !Void.class.equals(responseType)
&& !Utils.isUnit(responseType)) {
throw methodError(method, "HEAD method must use Void or Unit as response type.");
}
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType); ④
okhttp3.Call.Factory callFactory = retrofit.callFactory; ⑤
if (!isKotlinSuspendFunction) {
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>)
new SuspendForResponse<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter); ⑥
} else {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>)
new SuspendForBody<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
continuationBodyNullable,
continuationIsUnit); ⑦
}
}
這裡面的邏輯還是略微比較多的,逐一看下:
- 第一步,判斷是否是
Kotlin
的掛起函式,然後通過getGenericParameterTypes
獲取方法的返回型別; - 第二步,如果不是
Kotlin
的掛起函式,直接通過getGenericReturnType
獲取方法的返回型別,這裡的返回型別如果是泛型的話有可能拿不到具體的泛型值; - 第三步建立
CallAdapter
,這裡假設我們沒有設定任何額外的CallAdapter
,會使用預設的DefaultCallAdapter
物件; - 第四步建立
ConverterAdapter
,這裡我們一般使用Gson
來解析響應結果,轉換成資料類,這邊假設使用的是GsonResponseBodyConverter
物件; - 第五步僅僅是獲取
Retrofit
物件中callFactory
; - 第六步和第七步對
Kotlin
的掛起函式做判斷,分析過程中預設非掛起函式,直接進入第七步,煩惱會一個CallAdapted
物件。
到這我們發現Retrofit.create()
方法中,動態代理中loadServiceMethod()
方法最終返回的就是CallAdapted
物件,loadServiceMethod()
方法後面直接呼叫了invoke()
方法,接下來進入CallAdapted.invoke()
方法。
CallAdapter.invoke()
進入CallAdapted
物件中,發現並沒有invoke()
方法的實現,只能退而求其次去看它的父類了:
retrofit2.HttpServiceMethod#invoke
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
invoke()
方法中首先建立了一個OkHttpCall
物件,然後呼叫adapt()
方法。
OkhttpCall
是Retrofit
用來管理髮起網路呼叫和回撥的具體類。
CallAdapted.adapt()
retrofit2.HttpServiceMethod.CallAdapted#adapt
@Override
protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
// 這裡的callAdapter為DefaultCallAdapterFactory中CallAdapter
return callAdapter.adapt(call);
}
retrofit2.DefaultCallAdapterFactory#get
return new CallAdapter<Object, Call<?>>() {
@Override
public Type responseType() {
return responseType;
}
@Override
public Call<Object> adapt(Call<Object> call) {
return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
}
};
adapt()
方法直接呼叫callAdapter.adapt()
方法,前面我們說到,預設使用CallAdapter
,它是有DefaultCallAdapterFactory
建立而成,CallAdapter.adapt()
方法中只是建立了一個ExecutorCallbackCall
物件。
到這裡我們就清除了,在使用章節第四步生成的Call
其實就是ExecutorCallbackCall
物件,讓他呼叫它的enqueue()
方法就可以發起網路請求。
ExecutorCallbackCall.enqueue()
retrofit2.DefaultCallAdapterFactory.ExecutorCallbackCall#enqueue
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
this.callbackExecutor = callbackExecutor;
this.delegate = delegate;
}
public void enqueue(final Callback<T> callback) {
Objects.requireNonNull(callback, "callback == null");
// 直接使用代理Call的enqueue方法,這裡的delegate就是OkHttpCall物件
delegate.enqueue(
new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) {
// 主執行緒中回撥結果,Android預設callbackExecutor為MainExecutor
callbackExecutor.execute(
() -> {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on
// cancellation.
callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
});
}
@Override
public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
}
});
}
ExecutorCallbackCall.enqueue()
方法很簡單,直接呼叫delegate.enqueue()
,然後將網路請求的結果回撥到主執行緒中去處理。
這裡的delegate
從CallAdapted.invoke()
方法可以得知,它就是OkHttpCall
物件,接著我們進入OkhttpCall.enqueue()
方法。
OkHttpCall.enqueue()
retrofit2.OkHttpCall#enqueue
public void enqueue(final Callback<T> callback) {
Objects.requireNonNull(callback, "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(); ②
} catch (Throwable t) {
throwIfFatal(t);
failure = creationFailure = t;
}
}
}
if (failure != null) {
callback.onFailure(this, failure);
return;
}
if (canceled) {
call.cancel();
}
call.enqueue( ③
new okhttp3.Callback() {
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
...
}
@Override
public void onFailure(okhttp3.Call call, IOException e) {
...
}
});
}
此方法才是最終呼叫Okhttp3
發起網路請求最終地點,重要的一共有三步:
- 第一步檢查
rawCall
是否為空; - 第二步如果
rawCall
為空,需要通過createRawCall()
來建立OKhttp3.Call
物件; - 使用
Okhttp3.Call.enqueue()
方法來發起非同步請求。
到這為止,整個Retrofit
網路請求流程就分析結束了,Retrofit
並沒有執行任何網路請求操作,只是將所有方法註解、引數註解解析好之後,交給Okhttp3
來發起具體的網路請求。
最後
為了幫助大家能夠更好地學習框架原始碼,特在此為大家分享一份阿里大佬整理的《Android百大框架原始碼解析》,這份資料有1880頁,乾貨十足。除了比較流行的Retrofit,OkHttp等,還收納了很多經典框架。雖然有些框架我們不再使用,但還是可以通過原始碼,來領略其中的精髓思想,為自己開闊思路。
適用讀者範圍:
- 正在入門Android的新手——Android初級開發工程師 初出茅廬
- Android初級開發工程師——中級開發工程師 知其然知其所以然
- 中級、高階、資深工程師 知其然知其不可然
需要這份1880頁《Android百大框架原始碼解析》的朋友可以【點選這裡直達免費獲取方式!!!】