Glide源碼分析之一 with() + into()解析

相關(guān)文章

Glide源碼分析之一
Glide源碼分析之二
Glide源碼分析之三

文章基于3.7.0。主要參考郭神的Glide源碼解析。

簡單使用

      String imgUrl = "https://www.baidu.com/img/bd_logo1.png?where=super";
       
        Glide.with(this).load(imgUrl).into(imageView);

        Glide.with(getApplicationContext())
                .load(imgUrl)
                .asGif()
                .asBitmap()
                .placeholder(R.mipmap.ic_launcher)
                .error(R.mipmap.ic_launcher)
                .override(300,300)
                .fitCenter()
                .centerCrop()
                .skipMemoryCache(true)
                .diskCacheStrategy(DiskCacheStrategy.NONE)
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .diskCacheStrategy(DiskCacheStrategy.RESULT)
                .diskCacheStrategy(DiskCacheStrategy.SOURCE)
                .priority(Priority.HIGH)
                .into(imageView);

Model -(ModelLoader)-> Data -(Decoder)-> Resource -(Transform)-> TransformedResource -(Transcode)-> TranscodedResource --> Target

with() 到底做了什么?

==關(guān)鍵類==:RequestManagerRetriever、RequestManager

首先需要注意 with方法傳入的context對象將會(huì)決定我們Glide存活的生命周期。

    /** Begin a load with Glide by passing in a context.
     * <p>
     *     This method is appropriate for resources that will be used outside of the normal fragment or activity
     *     lifecycle (For example in services, or for notification thumbnails).
     * </p>
     *
     * @see #with(android.app.Activity)
     * @see #with(android.app.Fragment)
     * @see #with(android.support.v4.app.Fragment)
     * @see #with(android.support.v4.app.FragmentActivity)
     *
     * @param context Any context, will not be retained.
     * @return A RequestManager for the top level application that can be used to start a load.
     */
    public static RequestManager with(Context context) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(context);
    }
    
    
    /**
     * Begin a load with Glide that will be tied to the given {@link android.app.Activity}'s lifecycle and that uses the
     * given {@link Activity}'s default options.
     *
     * @param activity The activity to use.
     * @return A RequestManager for the given activity that can be used to start a load.
     */
    public static RequestManager with(Activity activity) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(activity);
    }

可以看到,with()方法的重載種類非常多,既可以傳入Activity,也可以傳入Fragment或者是Context。其實(shí)都是先調(diào)用RequestManagerRetriever的靜態(tài)get()方法得到一個(gè)RequestManagerRetriever對象,這個(gè)靜態(tài)get()方法是一個(gè)最基礎(chǔ)的單例模式。然后再調(diào)用RequestManagerRetriever的實(shí)例get()方法,去獲取RequestManager對象。其實(shí)無非就是兩種情況而已,即傳入Application類型的參數(shù),和傳入非Application類型的參數(shù)。

傳入Application類型的參數(shù)

代碼如下:

    public static RequestManager with(Context context) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(context);
    }
    
    
    
    public RequestManager get(Context context) {
        if (context == null) {
            throw new IllegalArgumentException("You cannot start a load on a null Context");
        } else if (Util.isOnMainThread() && !(context instanceof Application)) {
            if (context instanceof FragmentActivity) {
                return get((FragmentActivity) context);
            } else if (context instanceof Activity) {
                return get((Activity) context);
            } else if (context instanceof ContextWrapper) {
                return get(((ContextWrapper) context).getBaseContext());
            }
        }

        return getApplicationManager(context);
    }


    private RequestManager getApplicationManager(Context context) {
        // Either an application context or we're on a background thread.
        if (applicationManager == null) {
            synchronized (this) {
                if (applicationManager == null) {
                    // Normally pause/resume is taken care of by the fragment we add to the fragment or activity.
                    // However, in this case since the manager attached to the application will not receive lifecycle
                    // events, we must force the manager to start resumed using ApplicationLifecycle.
                    applicationManager = new RequestManager(context.getApplicationContext(),
                            new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
                }
            }
        }

        return applicationManager;
    }

這里插一句 不知道有沒有人好奇

RequestManagerTreeNode是干嘛的呢?

上文提到獲取所有childRequestManagerFragments的RequestManager就是通過該類獲得,就一個(gè)方法:getDescendants,作用就是基于給定的Context,獲取所有層級相關(guān)的RequestManager。上下文層級由Activity或者Fragment獲得,ApplicationContext的上下文不會(huì)提供RequestManager的層級關(guān)系,而且Application生命周期過長,所以Glide中對請求的控制只針對于Activity和Fragment。

繼續(xù)說,傳入Application類型,其實(shí)這是最簡單的一種情況,因?yàn)锳pplication對象的生命周期即應(yīng)用程序的生命周期,因此Glide并不需要做什么特殊的處理,它自動(dòng)就是和應(yīng)用程序的生命周期是同步的,如果應(yīng)用程序關(guān)閉的話,Glide的加載也會(huì)同時(shí)終止。

