Retrofit源碼解析

一、引言

RetrofitOKHttp同為 square 出品的網(wǎng)絡(luò)請(qǐng)求相關(guān)庫,不同的是 Retrofit本身不進(jìn)行網(wǎng)絡(luò)請(qǐng)求,而是作為一個(gè)協(xié)調(diào)者,協(xié)調(diào)其他組件共同處理網(wǎng)絡(luò)請(qǐng)求。用官網(wǎng)描述來說就是:Retrofit是可插拔的,它允許不同的執(zhí)行機(jī)制和類庫用于執(zhí)行HTTP請(qǐng)求、允許不同序列化的類庫進(jìn)行java實(shí)體類與HTTP響應(yīng)數(shù)據(jù)之間轉(zhuǎn)換。

Retrofit的網(wǎng)絡(luò)請(qǐng)求部分默認(rèn)基于OkHttp,關(guān)于OkHttp,鄙人寫過 OkHttp源碼分析一文,感興趣的童鞋可以看看。

本文純屬基于個(gè)人理解,源碼解析不限于執(zhí)行流程,因此受限于知識(shí)水平,有些地方可能依然沒有理解到位,還請(qǐng)發(fā)現(xiàn)問題的童鞋理性指出。

溫馨提示:本文源碼基于 Retrofit-2.4.0

二、流程分析

1. 簡(jiǎn)單使用

這里以請(qǐng)求 玩Android 首頁數(shù)據(jù)為例,演示使用Retrofit進(jìn)行網(wǎng)絡(luò)請(qǐng)求的最基本方式。

首先如下初始化 Retrofit

public void initializeRetrofit() {  
    retrofit = new Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl("http://wanandroid.com")
            .build();
}

然后如下建立請(qǐng)求接口:

public interface Service{ 
    @GET("article/list/{page}/json")
    public Call<ResponseEntry<ResponseData>> getHomeList(@Path("page")int page);
}

接著如下調(diào)用請(qǐng)求、處理響應(yīng)數(shù)據(jù):

public void getHomeList(int page, ResCallback<ResponseEntry<ResponseData>> callback){
    if (service == null)
        service = retrofit.create(Service.class);
    service.getHomeList(page).enqueue(new Callback<ResponseEntry<ResponseData>>() {
            @Override
            public void onResponse(Call<ResponseEntry<ResponseData>> call, Response<ResponseEntry<ResponseData>> response) {
                System.out.println(response.message());
                System.out.println(response.code());
                System.out.println(response.headers());

                if (response.isSuccessful()){
                    ResponseEntry<ResponseData> body = response.body();
                    if (body == null) {
                        callback.onFailed(new Exception("body is null !!"));
                        return;
                    }
                    callback.onSuccess(body);
                }
            }
            @Override
            public void onFailure(Call<ResponseEntry<ResponseData>> call, Throwable t) {
            }
        });
}

上面可以注意到的一點(diǎn)是,不同于直接使用OkHttp,這里response.body()可以直接拿到我們需要的解析好的Java實(shí)體類了,而不需要再做Json數(shù)據(jù)解析工作, 它的使用過程如下:

Retrofit使用過程

而一般來說,我們使用OkHttp進(jìn)行網(wǎng)絡(luò)請(qǐng)求的使用過程如下:

一般OkHttp使用過程

顯然 Retrofit 的目的就是把網(wǎng)絡(luò)請(qǐng)求、響應(yīng)數(shù)據(jù)解析等相互分離的操作都整合到一起,達(dá)到 All in one 的效果,而實(shí)際請(qǐng)求和解析都是以可插拔的插件形式存在,靈活度非常高。

2. 創(chuàng)建服務(wù)到建立Call過程分析

關(guān)于 Retrofit的構(gòu)建 ,我們注意一下必填參數(shù)以及默認(rèn)參數(shù)即可,根據(jù)如下Retrofit.Build#build源碼可知:

  • baseUrl必填
  • 默認(rèn)callFactoryOkHttpClient;默認(rèn)callbackExecutor(回調(diào)執(zhí)行器)在Android中是主線程的Handler;默認(rèn)會(huì)先添加Retrofit內(nèi)部的轉(zhuǎn)換器,然后是其他,比如我們自定義的轉(zhuǎn)換器,這是為了避免內(nèi)部轉(zhuǎn)換器的行為被復(fù)寫掉,以及確保使用消耗(consume)所有類型的轉(zhuǎn)換器時(shí)能有正確的行為。
