Retrofit原始碼解析之網路請求
1 解析註釋並生成ServiceMethod物件
首先從Retrofit的create動態代理開始分析
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);//步驟1
serviceMethodCache.put(method, result);
}
}
return result;
}
關鍵的方法在步驟1,其他只是做下快取而已。跟進去:
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);//步驟1
...
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);//步驟2
}
先看下步驟1,他通過retrofit以及method生成對應的請求工廠。跟進去看一下:
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();
}
可見它除了持有retrofit物件還持有方法註解以及引數註解,這些都是生成請求的必要元素。好了現在看步驟2,跟進去看程式碼:
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;
Annotation[] annotations = method.getAnnotations();
Type adapterType;
if (isKotlinSuspendFunction) {
...
} else {
adapterType = method.getGenericReturnType();
}
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);//步驟1
Type responseType = callAdapter.responseType();
...
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);//步驟2
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if (!isKotlinSuspendFunction) {
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);//步驟3
}
...
}
static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
private final CallAdapter<ResponseT, ReturnT> callAdapter;
CallAdapted(
RequestFactory requestFactory,
okhttp3.Call.Factory callFactory,
Converter<ResponseBody, ResponseT> responseConverter,
CallAdapter<ResponseT, ReturnT> callAdapter) {
super(requestFactory, callFactory, responseConverter);
this.callAdapter = callAdapter;
}
@Override
protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
return callAdapter.adapt(call);
}
}
步驟1生成非同步執行的callAdapter,步驟2生成響應資料的轉換器,最後步驟也就是生成一個新的CallAdapted。而CallAdapted繼承於HttpServiceMethod,而它也就是在HttpServiceMethod的基礎上增加了callAdapter。
那麼最後總結一下流程是
loadServiceMethod -->HttpServiceMethod.parseAnnotations(生成RequestFactory) --> HttpServiceMethod.parseAnnotations(生成CallAdapter以及Converter)
最後以CallAdapted物件的形式返回也就是生成了一個包含所有元素的HttpServiceMethod物件。
接下來具體分析下步驟1和步驟2是如何生成的,先看看步驟1:
private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
try {
//noinspection unchecked
return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(method, e, "Unable to create call adapter for %s", returnType);
}
}
public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
return nextCallAdapter(null, returnType, annotations);
}
public CallAdapter<?, ?> nextCallAdapter(
@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
Objects.requireNonNull(returnType, "returnType == null");
Objects.requireNonNull(annotations, "annotations == null");
int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
....
}
執行步驟是 createCallAdapter --> callAdapter – > nextCallAdapter,而在nextCallAdapter中會去遍歷callAdapterFactories以及引數returnType獲取CallAdapter。這個callAdapterFactories是Retrofit建立的時候傳進來的,這邊的話也就是說你如果傳進多個的話它只會去取可用的第一個。
這邊使用的Rxjava配置,所以跟到Rxjava的Factories進去:
@Override
public @Nullable CallAdapter<?, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
Class<?> rawType = getRawType(returnType);
...
boolean isFlowable = rawType == Flowable.class;
boolean isSingle = rawType == Single.class;
boolean isMaybe = rawType == Maybe.class;
...
boolean isResult = false;
boolean isBody = false;
Type responseType;
...
Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
Class<?> rawObservableType = getRawType(observableType);
if (rawObservableType == Response.class) {
...
responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
} else if (rawObservableType == Result.class) {
...
responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
isResult = true;
} else {
responseType = observableType;
isBody = true;
}
return new RxJava2CallAdapter(
responseType, scheduler, isAsync, isResult, isBody, isFlowable, isSingle, isMaybe, false);
}
RxJava2CallAdapter(
Type responseType,
@Nullable Scheduler scheduler,
boolean isAsync,
boolean isResult,
boolean isBody,
boolean isFlowable,
boolean isSingle,
boolean isMaybe,
boolean isCompletable) {
this.responseType = responseType;
this.scheduler = scheduler;
this.isAsync = isAsync;
this.isResult = isResult;
this.isBody = isBody;
this.isFlowable = isFlowable;
this.isSingle = isSingle;
this.isMaybe = isMaybe;
this.isCompletable = isCompletable;
}
由returnType獲取返回值的型別引數。這邊boolean isFlowable,isSingle,isMaybe分別表示Rxjava的三個不同的型別。rawObservableType而是指返回的資料型別也是有Response,Result以及使用者自定義的型別三種。這些標誌位提供給RxJava2CallAdapter使用。
接下來分析下Converter資料轉換,跟CallAdapter差不多流程:
createResponseConverter --> responseBodyConverter --> nextResponseBodyConverter --> responseBodyConverter
private static <ResponseT> Converter<ResponseBody, ResponseT> createResponseConverter(
Retrofit retrofit, Method method, Type responseType) {
Annotation[] annotations = method.getAnnotations();
try {
return retrofit.responseBodyConverter(responseType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(method, e, "Unable to create converter for %s", responseType);
}
}
public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
return nextResponseBodyConverter(null, type, annotations);
}
public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
@Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
Objects.requireNonNull(type, "type == null");
Objects.requireNonNull(annotations, "annotations == null");
int start = converterFactories.indexOf(skipPast) + 1;
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter<ResponseBody, ?> converter =
converterFactories.get(i).responseBodyConverter(type, annotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<ResponseBody, T>) converter;
}
}
...
}
步驟2最後是定位在了responseBodyConverter上。converterFactories是在配置的時候設定的,所以這邊會選擇配置的第一個有效工廠生成轉換器。我們以Gson作為例子分析responseBodyConverter:
進入GsonConverterFactory.java類看看:
@Override
public Converter<ResponseBody, ?> responseBodyConverter(
Type type, Annotation[] annotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonResponseBodyConverter<>(gson, adapter);
}
GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
這邊的type即前面CallAdapter指定返回的資料型別,加上gson物件構成解析資料的兩個引數生成轉化器物件。
回到原點以上分析的這些通過一系列步驟生成後續請求解析的各個必要元素,就像炒菜之前要把各種蔬菜肉類調味料切好。
2 請求與解析網路資料
前面生成HttpServiceMethod物件,現在開始對傳進來的引數進行處理了。它呼叫了invoke(args);
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
可見先把args引數封裝到OkHttpCall中去,再呼叫adapt(call, args)方法。之前說過HttpServiceMethod物件實際上是CallAdapted物件。我們的例子用的RxjavaAdapter,所以實際上該Adapter呼叫的是RxJava2CallAdapter的adapter。前面已經分析過了,如果返回的Observer物件被訂閱的時候,那麼OKHttpCall的execute就會被執行。也就是開始網路請求了:
@Override
public Response<T> execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
call = getRawCall();//步驟1
}
if (canceled) {
call.cancel();
}
return parseResponse(call.execute());//步驟2
}
步驟1:獲取okhttp3.Call的請求回撥。之前通過loadServiceMethod獲取了很多必要的元素,現在可以派上用場了。–> 請求
步驟2:call.execute()執行之後獲取到了網路資料。由於本例子用的是Rxjava,這邊會解析成動態代理指定的返回值。–> 返回
先看步驟1,也是有三個步驟 getRawCall --> createRawCall --> createRawCall:
@GuardedBy("this")
private okhttp3.Call getRawCall() throws IOException {
okhttp3.Call call = rawCall;
if (call != null) return call;
// Re-throw previous failures if this isn't the first attempt.
if (creationFailure != null) {
if (creationFailure instanceof IOException) {
throw (IOException) creationFailure;
} else if (creationFailure instanceof RuntimeException) {
throw (RuntimeException) creationFailure;
} else {
throw (Error) creationFailure;
}
}
// Create and remember either the success or the failure.
try {
return rawCall = createRawCall();
} catch (RuntimeException | Error | IOException e) {
throwIfFatal(e); // Do not assign a fatal error to creationFailure.
creationFailure = e;
throw e;
}
}
private okhttp3.Call createRawCall() throws IOException {
okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
最終也就是callFactory.newCall(requestFactory.create(args))這一行了。
args也就是傳進來的引數。requestFactory是之前建立的請求引數工廠
okhttp3.Request create(Object[] args) throws IOException {
@SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
int argumentCount = args.length;
if (argumentCount != handlers.length) {
throw new IllegalArgumentException(
"Argument count ("
+ argumentCount
+ ") doesn't match expected count ("
+ handlers.length
+ ")");
}
RequestBuilder requestBuilder =
new RequestBuilder(
httpMethod,
baseUrl,
relativeUrl,
headers,
contentType,
hasBody,
isFormEncoded,
isMultipart);//步驟1
if (isKotlinSuspendFunction) {
// The Continuation is the last parameter and the handlers array contains null at that index.
argumentCount--;
}
List<Object> argumentList = new ArrayList<>(argumentCount);
for (int p = 0; p < argumentCount; p++) {
argumentList.add(args[p]);
handlers[p].apply(requestBuilder, args[p]);
}//步驟2
//通過RequestBuilder構造時候的那些引數獲取okhttp的okhttp3.Request
return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();//步驟3
}
步驟1:生成請求RequestBuilder物件,裡面的引數都是loadServiceMethod時候通過builder工廠生成的。
httpMethod:請求型別(POST,GET等)
baseUrl:字首URL
relativeUrl:相對URL
headers:請求頭
contentType:
hasBody:是否有請求體
isFormEncoded:是否表單
isMultipart:
步驟2:將args引數逐個解析之後存在requestBuilder物件中。
步驟3:整合步驟2生成的各個要素,生成Request物件。
現在一個一個說。首先是步驟1,看看loadServiceMethod是怎麼解析出這些引數
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);
}
...
}
//解析引數
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);
}
...
return new RequestFactory(this);
}
首先是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);
}
...
}
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
...
this.httpMethod = httpMethod;
this.hasBody = hasBody;
if (value.isEmpty()) {
return;
}
...
this.relativeUrl = value;
this.relativeUrlParamNames = parsePathParameters(value);
}
parseMethodAnnotation有各種註解,其實大同小異。這邊以"GET"為例子這邊的執行結果就是
“GET” --> httpMethod
((GET) annotation).value() --> relativeUrl //這個其實就是GET後面的那個相對地址了
fale = hasBody;//GET是沒有請求體的
方法最終就是在這裡解析的,接下來看看引數的解析,其實是針對不同的註解生成不同的處理Handler:
parameterHandlers[p] = parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
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);
...
result = annotationAction;
}
}
...
return result;
}
private ParameterHandler<?> parseParameterAnnotation(
int p, Type type, Annotation[] annotations, Annotation annotation) {
...
else if (annotation instanceof Body) {
validateResolvableType(p, type);
...
Converter<?, RequestBody> converter;
try {
converter = retrofit.requestBodyConverter(type, annotations, methodAnnotations);
} catch (RuntimeException e) {
// Wide exception range because factories are user code.
throw parameterError(method, e, p, "Unable to create @Body converter for %s", type);
}
gotBody = true;
return new ParameterHandler.Body<>(method, p, converter);
}
...
}
public <T> Converter<T, RequestBody> requestBodyConverter(
Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations) {
return nextRequestBodyConverter(null, type, parameterAnnotations, methodAnnotations);
}
public <T> Converter<T, RequestBody> nextRequestBodyConverter(
@Nullable Converter.Factory skipPast,
Type type,
Annotation[] parameterAnnotations,
Annotation[] methodAnnotations) {
Objects.requireNonNull(type, "type == null");
Objects.requireNonNull(parameterAnnotations, "parameterAnnotations == null");
Objects.requireNonNull(methodAnnotations, "methodAnnotations == null");
int start = converterFactories.indexOf(skipPast) + 1;
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter.Factory factory = converterFactories.get(i);
Converter<?, RequestBody> converter =
factory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<T, RequestBody>) converter;
}
}
...
}
流程如下:
parseParameter --> parseParameterAnnotation(這邊以Body為例子) --> requestBodyConverter --> nextRequestBodyConverter --> factory.requestBodyConverter
最終是跑到了factory的requestBodyConverter方法,也就是說最終由GsonRequestBodyConverter轉換器以及method生成了ParameterHandler.Body物件。
回頭再看下步驟2,為了閱讀方便將上面的程式碼挪下來:
okhttp3.Request create(Object[] args) throws IOException {
...
List<Object> argumentList = new ArrayList<>(argumentCount);
for (int p = 0; p < argumentCount; p++) {
argumentList.add(args[p]);
handlers[p].apply(requestBuilder, args[p]);
}//步驟2
//通過RequestBuilder構造時候的那些引數獲取okhttp的okhttp3.Request
return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();//步驟3
}
步驟2的handlers就是剛才生成的ParameterHandler.Body物件,跟進apply程式碼看看:
@Override
void apply(RequestBuilder builder, @Nullable T value) {
if (value == null) {
throw Utils.parameterError(method, p, "Body parameter value must not be null.");
}
RequestBody body;
try {
body = converter.convert(value);
} catch (IOException e) {
throw Utils.parameterError(method, e, p, "Unable to convert " + value + " to RequestBody");
}
builder.setBody(body);
}
就是說通過轉換器將引數value轉換成RequestBody物件並存在RequestBuilder物件中。
步驟3,主要是看下get()程式碼:
Request.Builder get() {
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.
//noinspection ConstantConditions Non-null if urlBuilder is null.
url = baseUrl.resolve(relativeUrl);//步驟1
if (url == null) {
throw new IllegalArgumentException(
"Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl);
}
}
RequestBody body = this.body;//步驟2
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 {
headersBuilder.add("Content-Type", contentType.toString());//步驟3
}
}
return requestBuilder.url(url).headers(headersBuilder.build()).method(method, body);//步驟4
}
步驟1:之前parseMethodAnnotation獲取到的baseUrl和relativeUrl在這裡進行拼裝
步驟2:取出之前ParameterHandler.Body的apply方法生成的RequestBody物件
步驟3:headersBuilder新增請求頭或者之前根據註解也會新增請求頭
步驟4:將步驟123的引數組合生成builder。
回到okhttp3.Request create的 return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build(),那麼他就會返回okhttp3.Request物件,再回到前面的OkHttpCall的createRawCall方法:
private okhttp3.Call createRawCall() throws IOException {
okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
這邊返回了okhttp3.Call物件。
總結一下,這邊主要是requestFactory這個物件生產出request請求。先是方法解析註解,再解析生成引數註解Hander然後處理傳進來的引數。最後將這兩個結果組合成okhttp的請求。請求的過程算是解析完了。下面說說解析應答,也就是OkHttpCall的execute方法中的parseResponse方法:
@Override
public Response<T> execute() throws IOException {
...
return parseResponse(call.execute());//步驟2
}
跟進去parseResponse看下:
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
// Remove the body's source (the only stateful object) so we can pass the response along.
rawResponse =
rawResponse
.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
...
ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
try {
T body = responseConverter.convert(catchingBody);
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;
}
}
這比較簡單,就是之前的轉換器將得到的結果轉成動態代理方法標註的返回型別即可然後封裝成Response物件返回。
至此,網路請求和響應解析處理完畢!
相關文章
- Retrofit網路請求原始碼解析原始碼
- Volley 原始碼解析之網路請求原始碼
- Android網路請求(終) 網路請求框架RetrofitAndroid框架
- Android 網路框架 Retrofit 原始碼解析Android框架原始碼
- 原始碼分析Retrofit請求流程原始碼
- 使用retrofit進行網路請求
- Volley 原始碼解析之圖片請求原始碼
- Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析Android原始碼
- OkHttp、rxJava、Retrofit聯合網路請求(二)HTTPRxJava
- OkHttp、rxJava、Retrofit聯合網路請求(一)HTTPRxJava
- 使用Retrofit+RxJava實現網路請求RxJava
- Kotlin中Retrofit網路請求簡單封裝Kotlin封裝
- OkHttp3原始碼解析(一)之請求流程HTTP原始碼
- 輕鬆搞定Retrofit不同網路請求方式的請求引數配置,Retrofit常用註解的使用
- Android Retrofit原始碼解析Android原始碼
- RxJava + Retrofit原始碼解析RxJava原始碼
- Axios 原始碼解讀 —— 網路請求篇iOS原始碼
- Retrofit+okhttp+Rxjava封裝網路請求工具類HTTPRxJava封裝
- 分分鐘使用Retrofit+Rxjava實現網路請求RxJava
- 網路請求優化之取消請求優化
- [Android] Retrofit原始碼:流程解析Android原始碼
- OKHttp網路請求原理流程解析HTTP
- Android網路程式設計:Retrofit原始碼解析Android程式設計原始碼
- Retrofit2.5怎麼做到網路請求的處理的?
- Kotlin + 協程 + Retrofit + MVVM優雅的實現網路請求KotlinMVVM
- Retrofit + Kotlin + MVVM 的網路請求框架的封裝嘗試KotlinMVVM框架封裝
- 【原始碼SOLO】Retrofit2原始碼解析(一)原始碼
- Swift 3 網路請求+資料解析Swift
- Retrofit2原始碼解析(一)原始碼
- Android中用Kotlin Coroutine(協程)和Retrofit進行網路請求和取消請求AndroidKotlin
- 輕鬆搞定Retrofit不同網路請求方式的請求引數配置,及常用註解使用
- RxJava+Retrofit2搭建網路請求元件完整配置、示例程式碼及流程梳理RxJava元件
- Vue 入門之網路請求Vue
- 小程式系列之網路請求
- Android小知識-剖析Retrofit中網路請求的兩種方式Android
- Android使用Kotlin+Retrofit+Rxjava實現簡單的網路請求AndroidKotlinRxJava
- Android網路請求(4) 網路請求框架VolleyAndroid框架
- Android網路請求(3) 網路請求框架OkHttpAndroid框架HTTP