傳入非Application參數(shù)的情況

代碼如下:

public RequestManager get(Fragment fragment) {
        if (fragment.getActivity() == null) {
            throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
        }
        if (Util.isOnBackgroundThread()) {
            return get(fragment.getActivity().getApplicationContext());
        } else {
            FragmentManager fm = fragment.getChildFragmentManager();
            return supportFragmentGet(fragment.getActivity(), fm);
        }
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public RequestManager get(Activity activity) {
        if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            return get(activity.getApplicationContext());
        } else {
            assertNotDestroyed(activity);
            android.app.FragmentManager fm = activity.getFragmentManager();
            return fragmentGet(activity, fm);
        }
    }

如果我們是在非主線程當(dāng)中使用的Glide,那么不管你是傳入的Activity還是Fragment,都會(huì)被強(qiáng)制當(dāng)成Application來處理。

方法中傳入的是Activity、FragmentActivity、v4包下的Fragment、還是app包下的Fragment,最終的流程都是一樣的,那就是會(huì)調(diào)用fragmentGet()方法,向當(dāng)前的Activity當(dāng)中添加一個(gè)隱藏的RequestManagerFragment。

其實(shí),最終都是調(diào)用了fragmentGet()這個(gè)方法去獲取RequestManager,

 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
        RequestManagerFragment current = getRequestManagerFragment(fm);
        RequestManager requestManager = current.getRequestManager();
        if (requestManager == null) {
            requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
            //非常重要的一個(gè)方法,就是通過這個(gè)方法將我們空的fragment關(guān)聯(lián)到了Requestmanager關(guān)聯(lián)綁定到了一起
            //RequestManager和RequestManagerFragment都是一一對應(yīng)的
            current.setRequestManager(requestManager);
        }
        return requestManager;
    }
    
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm) {
        RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
        if (current == null) {
            current = pendingRequestManagerFragments.get(fm);
            if (current == null) {
                current = new RequestManagerFragment();
                pendingRequestManagerFragments.put(fm, current);
                fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
                //這里
                handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
            }
        }
        return current;
    }

可以看到RequestManagerRetriever其實(shí)就是一個(gè)RequestManager的生產(chǎn)類。

那這個(gè)RequsetManager是干什么的呢?

其實(shí)RequestManager是用于管理Glide的圖片加載請求的和完成glide對象的構(gòu)造。最重要的一點(diǎn)就是用于監(jiān)聽我們整個(gè)組件的生命周期。

那么這里為什么要添加一個(gè)隱藏的Fragment呢?

因?yàn)镚lide需要知道加載的生命周期。很簡單的一個(gè)道理,如果你在某個(gè)Activity上正在加載著一張圖片,結(jié)果圖片還沒加載出來,Activity就被用戶關(guān)掉了,那么圖片還應(yīng)該繼續(xù)加載嗎?當(dāng)然不應(yīng)該??墒荊lide并沒有辦法知道Activity的生命周期,于是Glide就使用了添加隱藏Fragment的這種小技巧,因?yàn)镕ragment的生命周期和Activity是同步的,如果Activity被銷毀了,F(xiàn)ragment是可以監(jiān)聽到的,這樣Glide就可以捕獲這個(gè)事件并停止圖片加載了。

生命周期事件的傳遞

Glide精妙設(shè)計(jì)之一

with()的源碼設(shè)計(jì)中比較重要,核心的一點(diǎn)來說就是,將Glide和組件的生命周期相掛鉤。

總體來說,第一個(gè)with()方法的源碼還是比較好理解的。其實(shí)就是為了得到一個(gè)RequestManager對象而已,然后Glide會(huì)根據(jù)我們傳入with()方法的參數(shù)來確定圖片加載的生命周期,并沒有什么特別復(fù)雜的邏輯,就是一個(gè)準(zhǔn)備好基礎(chǔ)配置的方法。

load()方法到底做了什么?

==關(guān)鍵詞==
DrawableTypeRequest,GenericRequestBuilder(是我們在glide當(dāng)中配置所有參數(shù)的父類,也就是說,只要是在glide當(dāng)中配置參數(shù),就一定是通過這個(gè)類或者他的子類來實(shí)現(xiàn)的)

