您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“OkHttp與Retrofit的區(qū)別有哪些”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“OkHttp與Retrofit的區(qū)別有哪些”這篇文章吧。
參考答案:
OkHttp和Retrofit都是目前流行網(wǎng)絡(luò)開源框架
封裝不同:
Retrofit封裝了具體的請(qǐng)求,線程切換以及數(shù)據(jù)轉(zhuǎn)換。
retrofit通過使用代理,外觀,策略模式對(duì)okhttp進(jìn)行了封裝
OkHttp 是基于Http協(xié)議封裝的一套請(qǐng)求客戶端
職責(zé)不同:
Retrofit主要負(fù)責(zé)應(yīng)用層面的封裝,面向開發(fā)者,方便使用,比如請(qǐng)求參數(shù),響應(yīng)數(shù)據(jù)的處理,錯(cuò)誤處理等等。
OkHttp主要負(fù)責(zé)socket部分的優(yōu)化與封裝,比如網(wǎng)絡(luò)訪問,多路復(fù)用,buffer緩存,數(shù)據(jù)壓縮等等。
(順手留下GitHub鏈接,需要獲取相關(guān)面試等內(nèi)容的可以自己去找)
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)
Retrofit
可以說和 OkHttp
是親兄弟了,它們都是由 Square 公司推出的網(wǎng)絡(luò)請(qǐng)求庫(kù),并且 Retrofit
實(shí)際上是基于 OkHttp
實(shí)現(xiàn)的,它在 OkHttp
現(xiàn)有功能的基礎(chǔ)上進(jìn)行了封裝,支持通過注解進(jìn)行網(wǎng)絡(luò)請(qǐng)求參數(shù)的配置,同時(shí)對(duì)數(shù)據(jù)返回后的解析、序列化進(jìn)行了統(tǒng)一的包裝,甚至在近期引入了對(duì)協(xié)程對(duì)支持。
今天就讓我們一起來看看 Retrofit
是如何在 OkHttp
這樣一個(gè)已經(jīng)固定的框架的基礎(chǔ)上,優(yōu)雅的進(jìn)行封裝并拓展功能的。
基本使用
我們首先來看看 Retrofit 的基本使用,來對(duì)它有個(gè)大致的了解。
首先,我們可以構(gòu)建一個(gè)如下的請(qǐng)求 Service 類,它里面對(duì)各個(gè)請(qǐng)求的方法及參數(shù)通過注解進(jìn)行了標(biāo)注:
public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user); }
之后,我們可以構(gòu)建一個(gè) Retrofit
對(duì)象,并通過 Retrofit.create
方法傳入對(duì)應(yīng) class
從而構(gòu)建對(duì)應(yīng)的 Service
對(duì)象:
Retrofit retrofit = new Retrofit.Builder() baseUrl("https://api.github.com/") build(); } GitHubService service = retrofit.create(GitHubService.class);
之后,我們調(diào)用 service
中對(duì)應(yīng)的方法,就可以獲取到 Call 對(duì)象了。
通過對(duì) Call 對(duì)象調(diào)用 enqueue
就可以實(shí)現(xiàn)對(duì)請(qǐng)求的異步調(diào)用,而通過 execute
方法則可以實(shí)現(xiàn)請(qǐng)求的同步調(diào)用。
Retrofit 采用了 Builder 模式進(jìn)行了構(gòu)建,在 Builder 中可以進(jìn)行非常多的配置,其中可以對(duì) baseUrl
、okhttpClient
、converterFactory
、callAdapterFactory
等進(jìn)行設(shè)置。
這里沒什么特別的,都是一些簡(jiǎn)單的賦值,就不再關(guān)注了,我們只需要看看最后 Retrofit 被傳入了哪些參數(shù)。它最后調(diào)用了下面這個(gè)構(gòu)造函數(shù)對(duì)參數(shù)進(jìn)行了初始化。
Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl, List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories, @Nullable Executor callbackExecutor, boolean validateEagerly) { this.callFactory = callFactory; this.baseUrl = baseUrl; this.converterFactories = converterFactories; // Copy+unmodifiable at call site. this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site. this.callbackExecutor = callbackExecutor; this.validateEagerly = validateEagerly; }
接著我們看到自己定義的 interface
是如何僅僅靠傳遞 class
給 Retrofit.create
就能實(shí)現(xiàn)實(shí)例的獲取的,它明明只是個(gè)接口呀?
public <T> T create(final Class<T> service) { // 對(duì) Service 的接口進(jìn)行檢測(cè) validateServiceInterface(service); // 通過動(dòng)態(tài)代理構(gòu)建代理對(duì)象 return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); private final Object[] emptyArgs = new Object[0]; @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // Object 類的方法照常調(diào)用 if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } // 如果是對(duì)應(yīng)平臺(tái)本身的類就有的方法,照常調(diào)用 if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } // 否則通過 loadServiceMethod 方法獲取到對(duì)應(yīng) Method 并 invoke return loadServiceMethod(method).invoke(args != null ? args : emptyArgs); } }); }
可以看到,實(shí)際上 Service
對(duì)象的獲取是通過動(dòng)態(tài)代理實(shí)現(xiàn)的。這里首先通過 validateServiceInterface
方法對(duì)接口進(jìn)行了檢測(cè),之后通過動(dòng)態(tài)代理對(duì)該接口進(jìn)行了代理。
對(duì)于 Object
類本身獨(dú)有以及對(duì)應(yīng)平臺(tái)本身就存在的方法,就照常調(diào)用,否則通過 loadServiceMethod
對(duì) Service
中對(duì)應(yīng)的 Method
對(duì)象進(jìn)行處理,之后對(duì)其調(diào)用 invoke
方法。
這里說明了 Retrofit
不是在創(chuàng)建 Service
接口對(duì)應(yīng)對(duì)象時(shí)就立即對(duì)所有該接口中的所有方法都進(jìn)行注解的解析,而是采用了在方法被調(diào)用時(shí)才進(jìn)行注解的解析這種懶加載的思想。
接著我們看看 validateServiceInterface
方法:
private void validateServiceInterface(Class<?> service) { // 判斷是否是接口 if (!service.isInterface()) { throw new IllegalArgumentException("API declarations must be interfaces."); } // 判斷該接口及其繼承的所有接口是否包含了范型參數(shù),如果包含則拋出異常 Deque<Class<?>> check = new ArrayDeque<>(1); check.add(service); while (!check.isEmpty()) { Class<?> candidate = check.removeFirst(); if (candidate.getTypeParameters().length != 0) { StringBuilder message = new StringBuilder("Type parameters are unsupported on ").append(candidate.getName()); if (candidate != service) { message.append(" which is an interface of ").append(service.getName()); } throw new IllegalArgumentException(message.toString()); } Collections.addAll(check, candidate.getInterfaces()); } // 如果在創(chuàng)建Retrofit時(shí)設(shè)置了很急切地對(duì)Service的方法進(jìn)行處理,則對(duì)非平臺(tái)獨(dú)有且非static的方法通過 loadServiceMethod 方法進(jìn)行處理。 if (validateEagerly) { Platform platform = Platform.get(); for (Method method : service.getDeclaredMethods()) { if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) { loadServiceMethod(method); } } } }
首先,這個(gè)方法對(duì) service
進(jìn)行了檢測(cè),保證了它是一個(gè)接口并且它和它繼承的類中沒有范型參數(shù)。
之后如果在 Retrofit 創(chuàng)建時(shí)設(shè)置 validateEagerly
為 true 的話,會(huì)對(duì) Service 中所有非平臺(tái)獨(dú)有且非static的方法通過 loadServiceMethod
方法提前進(jìn)行處理
loadServiceMethod
究竟做了些什么: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); serviceMethodCache.put(method, result); } } return result; }
首先它會(huì)采用 Double Check 的方式嘗試從 serviceMethodCache
緩存中獲取 ServiceMethod
對(duì)象,如果獲取不到則通過 ServiceMethod.parseAnnotations
方法對(duì)該 Method 的注解進(jìn)行處理并將得到的 ServiceMethod
對(duì)象加入了緩存。
也就是說為了避免多次對(duì)方法的注解進(jìn)行處理,Retrofit 采用了一個(gè) serviceMethodCache
對(duì)解析后的 ServiceMethod
進(jìn)行緩存。
接著我們就來看看,parseAnnotations
方法是如何對(duì)方法的注解進(jìn)行解析的。
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) { 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); } if (returnType == void.class) { throw methodError(method, "Service methods cannot return void."); } return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory); }
這里先通過 RequestFactory.parseAnnotations
方法對(duì)注解解析并獲得了一個(gè) RequestFactory
對(duì)象。
之后又通過 HttpServiceMethod.parseAnnotations
方法傳入了 requestFactory
繼續(xù)進(jìn)行注解的解析并獲得 ServiceMethod
對(duì)象
我們先看看 RequestFactory.parseAnnotations
:
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) { return new Builder(retrofit, method).build(); }
它把 Method
傳入 Builder
從而構(gòu)建了一個(gè)新的 RequestFactory
:
Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; this.methodAnnotations = method.getAnnotations(); this.parameterTypes = method.getGenericParameterTypes(); this.parameterAnnotationsArray = method.getParameterAnnotations(); }
Builder 中通過反射獲取到method
所包含的注解、參數(shù)包含的范型以及參數(shù)的注解。
接著看看 build
方法:
RequestFactory build() { for (Annotation annotation : methodAnnotations) { // 遍歷方法注解對(duì)每個(gè)注解進(jìn)行解析 parseMethodAnnotation(annotation); } // ...異常的處理 // 對(duì)參數(shù)進(jìn)行解析 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); }
在 build 方法中主要是對(duì)方法的每個(gè)注解調(diào)用了 parseMethodAnnotation
進(jìn)行了解析,并且對(duì)每個(gè)參數(shù)調(diào)用了 parseParamter
方法解析為了 ParamterHandler
對(duì)象。
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; } }
這里實(shí)際上就是對(duì)每一種 HTTP 所支持的類型進(jìn)行了支持,獲取到了對(duì)應(yīng)注解的中的 url,并調(diào)用parseHttpMethodAndPath
進(jìn)行處理,同時(shí)對(duì) Headers 注解則是通過 parseHeaders
進(jìn)行了處理。
對(duì)于 Method 和 Path,通過 parseHttpMethodAndPath
進(jìn)行了參數(shù)的賦值:
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) { if (this.httpMethod != null) { throw methodError(method, "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(method, "URL query string \"%s\" must not have replace block. "+ "For dynamic query parameters use @Query.", queryParams); } } this.relativeUrl = value; this.relativeUrlParamNames = parsePathParameters(value); }
這里實(shí)際上就是對(duì)不同 HTTP 請(qǐng)求方式和 Path 進(jìn)行了賦值,同時(shí)通過正則表達(dá)式保證了這個(gè)接口的 Path 中沒有包含參數(shù)。
private Headers parseHeaders(String[] headers) { Headers.Builder builder = new Headers.Builder(); for (String header : headers) { int colon = header.indexOf(':'); if (colon == -1 || colon == 0 || colon == header.length() - 1) { throw methodError(method, "@Headers value must be in the form \"Name: Value\". Found: \"%s\"", header); } String headerName = header.substring(0, colon); String headerValue = header.substring(colon + 1).trim(); if ("Content-Type".equalsIgnoreCase(headerName)) { try { contentType = MediaType.get(headerValue); } catch (IllegalArgumentException e) { throw methodError(method, e, "Malformed content type: %s", headerValue); } } else { builder.add(headerName, headerValue); } } return builder.build(); }
而對(duì)于 Headers 則是將傳遞進(jìn)來的 Headers
列表解析為了對(duì)應(yīng)的 Headers
對(duì)象。
private @Nullable ParameterHandler<?> parseParameter( int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) { ParameterHandler<?> result = null; if (annotations != null) { for (Annotation annotation : annotations) { // 對(duì)每個(gè)注解通過 parseParameterAnnotation 進(jìn)行解析 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; } } if (result == null) { // 在協(xié)程的情況下對(duì)進(jìn)行特殊處理 if (allowContinuation) { try { if (Utils.getRawType(parameterType) == Continuation.class) { isKotlinSuspendFunction = true; return null; } } catch (NoClassDefFoundError ignored) { } } throw parameterError(method, p, "No Retrofit annotation found."); } return result; }
而 parseParamterAnnotation
方法的代碼太長(zhǎng)了,這里就不再貼了,它對(duì)方法的每個(gè)注解都進(jìn)行了獨(dú)有的處理,并返回了對(duì)應(yīng)的 ParamterHandler
。
可以發(fā)現(xiàn),RequestFactory.parseAnnotations
的主要作用就是完成對(duì)方法注解信息的解析,從而用于產(chǎn)生對(duì)應(yīng)的 Request
。
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; Annotation[] annotations = method.getAnnotations(); Type adapterType; if (isKotlinSuspendFunction) { // 如果方法是 kotlin 中的 suspend 方法 Type[] parameterTypes = method.getGenericParameterTypes(); // 獲取 Continuation 的范型參數(shù),它就是 suspend 方法的返回值類型 Type responseType = Utils.getParameterLowerBound(0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]); // 如果 Continuation 的范型參數(shù)是 Response,則說明它需要的是 Response,那么將 continuationWantsResponse 置為 true; 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 { // TODO figure out if type is nullable or not // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class) // Find the entry for method // Determine if return type is nullable or not } adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType); annotations = SkipCallbackExecutorImpl.ensurePresent(annotations); } else { // 否則獲取方法返回值的范型參數(shù),即為請(qǐng)求需要的返回值的類型 adapterType = method.getGenericReturnType(); } // 通過 createCallAdapter 方法創(chuàng)建 CallAdapter 對(duì)象 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)) { throw methodError(method, "HEAD method must use Void as response type."); } // 通過 createResponseConverter 方法創(chuàng)建 Converter對(duì)象 Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType); okhttp3.Call.Factory callFactory = retrofit.callFactory; if (!isKotlinSuspendFunction) { // 不是suspend方法的話則直接創(chuàng)建并返回一個(gè) CallAdapted 對(duì)象 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); } }
這里的代碼非常非常長(zhǎng),大致可歸納為下面的步驟:
1. 如果這個(gè)方法是 Kotlin 中的 suspend 方法,由于由協(xié)程實(shí)現(xiàn),因此需要獲取
Continuation
的范型參數(shù),這個(gè)參數(shù)就是請(qǐng)求返回值的真正類型。
2. 如果suspend
方法返回值是Response
,則說明它需要的是 Response 而不是具體的類型,那么將continuationWantsResponse
置為 true;
3. 如果不是suspend
方法,則返回值的范型參數(shù)的類型就是請(qǐng)求返回值的真正類型(Call<ReturnType>
則ReturnType
才是真正經(jīng)過轉(zhuǎn)換后需要的類型)。
4. 通過createCallAdapter
方法創(chuàng)建CallAdapter
對(duì)象,它是用于將Call<ResponseT>
對(duì)象適配為需要的類型ReturnT
對(duì)象的。
5. 拿到CallAdapter
后,獲取到了Response
的類型,并進(jìn)行了校驗(yàn)。
6. 通過createResponseConverter
方法獲取Converter
對(duì)象,它可以完成從ResponseBody
到Response
類型ResponseT
的轉(zhuǎn)換。
7. 如果并非 Kotlin 的 suspend 方法,則直接傳入CallAdapter
及 Converter,創(chuàng)建CallAdapted
對(duì)象。
8. 否則根據(jù) suspend 方法需要的是Response
還是具體的類型,分別返回SuspendForResponse
和SuspendForBody
對(duì)象。
可以發(fā)現(xiàn),新版的 Retrofit 對(duì) Kotlin 的協(xié)程進(jìn)行了支持。HttpServiceMethod.parseAnnotations
的主要作用就是創(chuàng)建 CallAdapter
以及 Converter 對(duì)象,并構(gòu)建對(duì)應(yīng) HttpServiceMethod
。
CallAdapter
是用于將Call<R>
對(duì)象適配為需要的類型 T 對(duì)象的。它的聲明如下:
public interface CallAdapter<R, T> { // 返回 Response 的類型 Type responseType(); // 將 Call<R> 轉(zhuǎn)換為 T 類型 T adapt(Call<R> call); }
我們先看看 createCallAdapter
方法是如何對(duì)它創(chuàng)建的:
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); } }
它調(diào)用了 retrofit.callAdapter
方法:
public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) { return nextCallAdapter(null, returnType, annotations); }
之后調(diào)用到 retrofit.nextCallAdapter
方法:
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++) { // 遍歷 callAdapterFactories,嘗試創(chuàng)建 CallAdapter CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this); if (adapter != null) { return adapter; } } // ...不存在對(duì)應(yīng)的 CallAdapterFactory,拋出異常 }
這里實(shí)際上是遍歷了創(chuàng)建 Retrofit 對(duì)象時(shí)傳遞的 CallAdapter.Factory
列表嘗試去創(chuàng)建 CallAdapter
。如果這些 CallAdapter.Factory
都無法處理這個(gè)對(duì)應(yīng)的 returnType
以及 annotations
的話,則會(huì)拋出異常。(前面 Factory 的優(yōu)先級(jí)更高)
Retrofit 中有一個(gè)默認(rèn)的 CallAdapter
工廠 DefaultCallAdapterFactory
,它的優(yōu)先級(jí)比所有自定義工廠要低,它在創(chuàng)建時(shí)會(huì)傳入一個(gè) Executor
,我們可以看到它的 get 方法:
@Override public @Nullable CallAdapter<?, ?> get( Type returnType, Annotation[] annotations, Retrofit retrofit) { if (getRawType(returnType) != Call.class) { return null; } if (!(returnType instanceof ParameterizedType)) { throw new IllegalArgumentException( "Call return type must be parameterized as Call<Foo> or Call<? extends Foo>"); } final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType); final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class) ? null: callbackExecutor; 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); } }; }
可以看到,在沒有 Executor
時(shí),它不對(duì) Call 進(jìn)行修改,在有指定 Executor 時(shí),則會(huì)將其包裝為 ExecutorCallbackCall
。一般來說這個(gè) Executor 就是創(chuàng)建 Retrofit 時(shí)指定的 callbackExecutor
。
這個(gè) callbackExecutor
實(shí)際上是用來指定調(diào)用 Callback
的線程的,從而使得 Callback
并不一定是在主線程被回調(diào):
static final class ExecutorCallbackCall<T> implements Call<T> { final Executor callbackExecutor; final Call<T> delegate; ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) { this.callbackExecutor = callbackExecutor; this.delegate = delegate; } @Override public void enqueue(final Callback<T> callback) { Objects.requireNonNull(callback, "callback == null"); // 對(duì) Callback 進(jìn)行了包裝,通過 callbackExecutor 進(jìn)行回調(diào) delegate.enqueue(new Callback<T>() { @Override public void onResponse(Call<T> call, final Response<T> response) { 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)); } }); } // ... }
可以看到,這里實(shí)際上只是對(duì) Callback 進(jìn)行了包裝,通過傳遞的 Executor 進(jìn)行回調(diào),從而對(duì) callbackExecutor
進(jìn)行支持。
接著我們看看 Converter
類,它是一個(gè)接口,用于將類型 F 的數(shù)據(jù)轉(zhuǎn)換為類型 T:
public interface Converter<F, T> { @Nullable T convert(F value) throws IOException; // ... }
接著我們看看 createResponseConverter
是如何對(duì)它進(jìn)行創(chuàng)建的:
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); } }
轉(zhuǎn)調(diào)到了 retrofit.responseBodyConverter
:
public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) { return nextResponseBodyConverter(null, type, annotations); }
轉(zhuǎn)調(diào)到了 nextResponseBodyConverter
:
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; } } // 沒有找到對(duì)應(yīng)的 ConverterFactory 進(jìn)行處理,拋出異常 }
可以看到,這里與 CallAdapter
工廠類似,遍歷創(chuàng)建 Retrofit 時(shí)傳入的 Converter.Factory
列表,嘗試進(jìn)行創(chuàng)建,如果沒有工廠能對(duì)其進(jìn)行處理,拋出異常。(前面 Factory 的優(yōu)先級(jí)更高)
Retrofit
中內(nèi)置了兩個(gè) Converter.Factory
,分別是 BuiltInConverters
以及 OptionalConverterFactory
。
其中 BuiltInConverters
的優(yōu)先級(jí)比所有自定義工廠要高,以避免其他工廠覆蓋它的方法,而 OptionalConverterFactory
的優(yōu)先級(jí)比所有自定義工廠的優(yōu)先級(jí)更低。
BuiltInConverters
中實(shí)現(xiàn)了多個(gè)轉(zhuǎn)換器如將 ResponseBody
轉(zhuǎn)換為 Void 或 Unit,將 Object 轉(zhuǎn)換為 String 等。
OptionalConverterFactory
是通過 platform 獲取到的 defaultConverterFactories
,它是為了支持 Java 8 的 Optional 而實(shí)現(xiàn)的,Optional 是 Java 8 引入的用來解決空指針異常的類。
接著我們看看之前創(chuàng)建的 ServiceMethod
類,它是一個(gè)抽象類,需要子類對(duì) invoke 方法進(jìn)行實(shí)現(xiàn)。
abstract class ServiceMethod<T> { abstract @Nullable T invoke(Object[] args); }
它的子類就是前面提到的 HttpServiceMethod
abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> { @Override final @Nullable ReturnT invoke(Object[] args) { Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter); return adapt(call, args); } protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args); }
HttpServiceMethod
的 invoke 方法非常簡(jiǎn)單,它構(gòu)造了一個(gè) OkHttpCall
,然后通過 adapt 這個(gè)虛函數(shù)來實(shí)現(xiàn)對(duì) Call 的轉(zhuǎn)換。它的子類只需要實(shí)現(xiàn) adapt 從而對(duì) Call 進(jìn)行轉(zhuǎn)換即可。
它共有三個(gè)子類,首先就是并非使用協(xié)程的情況下的 CallAdapted
類,另外兩個(gè)子類則是在使用協(xié)程的情況下為了配合協(xié)程的 SuspendForResponse
以及 SuspendForBody
類
CallAdapted
類繼承自 HttpServiceMethod
類,并通過傳遞進(jìn)來的 CallAdapter
對(duì) Call 進(jìn)行了轉(zhuǎn)換。
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); } }
SuspendForResponse
類首先根據(jù)傳遞進(jìn)來的 Call 構(gòu)造了一個(gè)參數(shù)為 Response<ResponseT>
的 Continuation
對(duì)象然后通過 Kotlin 實(shí)現(xiàn)的 awaitResponse
方法將 call 的 enqueue
異步回調(diào)過程封裝為了一個(gè) suspend 的函數(shù)。
static final class SuspendForResponse<ResponseT> extends HttpServiceMethod<ResponseT, Object> { private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter; SuspendForResponse(RequestFactory requestFactory, okhttp3.Call.Factory callFactory, Converter<ResponseBody, ResponseT> responseConverter, CallAdapter<ResponseT, Call<ResponseT>> callAdapter) { super(requestFactory, callFactory, responseConverter); this.callAdapter = callAdapter; } @Override protected Object adapt(Call<ResponseT> call, Object[] args) { call = callAdapter.adapt(call); //noinspection unchecked Checked by reflection inside RequestFactory. Continuation<Response<ResponseT>> continuation = (Continuation<Response<ResponseT>>) args[args.length - 1]; // See SuspendForBody for explanation about this try/catch. try { return KotlinExtensions.awaitResponse(call, continuation); } catch (Exception e) { return KotlinExtensions.suspendAndThrow(e, continuation); } } }
awaitResponse
方法如下:
suspend fun <T : Any> Call<T>.awaitResponse(): Response<T> { return suspendCancellableCoroutine { continuation -> continuation.invokeOnCancellation { cancel() } enqueue(object : Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) { continuation.resume(response) } override fun onFailure(call: Call<T>, t: Throwable) { continuation.resumeWithException(t) } }) } }
可以看到,分別通過在 onResponse
和 onFailure
中調(diào)用 continuation.resume
和 continuation.resumeWithException
從而對(duì)協(xié)程進(jìn)行支持。
而 SuspendForBody
則是根據(jù)傳遞進(jìn)來的 Call 構(gòu)造了一個(gè) Continuation<ResponseT>
對(duì)象然后通過 Kotlin 實(shí)現(xiàn)的 await 或 awaitNullable
方法將 call 的 enqueue
異步回調(diào)過程封裝為了一個(gè) suspend 的函數(shù)。
static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> { private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter; private final boolean isNullable; SuspendForBody(RequestFactory requestFactory, okhttp3.Call.Factory callFactory, Converter<ResponseBody, ResponseT> responseConverter, CallAdapter<ResponseT, Call<ResponseT>> callAdapter, boolean isNullable) { super(requestFactory, callFactory, responseConverter); this.callAdapter = callAdapter; this.isNullable = isNullable; } @Override protected Object adapt(Call<ResponseT> call, Object[] args) { call = callAdapter.adapt(call); //noinspection unchecked Checked by reflection inside RequestFactory. Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1]; // Calls to OkHttp Call.enqueue() like those inside await and awaitNullable can sometimes // invoke the supplied callback with an exception before the invoking stack frame can return. // Coroutines will intercept the subsequent invocation of the Continuation and throw the // exception synchronously. A Java Proxy cannot throw checked exceptions without them being // in an UndeclaredThrowableException, it is intercepted and supplied to a helper which will // force suspension to occur so that it can be instead delivered to the continuation to // bypass this restriction. try { return isNullable ? KotlinExtensions.awaitNullable(call, continuation) : KotlinExtensions.await(call, continuation); } catch (Exception e) { return KotlinExtensions.suspendAndThrow(e, continuation); } } }
Call 實(shí)際上是一個(gè)接口,它提供了 execute
、enqueue
、cancel
等接口用于實(shí)現(xiàn)請(qǐng)求,當(dāng)我們需要請(qǐng)求一個(gè)接口的時(shí)候,只需要調(diào)用其 enqueue
或 execute
方法即可。
public interface Call<T> extends Cloneable { Response<T> execute() throws IOException; void enqueue(Callback<T> callback); boolean isExecuted(); void cancel(); boolean isCanceled(); Call<T> clone(); Request request(); }
從前面的過程中我們可以了解到,如果我們沒有傳入 CalAdapter
的話,默認(rèn)情況下返回的 Call 實(shí)際上是 OkHttpCall
對(duì)象,讓我們通過它來看看 Retrofit 如何基于 OkHttp
實(shí)現(xiàn)的網(wǎng)絡(luò)請(qǐng)求:
首先讓我們看看 enqueue
的代碼:
@Override public void enqueue(final Callback<T> callback) { Objects.requireNonNull(callback, "callback == null"); okhttp3.Call call; Throwable failure; // 加鎖,對(duì)狀態(tài)進(jìn)行設(shè)置并通過 createRawCall 方法創(chuàng)建 okhttp3.Call 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; } // 如果外界取消該任務(wù),則調(diào)用 okhttp3.Call.cancel if (canceled) { call.cancel(); } // 通過 okhttp3.Call.enqueue 將消息入隊(duì) call.enqueue(new okhttp3.Callback() { @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) { Response<T> response; try { // 獲得結(jié)果后通過 parseResponse 進(jìn)行解析 response = parseResponse(rawResponse); } catch (Throwable e) { throwIfFatal(e); callFailure(e); return; } try { // 解析完成后通過 onResponse 進(jìn)行回調(diào) callback.onResponse(OkHttpCall.this, response); } catch (Throwable t) { throwIfFatal(t); t.printStackTrace(); // TODO this is not great } } @Override public void onFailure(okhttp3.Call call, IOException e) { // 請(qǐng)求失敗調(diào)用 callFailure 回調(diào)失敗請(qǐng)求 callFailure(e); } private void callFailure(Throwable e) { try { callback.onFailure(OkHttpCall.this, e); } catch (Throwable t) { throwIfFatal(t); t.printStackTrace(); // TODO this is not great } } }); }
enqueue
的代碼看似多,實(shí)際上比較簡(jiǎn)單,主要分為以下幾步:
1. 加鎖,對(duì)執(zhí)行狀態(tài)進(jìn)行設(shè)置,若不存在 rawCall 則調(diào)用 createRawCall 方法創(chuàng)建 okhttp3.Call 對(duì)象。
2. 如果外界取消該任務(wù),則調(diào)用 okhttp3.Call.cancel
3. 通過 okhttp3.Call.enqueue 將消息入隊(duì)
4. 若獲得 Response,則通過 parseResponse 方法對(duì) Response 進(jìn)行解析,解析完成后通過 onResponse 回調(diào)解析結(jié)果。
5. 若請(qǐng)求失敗,通過 callFailure 方法調(diào)用 onFailure 回調(diào)請(qǐng)求失敗。
可以發(fā)現(xiàn)一個(gè)小細(xì)節(jié),Retrofit 對(duì)已經(jīng)創(chuàng)建的 okhttp3.Call
進(jìn)行了復(fù)用,避免了重復(fù)創(chuàng)建從而浪費(fèi)效率。
接著讓我們看看 execute
是如何實(shí)現(xiàn)的:
@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 if (creationFailure instanceof RuntimeException) { throw (RuntimeException) creationFailure; } else { throw (Error) creationFailure; } } call = rawCall; if (call == null) { try { call = rawCall = createRawCall(); } catch (IOException | RuntimeException | Error e) { throwIfFatal(e); // Do not assign a fatal error to creationFailure.creationFailure = e;throw e; } } } if (canceled) { call.cancel(); } return parseResponse(call.execute()); }
也非常簡(jiǎn)單:
1.首先加鎖后對(duì)執(zhí)行狀態(tài)進(jìn)行設(shè)置,若不存在
rawCall
則調(diào)用createRawCall
方法創(chuàng)建okhttp3.Call
對(duì)象。
2.如果外界取消該任務(wù),則調(diào)用okhttp3.Call.cancel
。
3.若獲得Response
,則通過parseResponse
方法對(duì)Response
進(jìn)行解析并返回
接著讓我們看看 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; }
它實(shí)際上是調(diào)用了 callFactory
的 newCall
方法進(jìn)行創(chuàng)建,而傳入的 okhttp3.Request
則是通過 requestFactory.create
創(chuàng)建的:
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); 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]); } return requestBuilder.get(.tag(Invocation.class, new Invocation(method, argumentList)) .build(); }
這里首先構(gòu)建了一個(gè) RequestBuilder
,之后通過遍歷 ParamterHandler
列表并調(diào)用其 apply 方法將參數(shù)應(yīng)用到 RequestBuilder
中。
接著我們看看 parseResponse
是如何對(duì) Response 進(jìn)行解析的:
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; } }
可以看到,它會(huì)通過 Converter.convert
方法將 Response 的 body
轉(zhuǎn)換為我們所需要的類型。
那么到這里,Retrofit
的源碼我們就基本上看完了,可以發(fā)現(xiàn)它的代碼中基本很少涉及到對(duì)網(wǎng)絡(luò)請(qǐng)求的處理,基本都是對(duì)于 OkHttp
的封裝,在 OkHttp
現(xiàn)有的 API 上提供了一套更便于使用的框架。
Retrofit
很好地向我們證明了,如何在一套已經(jīng)固定的 API 上,以最優(yōu)雅的方式對(duì)現(xiàn)有的框架進(jìn)行封裝,拓展出更為強(qiáng)大的功能。
Retrofit
的注解是一種運(yùn)行時(shí)注解,它通過動(dòng)態(tài)代理對(duì) Service 對(duì)象進(jìn)行創(chuàng)建,通過反射對(duì)注解進(jìn)行解析,這樣雖然會(huì)有一定性能的損耗,但性能的損耗卻帶來了十分易用的 API。用戶所寫的 Service 接口中,每一個(gè) ServiceMethod
都對(duì)應(yīng)了一個(gè)接口。
對(duì)于 Service
的創(chuàng)建,它是通過動(dòng)態(tài)代理,對(duì)每個(gè)接口內(nèi)定義的方法進(jìn)行了代理,若設(shè)置 validateEagerly
了則會(huì)在創(chuàng)建 Service 接口對(duì)象時(shí)進(jìn)行注解的解析以及 ServiceMethod
的創(chuàng)建,否則會(huì)在方法調(diào)用時(shí)再創(chuàng)建對(duì)應(yīng)的 ServiceMethod
對(duì)象,在多次調(diào)用的情況下,它通過 serviceMethodCache
對(duì)已經(jīng)解析的 ServiceMethod
進(jìn)行了緩存從而避免了重復(fù)解析帶來的性能損耗。
這個(gè)對(duì)象的創(chuàng)建首先會(huì)經(jīng)過 RequestFactory.parseAnnotations
對(duì)方法中的注解進(jìn)行解析:
對(duì)于方法的請(qǐng)求方式注解,它會(huì)通過 parseHttpMethodAndPath
方法獲取注解對(duì)應(yīng)的請(qǐng)求方式以及注解中的 url 等信息并保存起來,在創(chuàng)建請(qǐng)求的時(shí)候設(shè)置進(jìn) RequestBuilder
。
對(duì)于方法的 Headers 注解,它會(huì)將 Header 注解解析為 Headers 對(duì)象并保存起來,在創(chuàng)建請(qǐng)求的時(shí)候設(shè)置進(jìn) RequestBuilder
。
對(duì)于方法的參數(shù),它會(huì)將每個(gè)參數(shù)根據(jù)具體的注解(@Query 等)解析為對(duì)應(yīng)的 ParamterHandler
,在創(chuàng)建請(qǐng)求的時(shí)候,會(huì)通過它的 apply 方法將參數(shù)提交到 RequestBuilder
中。
之后,這個(gè)對(duì)象會(huì)通過 HttpServiceMethod.parseAnnotations
對(duì) ServiceMethod
對(duì)象進(jìn)行創(chuàng)建,它在創(chuàng)建的過程中同時(shí)進(jìn)行了接口對(duì)應(yīng)的 CallAdapter
以及 Converter
的創(chuàng)建。
其中,CallAdapter
用于將Call<R>
對(duì)象適配為需要的類型 T 對(duì)象,也就是對(duì) Call 進(jìn)行轉(zhuǎn)換。
而 Converter
則是用于將 F 類型的數(shù)據(jù)轉(zhuǎn)換為 T,往往是用于對(duì) Response
的 body 進(jìn)行轉(zhuǎn)換。
對(duì)于 CallAdapter
和 Converter
都是通過它們對(duì)應(yīng)的工廠類進(jìn)行創(chuàng)建,創(chuàng)建時(shí)會(huì)根據(jù)工廠列表的順序從前向后嘗試進(jìn)行創(chuàng)建,也就是說在工廠列表中越靠前的工廠其優(yōu)先級(jí)越大。
同時(shí),Retrofit
還引入了對(duì) Continuation
協(xié)程的支持,它會(huì)將 ServerMethod
最終包裝為一個(gè) suspend
方法從而對(duì)協(xié)程進(jìn)行支持。
Retrofit
的網(wǎng)絡(luò)請(qǐng)求的執(zhí)行依賴于 OkHttp
,它首先會(huì)通過 RequestFactory
進(jìn)行 Request 的構(gòu)造,它的參數(shù)通過前面解析的信息得來。之后會(huì)將這個(gè) Request 包裝為一個(gè) okhttp3.Call
,在同步和異步請(qǐng)求時(shí)分別調(diào)用其對(duì)應(yīng)的 execute 及 enqueue
方法。同時(shí),為了避免okhttp3.Call
的重復(fù)創(chuàng)建,它對(duì)之前創(chuàng)建的 okhttp3.Call
進(jìn)行了復(fù)用。
以上是“OkHttp與Retrofit的區(qū)別有哪些”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。