public Retrofit build() {
    // baseUrl必填
    if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
    }
    // 默認(rèn)Call工廠為 OkHttpClient
    okhttp3.Call.Factory callFactory = this.callFactory;
    if (callFactory == null) {
        callFactory = new OkHttpClient();
    }
    // 默認(rèn)回調(diào)執(zhí)行器為主線程Handler
    Executor callbackExecutor = this.callbackExecutor;
    if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
    } 
    List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
    callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
 
    List<Converter.Factory> converterFactories = new ArrayList<>(
        1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize()); 
    // 這里會(huì)先添加Retrofit內(nèi)部的轉(zhuǎn)換器再添加我們自定的轉(zhuǎn)換器
    converterFactories.add(new BuiltInConverters());
    converterFactories.addAll(this.converterFactories);
    converterFactories.addAll(platform.defaultConverterFactories());
    // ...
}

這里關(guān)注一下Android 平臺(tái)的回調(diào)執(zhí)行器,因?yàn)榛卣{(diào)執(zhí)行在主線程的Handler上,因此可以在回調(diào)中直接操作UI控件。

static class Android extends Platform {
    @Override public Executor defaultCallbackExecutor() {
        return new MainThreadExecutor();
    }
    static class MainThreadExecutor implements Executor {
        // UI線程
        private final Handler handler = new Handler(Looper.getMainLooper());
        @Override public void execute(Runnable r) {
            handler.post(r);
        }
    }
    // ...
}

接著來分析一下使用Retrofit#create創(chuàng)建一個(gè)請(qǐng)求服務(wù)實(shí)例時(shí)發(fā)生了什么,Retrofit#create源碼如下,可知:

  • 首先需要確定的是service本身是個(gè)接口,并且不繼承于其他接口。

  • 然后重點(diǎn)來了,eagerlyValidateMethods會(huì)通過反射獲取service接口中所有的方法,接著嘗試從ServiceMethod緩存池中查找對(duì)應(yīng)于各個(gè)方法的ServiceMethod,如果沒找到的話,則重新通過ServiceMethod.parseAnnotations去解析各個(gè)方法的注解,解析完成后將返回的ServiceMethod(這里返回的ServiceMethod其實(shí)是實(shí)現(xiàn)類HttpServiceMethod,HttpServiceMethod會(huì)負(fù)責(zé)根據(jù)解析的注解參數(shù)創(chuàng)建Call,并在HttpServiceMethod#invoke調(diào)用時(shí)執(zhí)行網(wǎng)絡(luò)請(qǐng)求)存入緩存池中,方便后續(xù)復(fù)用,這里緩存池的作用跟線程池的概念異曲同工,都是為了減少因?yàn)槊看味冀馕觯▌?chuàng)建)而造成的不必要的性能損耗,所以干脆花點(diǎn)內(nèi)存存起來省事兒。eagerlyValidateMethods執(zhí)行過程如下:

    eagerlyValidateMethods執(zhí)行過程
  • 接著通過Proxy.newProxyInstance給服務(wù)接口創(chuàng)建一個(gè)代理實(shí)例,實(shí)際可轉(zhuǎn)成對(duì)應(yīng)接口的類型,這里主要關(guān)注一下InvocationHandler, 每個(gè)Proxy對(duì)象實(shí)例都會(huì)綁定一個(gè)InvocationHandler對(duì)象,當(dāng)執(zhí)行Proxy#invok方法時(shí),最終對(duì)派發(fā)給InvocationHandler#invok,也就是說,我們通過服務(wù)接口實(shí)例調(diào)用接口方法時(shí),最終都會(huì)通過InvocationHandler#invok去執(zhí)行。invoke方法執(zhí)行鏈如下:

    invoke執(zhí)行鏈
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();
          private final Object[] emptyArgs = new Object[0];

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation. 
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            // 這里實(shí)際最終執(zhí)行的是 HttpServiceMethod#invoke(..)
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
}

private void eagerlyValidateMethods(Class<?> service) {
    Platform platform = Platform.get();
    for (Method method : service.getDeclaredMethods()) {
        // 在 Android 7.0 以前版本都是 false,Android 7.0 及以上則根據(jù) `isDefaultMethod`的復(fù)寫值決定
      if (!platform.isDefaultMethod(method)) {
        loadServiceMethod(method);
      }
    }
}

ServiceMethod<?> loadServiceMethod(Method method) { 
    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
        // 先從服務(wù)方法緩存中獲取
      result = serviceMethodCache.get(method);
      if (result == null) {
        // 如果此前沒有解析,則重新解析注解
        result = ServiceMethod.parseAnnotations(this, method);
        // 然后將解析結(jié)果添加到緩存,以便后續(xù)復(fù)用
        serviceMethodCache.put(method, result);
      }
    }
    return result;
}