ModelLoader(通過數(shù)據(jù)來源,將數(shù)據(jù)來源加載成原始數(shù)據(jù))

RequestTracker(直譯的話就是請求追蹤器,跟蹤圖片請求的整個(gè)周期,可以做取消,重啟一些失敗的圖片請求生命周期的管理主要由RequestTracker和TargetTracker處理。builder.createGlide() 創(chuàng)建Glide對象。

由于with()方法返回的是一個(gè)RequestManager對象,那么很容易就能想到,load()方法是在RequestManager類當(dāng)中的,所以說我們首先要看的就是RequestManager這個(gè)類。

那么我們先來看load()方法,這個(gè)方法中的邏輯是非常簡單的,只有一行代碼,就是先調(diào)用了fromString()方法,再調(diào)用load()方法,然后把傳入的圖片URL地址傳進(jìn)去。(也可以從源碼中看出,load有多個(gè)重載方法,支持String,file,Integer,byte等各種數(shù)據(jù)來源)

而fromString()方法也極為簡單,就是調(diào)用了loadGeneric()方法,并且指定參數(shù)為String.class,因?yàn)閘oad()方法傳入的是一個(gè)字符串參數(shù)。那么看上去,好像主要的工作都是在loadGeneric()方法中進(jìn)行的了。

 /**
     * Returns a request builder to load the given {@link java.lang.String}.
     * signature.
     *
     * @see #fromString()
     * @see #load(Object)
     *
     * @param string A file path, or a uri or url handled by {@link com.bumptech.glide.load.model.UriLoader}.
     */
    public DrawableTypeRequest<String> load(String string) {
        return (DrawableTypeRequest<String>) fromString().load(string);
    }
    
     public DrawableTypeRequest<String> fromString() {
     //傳入的是String的class對象
        return loadGeneric(String.class);
    }
    
     private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
        ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);as
        ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
                Glide.buildFileDescriptorModelLoader(modelClass, context);
        if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
            throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for"
                    + " which there is a registered ModelLoader, if you are using a custom model, you must first call"
                    + " Glide#register with a ModelLoaderFactory for your custom model class");
        }

        return optionsApplier.apply(
                new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
                        glide, requestTracker, lifecycle, optionsApplier));
    }

在loadGeneric()方法第一行可以看到有一句Glide.buildStreamModelLoader(modelClass, context)點(diǎn)進(jìn)去可以看到他不僅返回了ModelLoader對象,而且還初始化了Glide。點(diǎn)進(jìn)去看看:

/**
     * A method to build a {@link ModelLoader} for the given model that produces {@link InputStream}s using a registered
     * factory.
     *
     * @see #buildModelLoader(Class, Class, android.content.Context)
     */
    public static <T> ModelLoader<T, InputStream> buildStreamModelLoader(Class<T> modelClass, Context context) {
        return buildModelLoader(modelClass, InputStream.class, context);
    }
    
    
     public static <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass,
            Context context) {
         if (modelClass == null) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Unable to load null model, setting placeholder only");
            }
            return null;
        }
        return Glide.get(context).getLoaderFactory().buildModelLoader(modelClass, resourceClass);
    }
    
    /**
     * Get the singleton.
     *
     * @return the singleton
     */
    public static Glide get(Context context) {
        if (glide == null) {
            synchronized (Glide.class) {
                if (glide == null) {
                    Context applicationContext = context.getApplicationContext();
                    //解析清單文件配置的自定義GlideModule的metadata標(biāo)簽,返回一個(gè)GlideModule集合
                    List<GlideModule> modules = new ManifestParser(applicationContext).parse();

                    GlideBuilder builder = new GlideBuilder(applicationContext);
                    for (GlideModule module : modules) {
                        module.applyOptions(applicationContext, builder);
                    }
                    //初始化了glide的單例。
                    glide = builder.createGlide();
                    for (GlideModule module : modules) {
                        module.registerComponents(applicationContext, glide);
                    }
                }
            }
        }

        return glide;
    }

我們看到通過反射的方式獲取我們在清單文件中聲明的自定義的GlideModule對象。在獲取到GlideModule集合之后,遍歷了集合并調(diào)用相應(yīng)的applyOptions和registerComponents方法,而Glide對象的生成是通過GlideBuilder的createGlide方法創(chuàng)建。(底下有例子)

看到這里不知道大家會(huì)不會(huì)跟我有一樣的疑問,就是

GlideModule是個(gè)啥?干嘛用的?

可以通過GlideBuilder進(jìn)行一些延遲的配置和ModelLoaders的注冊。注意:
所有的實(shí)現(xiàn)的module必須是public的,并且只擁有一個(gè)空的構(gòu)造函數(shù),以便Glide懶加載的時(shí)候可以通過反射調(diào)用。
GlideModule是不能指定調(diào)用順序的。因此在創(chuàng)建多個(gè)GlideModule的時(shí)候,要注意不同Module之間的setting不要沖突了。

接下來看一下glide = builder.createGlide();這句代碼做了什么

Glide createGlide() {
        if (sourceService == null) {
        //查看核心線程數(shù)
            final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
            //初始化線程池
            sourceService = new FifoPriorityThreadPoolExecutor(cores);
        }
        if (diskCacheService == null) {
            diskCacheService = new FifoPriorityThreadPoolExecutor(1);
        }

        MemorySizeCalculator calculator = new MemorySizeCalculator(context);
        //初始化bitmapPool
        //圖片池用的是targetPoolSize(即一般是緩存大小是屏幕的寬高4*4).
        if (bitmapPool == null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
               
                int size = calculator.getBitmapPoolSize();
                bitmapPool = new LruBitmapPool(size);
            } else {
                bitmapPool = new BitmapPoolAdapter();
            }
        }

        ////內(nèi)存緩存用的是targetMemoryCacheSize (即一般是緩存大小是屏幕的寬 * 高 * 4 * 2)
        if (memoryCache == null) {
            memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
        }

        if (diskCacheFactory == null) {
        //磁盤緩存 默認(rèn)大小:250 MB,默認(rèn)目錄:image_manager_disk_cache.
        //Glide默認(rèn)是用InternalCacheDiskCacheFactory類來創(chuàng)建硬盤緩存的,這個(gè)類會(huì)在應(yīng)用的內(nèi)部緩存目錄下面創(chuàng)建一個(gè)最大容量250MB的緩存文件夾,使用這個(gè)緩存目錄而不用sd卡,意味著除了本應(yīng)用之外,其他應(yīng)用是不能訪問緩存的圖片文件的。
            diskCacheFactory = new InternalCacheDiskCacheFactory(context);
        }

        if (engine == null) {
        //引擎初始化
            engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);
        }

        if (decodeFormat == null) {
            decodeFormat = DecodeFormat.DEFAULT;
        }

        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
    }

看到這里其實(shí)大體的glide所做的內(nèi)容我們已經(jīng)清楚,其實(shí)Glide還支持動(dòng)態(tài)的緩存大小調(diào)整,在存在大量圖片的Activity/Fragment中,可以通過setMemoryCategory方法來提高Glide的內(nèi)存緩存大小,從而加快圖片的加載速度。

Glide.get(getApplicationContext()).setMemoryCategory(MemoryCategory.HIGH);

MemoryCategory有3個(gè)值可供選擇:

  1. MemoryCategory.HIGH(初始緩存大小的1.5倍)
  2. MemoryCategory.NORMAL(初始緩存大小的1倍)
  3. MemoryCategory.LOW(初始緩存大小的0.5倍)

Glide磁盤緩存策略分為四種,默認(rèn)的是RESULT:

  1. ALL:緩存原圖和處理圖
  2. NONE:什么都不緩存
  3. SOURCE:只緩存原圖
  4. RESULT:只緩存處理圖
這么惡心的ModelLoader到底是干嘛用的?

ModelLoader對象是用于加載圖片各種資源的,而我們給load()方法傳入不同類型的參數(shù),這里也會(huì)得到不同的ModelLoader對象。該接口有兩個(gè)目的:將任意復(fù)雜的model轉(zhuǎn)換為可以被decode的數(shù)據(jù)類型,允許model結(jié)合View的尺寸獲取特定大小的資源













最后我們可以看到,loadGeneric()方法是要返回一個(gè)DrawableTypeRequest對象的,因此在loadGeneric()方法的最后又去new了一個(gè)DrawableTypeRequest對象,然后把剛才獲得的ModelLoader對象,還有一大堆雜七雜八的東西都傳了進(jìn)去。

那DrawableTypeRequest是做什么的呢

DrawableTypeRequest
這個(gè)類中的代碼本身就不多,主要看一下構(gòu)造方法和我們會(huì)用到的兩個(gè)比較重要的方法asGif()和asBitmap()。這兩個(gè)方法分別是用于強(qiáng)制指定加載靜態(tài)圖片和動(dòng)態(tài)圖片。將我們的圖片轉(zhuǎn)化為BitmapTypeRequest或者GifTypeRequest兩種圖片格式。