上面的 parseAnnotations 執(zhí)行鏈如下:

parseAnnotations執(zhí)行鏈

我們順著這條鏈看看,首先是ServiceMethod#parseAnnotations

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    // 1. 解析方法的注解參數(shù),保存在 RequestFactory
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
    // ...
    // 2. 使用將上面解析的參數(shù)建立Call,用于網(wǎng)絡(luò)請(qǐng)求
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }
}

接著是RequestFactory#parseAnnotations,源碼如下,主要做了三件事情,看注釋即可:

final class RequestFactory {
  static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
    return new Builder(retrofit, method).build();
  }
  static final class Builder{
    RequestFactory build() {
      // 1. 解析每個(gè)方法的注解
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
      // ... 
      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      // 2. 解析方法參數(shù)
      for (int p = 0; p < parameterCount; p++) {
        parameterHandlers[p] = parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p]);
      }
      // ...
      // 3. 創(chuàng)建 RequestFactory 保存參數(shù)
      return new RequestFactory(this);
    }
  }
}  

接著是 HttpServiceMethod#parseAnnotations,源碼如下:

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
    // 1. 獲取 Call 適配器
    CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method);
    Type responseType = callAdapter.responseType();
    // 2. 獲取響應(yīng)數(shù)據(jù)轉(zhuǎn)換器
    Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType);
    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    // 3. 根據(jù)解析的參數(shù)創(chuàng)建 HttpServiceMethod
    return new HttpServiceMethod<>(requestFactory, callFactory, callAdapter, responseConverter);
}

HttpServiceMethod#invok執(zhí)行時(shí)源碼如下:

  @Override ReturnT invoke(Object[] args) {
    // 創(chuàng)建一個(gè) OkHttpCall, 用于進(jìn)行網(wǎng)絡(luò)請(qǐng)求和響應(yīng)數(shù)據(jù)轉(zhuǎn)換
    return callAdapter.adapt(
        new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
  }

至此,便是一個(gè)服務(wù)接口從解析到創(chuàng)建成一個(gè)OkHttp#Call的過程,縱觀全局,其實(shí)這個(gè)過程就好比一個(gè)為了將如下接口:

public interface Service{ 
    @GET("article/list/{page}/json")
    public Call<ResponseEntry<ResponseData>> getHomeList(@Path("page")int page);
} 

解析成一個(gè)請(qǐng)求鏈接為http://wanandroid.com/article/list/0/json,請(qǐng)求方式為 GET,請(qǐng)求的調(diào)用方式為:

Service service = ...;
// 相當(dāng)于執(zhí)行 HttpServiceMethod#invoke 方法
Call<ResponseEntry<ResponseData>> = service.getHomeList(0);

的過程,而這個(gè)過程中需要解決將接口轉(zhuǎn)換成對(duì)象實(shí)例、將方法注解、參數(shù)解析處理拼接為請(qǐng)求連接、最后確定返回類型的問題,此時(shí)Call尚未進(jìn)行請(qǐng)求;

3. Call請(qǐng)求執(zhí)行到響應(yīng)數(shù)據(jù)回調(diào)過程分析

關(guān)于OkHttp#Call如何運(yùn)作的問題已經(jīng)在 OkHttp源碼解析 一文中做了詳細(xì)分析,這里的不同之處在于,在Retrofit中我們需要更多地關(guān)注它是如何協(xié)調(diào)請(qǐng)求和響應(yīng),最終回調(diào)給UI線程的。

OK,從HttpServiceMethod#invoke出發(fā),根據(jù)前面的內(nèi)容中我們已經(jīng)知道它會(huì)通過callAdapter.adapt(new OkHttpCall<>(requestFactory, args, callFactory, responseConverter))返回一個(gè)Call實(shí)例,并且在Android平臺(tái)上會(huì)將響應(yīng)數(shù)據(jù)回調(diào)在UI線程的Handler上,因此我們先關(guān)注一下Android平臺(tái)下的默認(rèn)CallAdapter,于是定位到Android#defaultCallAdapterFactories

@Override List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
    @Nullable Executor callbackExecutor) {
    if (callbackExecutor == null) throw new AssertionError();
    return singletonList(new ExecutorCallAdapterFactory(callbackExecutor));
} 