asBitmap()與asGif()

不管我們傳入的是一張普通圖片,還是一張GIF圖片,Glide都會(huì)自動(dòng)進(jìn)行判斷,并且可以正確地把它解析并展示出來。

但是如果我想指定圖片的格式該怎么辦呢?就比如說,我希望加載的這張圖必須是一張靜態(tài)圖片,我不需要Glide自動(dòng)幫我判斷它到底是靜圖還是GIF圖。

好的我們只需要反向操作下,兩種情況:

1. 傳入gif鏈接,使用asBitmap()方法,gif圖則無法正常播放,而是會(huì)停在第一幀的圖片。
2. 傳入靜態(tài)圖片鏈接,使用asGif()方法,會(huì)顯示error()設(shè)置的圖片,沒錯(cuò),如果指定了只能加載動(dòng)態(tài)圖片,而傳入的圖片卻是一張靜圖的話,那么結(jié)果自然就只有加載失敗。

看一下代碼:

  DrawableTypeRequest(Class<ModelType> modelClass, ModelLoader<ModelType, InputStream> streamModelLoader,
            ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader, Context context, Glide glide,
            RequestTracker requestTracker, Lifecycle lifecycle, RequestManager.OptionsApplier optionsApplier) {
        super(context, modelClass,
                buildProvider(glide, streamModelLoader, fileDescriptorModelLoader, GifBitmapWrapper.class,
                        GlideDrawable.class, null),
                glide, requestTracker, lifecycle);
        this.streamModelLoader = streamModelLoader;
        this.fileDescriptorModelLoader = fileDescriptorModelLoader;
        this.optionsApplier = optionsApplier;
    }

  
    public BitmapTypeRequest<ModelType> asBitmap() {
        return optionsApplier.apply(new BitmapTypeRequest<ModelType>(this, streamModelLoader,
                fileDescriptorModelLoader, optionsApplier));
    }

  
    public GifTypeRequest<ModelType> asGif() {
        return optionsApplier.apply(new GifTypeRequest<ModelType>(this, streamModelLoader, optionsApplier));
    }

而從源碼中可以看出,它們分別又創(chuàng)建了一個(gè)BitmapTypeRequest和GifTypeRequest,如果沒有進(jìn)行強(qiáng)制指定的話,那默認(rèn)就是使用DrawableTypeRequest。
好的,那么我們再回到RequestManager的load()方法中。剛才已經(jīng)分析過了,fromString()方法會(huì)返回一個(gè)DrawableTypeRequest對象,接下來會(huì)調(diào)用這個(gè)對象的load()方法,把圖片的URL地址傳進(jìn)去。點(diǎn)進(jìn)去看看load()是在DrawableRequestBuilder類中,我們也可以看到DrawableRequestBuilder是DrawableTypeRequest的父類??创a:

   @Override
    public DrawableRequestBuilder<ModelType> load(ModelType model) {
        super.load(model);
        return this;
    }
   
   
       public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
        this.model = model;
        isModelSet = true;  //注意這個(gè)boolean 在into方法時(shí)我們會(huì)講到
        return this;
    } 

其實(shí)這個(gè)model是什么,說白了就是我們傳進(jìn)來的數(shù)據(jù)對象。就是數(shù)據(jù)來源,可以支持多種類型,圖片,url,字節(jié),文件等。

DrawableTypeRequest的父類是DrawableRequestBuilder,DrawableRequestBuilder中有很多個(gè)方法,這些方法其實(shí)就是Glide絕大多數(shù)的API了。里面有不少我們在上篇文章中已經(jīng)用過了,比如說placeholder()方法、error()方法、diskCacheStrategy()方法、override()方法,當(dāng)然還有最重要的into()方法。其實(shí)通過源碼得知,DrawableRequestBuilder在這些方法中也沒有做什么處理,主要是通過父類的方法來做相應(yīng)處理。

最重要的來了,在DrawableRequestBuilder類中有一個(gè)into()方法,也就是說,最終load()方法返回的其實(shí)就是一個(gè)DrawableTypeRequest對象。

    @Override
    public Target<GlideDrawable> into(ImageView view) {
        return super.into(view);
    }
Glide精妙設(shè)計(jì)之二

其實(shí)通過Glide支持鏈?zhǔn)秸{(diào)用就可以知道,他是使用了建造者模式構(gòu)建的,類似于我們的Dialog,Retrofit。泛型,接口的使用。

相關(guān)文章:

獲取到系統(tǒng)可用的處理器核心數(shù)目

glideModule使用例子

Class的isAssignableFrom方法

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

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

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