可見Android平臺(tái)下的默認(rèn)CallAdapterExecutorCallAdapterFactory, 于是可以定位到ExecutorCallAdapterFactory#adapt

@Override public Call<Object> adapt(Call<Object> call) {
    return new ExecutorCallbackCall<>(callbackExecutor, call);
} 

ExecutorCallbackCall這里實(shí)際是使用了裝飾器模式,它將工作委托給了callbackExecutordelegate,而它自身僅僅起到了協(xié)調(diào)作用,將響應(yīng)數(shù)據(jù)回調(diào)到UI線程:

 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) {
      checkNotNull(callback, "callback == null");
      delegate.enqueue(new Callback<T>() {
        @Override public void onResponse(Call<T> call, final Response<T> response) {
          // 回調(diào)至主線程
          callbackExecutor.execute(new Runnable() {
            @Override public void run() {
              if (delegate.isCanceled()) { 
                callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
              } else {
                callback.onResponse(ExecutorCallbackCall.this, response);
              }
            }
          });
        }   // ...
      });
    }   // ...
  }

具體到網(wǎng)絡(luò)請(qǐng)求的執(zhí)行與響應(yīng)數(shù)據(jù)的轉(zhuǎn)換工作還得看OkHttpCall,這里我們只關(guān)注一下OKHttpCall#enqueue即可, 可見這里除了請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)外,還會(huì)先轉(zhuǎn)換響應(yīng)數(shù)據(jù)后再回調(diào)給上一級(jí):

  @Override public void enqueue(final Callback<T> callback) {
    
    okhttp3.Call call;
    Throwable failure;
    // 1. 執(zhí)行請(qǐng)求
    call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
        Response<T> response;
        try {
            // 2. 解析響應(yīng)數(shù)據(jù),將網(wǎng)絡(luò)響應(yīng)數(shù)據(jù)轉(zhuǎn)換成指定數(shù)據(jù)類型
          response = parseResponse(rawResponse);
        } catch (Throwable e) { 
            // ...
          return;
        } 
        try {
            // 3. 將解析完成的數(shù)據(jù)回調(diào)給上一級(jí)
          callback.onResponse(OkHttpCall.this, response);
        } catch (Throwable t) {
            // ...
        }
      }
     // ... 
    });
  } 

然后parseResponse部分源碼如下,可見這里會(huì)通過Converter網(wǎng)絡(luò)響應(yīng)數(shù)據(jù)轉(zhuǎn)換為我們指定的數(shù)據(jù)類型:

  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 {
      // 通過轉(zhuǎn)換器轉(zhuǎn)換數(shù)據(jù) 
      T body = responseConverter.convert(catchingBody);
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
      // ...
    }
  }

綜上可知,最終網(wǎng)絡(luò)請(qǐng)求會(huì)在OkHttpCall中執(zhí)行,獲取響應(yīng)數(shù)據(jù)后通過設(shè)定的Converter轉(zhuǎn)換器將數(shù)據(jù)轉(zhuǎn)換成指定類型;而最終回調(diào)給UI線程則是在ExecutorCallbackCall中進(jìn)行,作為裝飾器,它實(shí)際將請(qǐng)求和響應(yīng)數(shù)據(jù)處理工作都委托給了OkHttpCall,而自身僅僅做了最終數(shù)據(jù)的回調(diào)處理。

于是整體執(zhí)行流程如下:

整體執(zhí)行流程

三、Proxy

這里指的是反射工具類中的java.lang.reflect.Proxy,通過前面的分析,我們已經(jīng)知道,我們建立的服務(wù)接口會(huì)通過Proxy.newProxyInstance來實(shí)例化一個(gè)代理對(duì)象實(shí)例,而通過這個(gè)實(shí)例化的對(duì)象,就能像使用普通類對(duì)象實(shí)例一個(gè)調(diào)用方法。

這里我比較好奇的是它是如何給接口實(shí)例化的,因此咱就來研究研究,定位到Proxy#newProxyInstance,精簡(jiǎn)一下源碼(去除了驗(yàn)證邏輯等),如下,可以發(fā)現(xiàn)Proxy會(huì)為我們的服務(wù)接口構(gòu)建一個(gè)代理類(當(dāng)然會(huì)先從代理類緩存,也就是WeakCache中查找已經(jīng)構(gòu)建的代理類),然后通過這個(gè)類的構(gòu)造函數(shù)構(gòu)建出一個(gè)實(shí)例對(duì)象出來:

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    { 
        final Class<?>[] intfs = interfaces.clone(); 
        // 1. 從 `WeakCache`中查找,或者創(chuàng)建一個(gè)接口的代理類 
        Class<?> cl = getProxyClass0(loader, intfs);
        // 2. 拿到代理類的構(gòu)造函數(shù)
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h; 
        // ...
        // 3. 通過構(gòu)造函數(shù)創(chuàng)建一個(gè)實(shí)例對(duì)象
        return cons.newInstance(new Object[]{h});
    }

再來看看getProxyClass0(), 根據(jù)代碼注釋可知,如果根據(jù)類加載器查找已經(jīng)實(shí)現(xiàn)的代理類,那么直接返回拷貝的緩存,如果沒找到,那么就會(huì)通過ProxyClassFactory去創(chuàng)建一個(gè)代理類。

private static Class<?> getProxyClass0(ClassLoader loader,
                                        Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    return proxyClassCache.get(loader, interfaces);
}

于是再來看看ProxyClassFactory,可知通過其apply方法會(huì)根據(jù)我們服務(wù)接口的信息配置代理類,然后通過ProxyGenerator生成一個(gè)代理類class文件,最終通過defineClass0將這個(gè)代理類定義出來:

private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    private static final String proxyClassNamePrefix = "$Proxy";
    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        // ... 
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
        // 1. 進(jìn)行一系列的代理類信息的配置
        //... 
        // 2. 根據(jù)配置信息生成代理類class文件
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
        // 3. 最終生成特定代理類
        return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
    }
}

這里的defineClass0是個(gè)native方法,因此就不再深挖了:

  private static native Class<?> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);

至此,是不是已經(jīng)明白了Proxy如何實(shí)例化接口的呢?

四、總結(jié)

通過上面的分析,可以發(fā)現(xiàn) Retrofit 更像是對(duì)一個(gè)OkHttp請(qǐng)求的抽取與封裝:

  • 網(wǎng)絡(luò)請(qǐng)求參數(shù)全部抽離成服務(wù)接口方法的注解,注解參數(shù)解析和Request構(gòu)建工作抽離到了RequestFactory。
  • CallAdapterOkHttpCall的執(zhí)行匹配到我們指定的執(zhí)行器,而Converter則將網(wǎng)絡(luò)響應(yīng)數(shù)據(jù)轉(zhuǎn)換成我們想要的類型
  • 最終,在Android平臺(tái)上直接將指定的數(shù)據(jù)類型返回給UI線程的Handler處理。

關(guān)于Proxy,它將服務(wù)接口轉(zhuǎn)換成一個(gè)代理類對(duì)象實(shí)例的實(shí)現(xiàn)方式也很值得我們學(xué)習(xí)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容