美文网首页
Gilde的源码学习

Gilde的源码学习

作者: 他是达达 | 来源:发表于2018-03-01 10:37 被阅读56次

    在大多数的时候使用Gilde只用一行代码:

    Glide.with(this).load(url).into(imageView);
    
    

    对于源码学习,一开始喜欢逐行逐句的去看,后来发现自己非常容易陷入其中,其实只需要我们从常用的功能点入手去分析,明白主题逻辑就够了
    本文是基于4.0.0学习分析的

    第一个方法with()
    这是Gilde类提供的一组静态方法,有多个重载的方法

    public static RequestManager with(Context context) {
        return getRetriever(context).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) {
        return getRetriever(activity).get(activity);
      }
    
      /**
       * Begin a load with Glide that will tied to the give
       * {@link android.support.v4.app.FragmentActivity}'s lifecycle and that uses the given
       * {@link android.support.v4.app.FragmentActivity}'s default options.
       *
       * @param activity The activity to use.
       * @return A RequestManager for the given FragmentActivity that can be used to start a load.
       */
      public static RequestManager with(FragmentActivity activity) {
        return getRetriever(activity).get(activity);
      }
    
      /**
       * Begin a load with Glide that will be tied to the given {@link android.app.Fragment}'s lifecycle
       * and that uses the given {@link android.app.Fragment}'s default options.
       *
       * @param fragment The fragment to use.
       * @return A RequestManager for the given Fragment that can be used to start a load.
       */
      public static RequestManager with(android.app.Fragment fragment) {
        return getRetriever(fragment.getActivity()).get(fragment);
      }
    
      /**
       * Begin a load with Glide that will be tied to the given
       * {@link android.support.v4.app.Fragment}'s lifecycle and that uses the given
       * {@link android.support.v4.app.Fragment}'s default options.
       *
       * @param fragment The fragment to use.
       * @return A RequestManager for the given Fragment that can be used to start a load.
       */
      public static RequestManager with(Fragment fragment) {
        return getRetriever(fragment.getActivity()).get(fragment);
      }
    
    

    可以看到重载的种类非常多,参数可以传入Context,Activity,fragment,fragmentActivity等,方法也是很简单:getRetriever()获取了RequestManagerRetriever对象,再通过get()方法获取了RequestManager ()对象,
    下面分析一下getRetriever()方法干了什么

    private static RequestManagerRetriever getRetriever(@Nullable Context context) {
        // Context could be null for other reasons (ie the user passes in null), but in practice it will
        // only occur due to errors with the Fragment lifecycle.
        Preconditions.checkNotNull(
            context,
            "You cannot start a load on a not yet attached View or a  Fragment where getActivity() "
                + "returns null (which usually occurs when getActivity() is called before the Fragment "
                + "is attached or after the Fragment is destroyed).");
        return Glide.get(context).getRequestManagerRetriever();
      }
    

    首先检查了context的传入是否为空,若为空则抛出异常,接下来获取Glide实例并去获取RequestManagerRetriever实例
    接下来再来分析一下RequestManagerRetriever的get()方法干了啥

     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);                                                                          
     }                                                                                                                 
                                                                                                                       
     public RequestManager get(FragmentActivity activity) {                                                            
       if (Util.isOnBackgroundThread()) {                                                                              
         return get(activity.getApplicationContext());                                                                 
       } else {                                                                                                        
         assertNotDestroyed(activity);                                                                                 
         FragmentManager fm = activity.getSupportFragmentManager();                                                    
         return supportFragmentGet(activity, fm, null /*parentHint*/);                                                 
       }                                                                                                               
     }                                                                                                                 
                                                                                                                       
     public RequestManager get(Fragment fragment) {                                                                    
       Preconditions.checkNotNull(fragment.getActivity(),                                                              
             "You cannot start a load on a fragment before it is attached or after it is destroyed");                  
       if (Util.isOnBackgroundThread()) {                                                                              
         return get(fragment.getActivity().getApplicationContext());                                                   
       } else {                                                                                                        
         FragmentManager fm = fragment.getChildFragmentManager();                                                      
         return supportFragmentGet(fragment.getActivity(), fm, fragment);                                              
       }                                                                                                               
     }                                                                                                                 
                                                                                                                       
     public RequestManager get(Activity activity) {                                                                    
       if (Util.isOnBackgroundThread()) {                                                                              
         return get(activity.getApplicationContext());                                                                 
       } else {                                                                                                        
         assertNotDestroyed(activity);                                                                                 
         android.app.FragmentManager fm = activity.getFragmentManager();                                               
         return fragmentGet(activity, fm, null /*parentHint*/);                                                        
       }                                                                                                               
     }                                                                                                                 
                                                                                                                       
     public RequestManager get(View view) {                                                                            
       if (Util.isOnBackgroundThread()) {                                                                              
         return get(view.getContext().getApplicationContext());                                                        
       }                                                                                                               
    

    可以看到这也是一组重载方法,看上去有好多个参数,实际上只要分清楚是Application和非Application就行,
    我们先看Application的情况,如果在Glide.with()方法中传入的是一个Application对象,那么这里就会调用带有Context参数的get()方法重载,然后getApplicationManager()方法来获取一个RequestManager对象。其实这是最简单的一种情况,因为Application对象的生命周期即应用程序的生命周期,因此Glide并不需要做什么特殊的处理,它自动就是和应用程序的生命周期是同步的,如果应用程序关闭的话,Glide的加载也会同时终止。
    接下来我们看传入非Application参数的情况。不管你在Glide.with()方法中传入的是Activity、FragmentActivity、v4包下的Fragment、还是app包下的Fragment,最终的流程都是一样的,那就是会向当前的Activity当中添加一个隐藏的没有视图的Fragment,通过与fragment的生命周期进行关联进而实现组件的生命周期的实现,
    另外如果我们是在非主线程当中使用的Glide,那么不管你是传入的Activity还是Fragment,都会被强制当成Application来处理
    总结:
    Glide:初始化各个组件的使用入口
    RequestManagerRetriever
    是用来创建并从activity和fragment中检索已存在的RequestManager;
    RequestManagerFragment
    没有视图的fragment,简单的来讲,就是在每一个Activity或者Fragment上又添加了一个Fragment,该Fragment没有View,仅仅用来存储RequestManager并管理Glide请求
    Request Manager
    通过 ActivityFragmentLifecycle 的 addListener 方法注册 LifecycleListener。当 RequestManagerFragment 生命周期方法执行的时候,触发 ActivityFragmentLifecycle 的相应方法,然后会遍历所有注册的 LifecycleListener 并执行相应生命周期方法。这样就完成了生命周期和Glide的request请求的完整关联。

    总体来说,第一个with()方法的源码还是比较好理解的。其实就是为了得到一个RequestManager对象而已,然后Glide会根据我们传入with()方法的参数来确定图片加载的生命周期

    第二个方法load()
    由于with方法返回的是一个RequestManager的对象,接下来的这个方法肯定是在RequestManager类中,只有一个方法,与3.7.0不同没有那么多的重载方法:

     public RequestBuilder<Drawable> load(@Nullable Object model) {
        return asDrawable().load(model);
      }
    
    public RequestBuilder<Drawable> asDrawable() {
        return as(Drawable.class).transition(new DrawableTransitionOptions());
      }
    

    创建一个RequestBuilder,并添加一个过场动画

     public RequestBuilder<TranscodeType> load(@Nullable Object model) {
        return loadGeneric(model);
      }
    
      private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
        this.model = model;
        isModelSet = true;
        return this;
      }
    

    这一步没有什么内容,都是在给requestBuilder参数赋值,并返回一个对象为下一步做准备

    第三个方法into()
    with返回的是RequestManger的对象
    load返回的是RequestBuilder的对象
    into返回的是Target的对象

    public Target<TranscodeType> into(ImageView view) {
        Util.assertMainThread();
        Preconditions.checkNotNull(view);
    
        if (!requestOptions.isTransformationSet()
            && requestOptions.isTransformationAllowed()
            && view.getScaleType() != null) {
          if (requestOptions.isLocked()) {
            requestOptions = requestOptions.clone();
          }
          switch (view.getScaleType()) {
            case CENTER_CROP:
              requestOptions.optionalCenterCrop();
              break;
            case CENTER_INSIDE:
              requestOptions.optionalCenterInside();
              break;
            case FIT_CENTER:
            case FIT_START:
            case FIT_END:
              requestOptions.optionalFitCenter();
              break;
            case FIT_XY:
              requestOptions.optionalCenterInside();
              break;
            case CENTER:
            case MATRIX:
            default:
              // Do nothing.
          }
        }
    
        return into(context.buildImageViewTarget(view, transcodeClass));
    

    首先Util.assertMainThread();判断是否是在主线程,如果不是抛出异常,
    然后Preconditions.checkNotNull(view);判断view是否为空,若为空则抛出异常,通过判断view的scaleType设置图片的加载形式

    public <X> Target<X> buildImageViewTarget(ImageView imageView, Class<X> transcodeClass) {
        return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
      }
    

    这里其实又是调用了ImageViewTargetFactory的buildTarget()方法,我们继续跟进去,代码如下所示:

    public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
        if (Bitmap.class.equals(clazz)) {
          return (Target<Z>) new BitmapImageViewTarget(view);
        } else if (Drawable.class.isAssignableFrom(clazz)) {
          return (Target<Z>) new DrawableImageViewTarget(view);
        } else {
          throw new IllegalArgumentException(
              "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
        }
      }
    

    可以看到,在buildTarget()方法中会根据传入的class参数来构建不同的Target对象,这个class参数其实基本上只有两种情况,如果你在使用Glide加载图片的时候调用了asBitmap()方法,那么这里就会构建出BitmapImageViewTarget对象,否则的话构建的都是GlideDrawableImageViewTarget对象,我们使用的事asDrawable

    public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
        Util.assertMainThread();
        Preconditions.checkNotNull(target);
        if (!isModelSet) {
          throw new IllegalArgumentException("You must call #load() before calling #into()");
        }
    
        Request previous = target.getRequest();
    
        if (previous != null) {
          requestManager.clear(target);
        }
    
        requestOptions.lock();
        Request request = buildRequest(target);
        target.setRequest(request);
        requestManager.track(target, request);
    
        return target;
      }
    

    再次检查是否是在主线程,以及view是否为空,并且通过isModelSet标记判断load
    和into的先后顺序,我们关注核心代码,buildRequest构建了request对象,Request是用来发出加载图片请求的,它是Glide中非常关键的一个组件。我们先来看buildRequest()方法是如何构建Request对象的:

     private Request buildRequest(Target<TranscodeType> target) {
        return buildRequestRecursive(target, null, transitionOptions, requestOptions.getPriority(),
            requestOptions.getOverrideWidth(), requestOptions.getOverrideHeight());
      }
    

    内部又调用了

     private Request buildRequestRecursive(Target<TranscodeType> target,
          @Nullable ThumbnailRequestCoordinator parentCoordinator,
          TransitionOptions<?, ? super TranscodeType> transitionOptions,
          Priority priority, int overrideWidth, int overrideHeight) {
        if (thumbnailBuilder != null) {
          // Recursive case: contains a potentially recursive thumbnail request builder.
          if (isThumbnailBuilt) {
            throw new IllegalStateException("You cannot use a request as both the main request and a "
                + "thumbnail, consider using clone() on the request(s) passed to thumbnail()");
          }
    
          TransitionOptions<?, ? super TranscodeType> thumbTransitionOptions =
              thumbnailBuilder.transitionOptions;
          if (DEFAULT_ANIMATION_OPTIONS.equals(thumbTransitionOptions)) {
            thumbTransitionOptions = transitionOptions;
          }
    
          Priority thumbPriority = thumbnailBuilder.requestOptions.isPrioritySet()
              ? thumbnailBuilder.requestOptions.getPriority() : getThumbnailPriority(priority);
    
          int thumbOverrideWidth = thumbnailBuilder.requestOptions.getOverrideWidth();
          int thumbOverrideHeight = thumbnailBuilder.requestOptions.getOverrideHeight();
          if (Util.isValidDimensions(overrideWidth, overrideHeight)
              && !thumbnailBuilder.requestOptions.isValidOverride()) {
            thumbOverrideWidth = requestOptions.getOverrideWidth();
            thumbOverrideHeight = requestOptions.getOverrideHeight();
          }
    
          ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
          Request fullRequest = obtainRequest(target, requestOptions, coordinator,
              transitionOptions, priority, overrideWidth, overrideHeight);
          isThumbnailBuilt = true;
          // Recursively generate thumbnail requests.
          Request thumbRequest = thumbnailBuilder.buildRequestRecursive(target, coordinator,
              thumbTransitionOptions, thumbPriority, thumbOverrideWidth, thumbOverrideHeight);
          isThumbnailBuilt = false;
          coordinator.setRequests(fullRequest, thumbRequest);
          return coordinator;
        } else if (thumbSizeMultiplier != null) {
          // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
          ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
          Request fullRequest = obtainRequest(target, requestOptions, coordinator, transitionOptions,
              priority, overrideWidth, overrideHeight);
          RequestOptions thumbnailOptions = requestOptions.clone()
              .sizeMultiplier(thumbSizeMultiplier);
    
          Request thumbnailRequest = obtainRequest(target, thumbnailOptions, coordinator,
              transitionOptions, getThumbnailPriority(priority), overrideWidth, overrideHeight);
    
          coordinator.setRequests(fullRequest, thumbnailRequest);
          return coordinator;
        } else {
          // Base case: no thumbnail.
          return obtainRequest(target, requestOptions, parentCoordinator, transitionOptions, priority,
              overrideWidth, overrideHeight);
        }
      }
    

    大篇幅写的是如何处理缩略图的,我们关心这个方法obtainRequest

    private Request obtainRequest(Target<TranscodeType> target,
          RequestOptions requestOptions, RequestCoordinator requestCoordinator,
          TransitionOptions<?, ? super TranscodeType> transitionOptions, Priority priority,
          int overrideWidth, int overrideHeight) {
        requestOptions.lock();
    
        return SingleRequest.obtain(
            context,
            model,
            transcodeClass,
            requestOptions,
            overrideWidth,
            overrideHeight,
            priority,
            target,
            requestListener,
            requestCoordinator,
            context.getEngine(),
            transitionOptions.getTransitionFactory());
      }
    

    在这里调用了SingleRequest的obtain方法,这么多参数,是不是把之前所有用到的api都添加到这个request中去了

     public static <R> SingleRequest<R> obtain(
          GlideContext glideContext,
          Object model,
          Class<R> transcodeClass,
          RequestOptions requestOptions,
          int overrideWidth,
          int overrideHeight,
          Priority priority,
          Target<R> target,
          RequestListener<R> requestListener,
          RequestCoordinator requestCoordinator,
          Engine engine,
          TransitionFactory<? super R> animationFactory) {
        @SuppressWarnings("unchecked") SingleRequest<R> request =
            (SingleRequest<R>) POOL.acquire();
        if (request == null) {
          request = new SingleRequest<>();
        }
        request.init(
            glideContext,
            model,
            transcodeClass,
            requestOptions,
            overrideWidth,
            overrideHeight,
            priority,
            target,
            requestListener,
            requestCoordinator,
            engine,
            animationFactory);
        return request;
      }
    private void init(
          GlideContext glideContext,
          Object model,
          Class<R> transcodeClass,
          RequestOptions requestOptions,
          int overrideWidth,
          int overrideHeight,
          Priority priority,
          Target<R> target,
          RequestListener<R> requestListener,
          RequestCoordinator requestCoordinator,
          Engine engine,
          TransitionFactory<? super R> animationFactory) {
        this.glideContext = glideContext;
        this.model = model;
        this.transcodeClass = transcodeClass;
        this.requestOptions = requestOptions;
        this.overrideWidth = overrideWidth;
        this.overrideHeight = overrideHeight;
        this.priority = priority;
        this.target = target;
        this.requestListener = requestListener;
        this.requestCoordinator = requestCoordinator;
        this.engine = engine;
        this.animationFactory = animationFactory;
        status = Status.PENDING;
      }
    

    new 了一个SingleRequest对象,这个类是Request的实现类,下边的init实际上是对这些参数赋值,至此解决了创建request的问题,那么是怎么执行的呢
    回到最还是into方法

    Request request = buildRequest(target);
    target.setRequest(request);
    requestManager.track(target, request);
    

    看看track方法

    void track(Target<?> target, Request request) {
        targetTracker.track(target);
        requestTracker.runRequest(request);
      }
    

    这里涉及到TargetTracker这个类,是对目标view的生命周期进行跟踪管理的辅助类
    接下来看runRequest这个方法

     public void runRequest(Request request) {
        requests.add(request);
        if (!isPaused) {
          request.begin();
        } else {
          pendingRequests.add(request);
        }
      }
    

    这个RequestTracker类是对request进行队列管理的类,当请求不是暂停时请求开始,先不考虑暂停又开始的情况,由于上面分析了SingleRequest是Request的实现类,那么就去那看

    public void begin() {
        stateVerifier.throwIfRecycled();
        startTime = LogTime.getLogTime();
        if (model == null) {
          if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
            width = overrideWidth;
            height = overrideHeight;
          }
          // Only log at more verbose log levels if the user has set a fallback drawable, because
          // fallback Drawables indicate the user expects null models occasionally.
          int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
          onLoadFailed(new GlideException("Received null model"), logLevel);
          return;
        }
    
        status = Status.WAITING_FOR_SIZE;
        if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
          onSizeReady(overrideWidth, overrideHeight);
        } else {
          target.getSize(this);
        }
    
        if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
            && canNotifyStatusChanged()) {
          target.onLoadStarted(getPlaceholderDrawable());
        }
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          logV("finished run method in " + LogTime.getElapsedMillis(startTime));
        }
      }
    
    

    当model==null时也就是load中传入的为空时我们看这个方法 onLoadFailed

    private void onLoadFailed(GlideException e, int maxLogLevel) {
        stateVerifier.throwIfRecycled();
        int logLevel = glideContext.getLogLevel();
        if (logLevel <= maxLogLevel) {
          Log.w(GLIDE_TAG, "Load failed for " + model + " with size [" + width + "x" + height + "]", e);
          if (logLevel <= Log.INFO) {
            e.logRootCauses(GLIDE_TAG);
          }
        }
    
        loadStatus = null;
        status = Status.FAILED;
        //TODO: what if this is a thumbnail request?
        if (requestListener == null
            || !requestListener.onLoadFailed(e, model, target, isFirstReadyResource())) {
          setErrorPlaceholder();
        }
      }
    

    执行了setErrorPlaceholder方法

    private void setErrorPlaceholder() {
        if (!canNotifyStatusChanged()) {
          return;
        }
    
        Drawable error = null;
        if (model == null) {
          error = getFallbackDrawable();
        }
        // Either the model isn't null, or there was no fallback drawable set.
        if (error == null) {
          error = getErrorDrawable();
        }
        // The model isn't null, no fallback drawable was set or no error drawable was set.
        if (error == null) {
          error = getPlaceholderDrawable();
        }
        target.onLoadFailed(error);
      }
    

    下面看一下getFallbackDrawable方法,是干什么的

     private Drawable getFallbackDrawable() {
        if (fallbackDrawable == null) {
          fallbackDrawable = requestOptions.getFallbackDrawable();
          if (fallbackDrawable == null && requestOptions.getFallbackId() > 0) {
            fallbackDrawable = loadDrawable(requestOptions.getFallbackId());
          }
        }
        return fallbackDrawable;
      }
    
    public final Drawable getFallbackDrawable() {
        return fallbackDrawable;
      }
    
      /**
       * Sets an {@link Drawable} to display if the model provided to
       * {@link com.bumptech.glide.RequestBuilder#load(Object)} is {@code null}.
       *
       * <p> If a fallback is not set, null models will cause the error drawable to be displayed. If the
       * error drawable is not set, the placeholder will be displayed.
       *
       * @see #placeholder(Drawable)
       * @see #placeholder(int)
       *
       * @param drawable The drawable to display as a placeholder.
       * @return This request builder.
       */
      public RequestOptions fallback(Drawable drawable) {
        if (isAutoCloneEnabled) {
          return clone().fallback(drawable);
        }
    
        this.fallbackDrawable = drawable;
        fields |= FALLBACK;
    
        return selfOrThrowIfLocked();
      }
    

    这里写的很明白了,当load的参数为空的时展示一个占位图片,如果这个图片没有设置,就设置一个错误图片,如果错误图片都没设置的话只能设置一个loading图
    下面看看target.onLoadFailed(error);这个方法干了什么:

    /**
     * A lifecycle callback that is called when a load fails.
     *
     * <p> Note - This may be called before {@link #onLoadStarted(android.graphics.drawable.Drawable)
     * } if the model object is null.
     *
     * <p>You must ensure that any current Drawable received in {@link #onResourceReady(Object,
     * Transition)} is no longer displayed before redrawing the container (usually a View) or
     * changing its visibility.
     *
     * @param errorDrawable The error drawable to optionally show, or null.
     */
    void onLoadFailed(@Nullable Drawable errorDrawable);
    

    这是个接口,看看具体实现

    /**
       * Sets the given {@link android.graphics.drawable.Drawable} on the view using {@link
       * android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
       *
       * @param errorDrawable {@inheritDoc}
       */
      @Override
      public void onLoadFailed(@Nullable Drawable errorDrawable) {
        super.onLoadFailed(errorDrawable);
        setResourceInternal(null);
        setDrawable(errorDrawable);
      }
    
     @Override
      public void setDrawable(Drawable drawable) {
        view.setImageDrawable(drawable);
      }
    

    这里可以看到,因为出现了 异常无法正常过的加载图片,具体的为view设置了图片,至于是什么图片则是给的什么接什么,让我们回到begin方法();
    target.onLoadStarted()图片请求开始之前,会先使用这张占位图代替最终的图片显示,这也就是placeholder和error的底层实现原理
    我们看看这个方法里边是什么

     /**
       * Sets the given {@link android.graphics.drawable.Drawable} on the view using {@link
       * android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
       *
       * @param placeholder {@inheritDoc}
       */
      @Override
      public void onLoadStarted(@Nullable Drawable placeholder) {
        super.onLoadStarted(placeholder);
        setResourceInternal(null);
        setDrawable(placeholder);
      }
      /**
       * Sets the given {@link android.graphics.drawable.Drawable} on the view using {@link
       * android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
       *
       * @param drawable {@inheritDoc}
       */
      @Override
      public void setDrawable(Drawable drawable) {
        view.setImageDrawable(drawable);
      }
    

    知道了占位图的实现,那么 图片加载是从哪开始的呢,在begin方法里

    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
          onSizeReady(overrideWidth, overrideHeight);
        } else {
          target.getSize(this);
        }
    

    判断了下你是否指定了一个固定的宽高,不管你是否设置了宽高都会走这个方法

    /**
       * A callback method that should never be invoked directly.
       */
      @Override
      public void onSizeReady(int width, int height) {
        stateVerifier.throwIfRecycled();
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
        }
        if (status != Status.WAITING_FOR_SIZE) {
          return;
        }
        status = Status.RUNNING;
    
        float sizeMultiplier = requestOptions.getSizeMultiplier();
        this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
        this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
    
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
        }
        loadStatus = engine.load(
            glideContext,
            model,
            requestOptions.getSignature(),
            this.width,
            this.height,
            requestOptions.getResourceClass(),
            transcodeClass,
            priority,
            requestOptions.getDiskCacheStrategy(),
            requestOptions.getTransformations(),
            requestOptions.isTransformationRequired(),
            requestOptions.getOptions(),
            requestOptions.isMemoryCacheable(),
            requestOptions.getUseUnlimitedSourceGeneratorsPool(),
            requestOptions.getOnlyRetrieveFromCache(),
            this);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
        }
      }
    

    从这开始涉及engine我们先看这个类是干嘛的

    
    /**
     * Responsible for starting loads and managing active and cached resources.
     * 负责启动加载和管理活动和缓存资源。
     */
    public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {
    
    
    /**
       * Starts a load for the given arguments. Must be called on the main thread.
       *
       * <p> The flow for any request is as follows: <ul> <li>Check the memory cache and provide the
       * cached resource if present</li> <li>Check the current put of actively used resources and return
       * the active resource if present</li> <li>Check the current put of in progress loads and add the
       * cb to the in progress load if present</li> <li>Start a new load</li> </ul> </p>
       *
       * <p> Active resources are those that have been provided to at least one request and have not yet
       * been released. Once all consumers of a resource have released that resource, the resource then
       * goes to cache. If the resource is ever returned to a new consumer from cache, it is re-added to
       * the active resources. If the resource is evicted from the cache, its resources are recycled and
       * re-used if possible and the resource is discarded. There is no strict requirement that
       * consumers release their resources so active resources are held weakly. </p>
       *
       * @param width  The target width in pixels of the desired resource.
       * @param height The target height in pixels of the desired resource.
       * @param cb     The callback that will be called when the load completes.
       */
      public <R> LoadStatus load(
          GlideContext glideContext,
          Object model,
          Key signature,
          int width,
          int height,
          Class<?> resourceClass,
          Class<R> transcodeClass,
          Priority priority,
          DiskCacheStrategy diskCacheStrategy,
          Map<Class<?>, Transformation<?>> transformations,
          boolean isTransformationRequired,
          Options options,
          boolean isMemoryCacheable,
          boolean useUnlimitedSourceExecutorPool,
          boolean onlyRetrieveFromCache,
          ResourceCallback cb) {
        Util.assertMainThread();
        long startTime = LogTime.getLogTime();
    
        //EngineKey 的介绍是An in memory only cache key used to multiplex loads.
        //内存中只缓存用于复用的键。所以这个key记录了一次加载的各种信息
        EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
            resourceClass, transcodeClass, options);
      
        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
        if (cached != null) {
          cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
          if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from cache", startTime, key);
          }
          return null;
        }
    
        EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
        if (active != null) {
          cb.onResourceReady(active, DataSource.MEMORY_CACHE);
          if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from active resources", startTime, key);
          }
          return null;
        }
        //这个应该也属于内存缓存,这里避免了EngineJob的重复创建,EngineJob代表从硬盘缓存或者网络上加载图片并进行decode的整个过程。
        EngineJob<?> current = jobs.get(key);
        if (current != null) {
          current.addCallback(cb);
          if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Added to existing load", startTime, key);
          }
          return new LoadStatus(cb, current);
        }
    
        EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable,
            useUnlimitedSourceExecutorPool);
        DecodeJob<R> decodeJob = decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            onlyRetrieveFromCache,
            options,
            engineJob);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        engineJob.start(decodeJob);
    
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          logWithTimeAndKey("Started new load", startTime, key);
        }
        return new LoadStatus(cb, engineJob);
      }
    

    这个方法有点长,我们慢慢看
    从这就开始涉及Glide的缓存机制
    EngineKey 这个类是用产生一个用于进行缓存的Key,决定key的因素特别多,这个类通过重写了equals()和hashCode()方法,保证只有传入EngineKey的所有参数都相同的情况下才认为是同一个EngineKey对象

    接下来看loadFromCache方法

    private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
          return null;
        }
    
        EngineResource<?> cached = getEngineResourceFromCache(key);
        if (cached != null) {
          cached.acquire();
          activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
        }
        return cached;
      }  
    
     private EngineResource<?> getEngineResourceFromCache(Key key) {
        Resource<?> cached = cache.remove(key);
    
        final EngineResource<?> result;
        if (cached == null) {
          result = null;
        } else if (cached instanceof EngineResource) {
          // Save an object allocation if we've cached an EngineResource (the typical case).
          result = (EngineResource<?>) cached;
        } else {
          result = new EngineResource<>(cached, true /*isMemoryCacheable*/);
        }
        return result;
      }
    
    
     private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
          return null;
        }
    
        EngineResource<?> active = null;
        WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
        if (activeRef != null) {
          active = activeRef.get();
          if (active != null) {
            active.acquire();
          } else {
            activeResources.remove(key);
          }
        }
    
        return active;
      }
    

    为什么要判断isMemoryCacheable 呢是因为Glide自动帮我们开启了缓存如果想关闭这功能只需要在skipMemoryCache()方法传入true,那么在这里就是false,表示内存被禁用,接着调用了getEngineResourceFromCache()方法来获取缓存。在这个方法中,会使用缓存Key来从cache当中取值,而这里的cache对象就是在构建Glide对象时创建的LruResourceCache,那么说明这里其实使用的就是LruCache算法了,当我们从LruResourceCache中获取到缓存图片之后会将它从缓存中移除,然后在将这个缓存图片存储到activeResources当中。activeResources就是一个弱引用的HashMap,用来缓存正在使用中的图片,我们可以看到,loadFromActiveResources()方法就是从activeResources这个HashMap当中取值的。使用activeResources来缓存正在使用中的图片,可以保护这些图片不会被LruCache算法回收掉,如果有的话就直接在这获取,没有的话就开启线程去加载图片,接下来又进行了内存缓存的判断这里避免了EngineJob的重复创建,EngineJob代表从硬盘缓存或者网络上加载图片并进行decode的整个过程。

    那么怎么开启的线程又是如何加载的呢:

    EngineJob的职责是调度DecodeJob,添加,移除资源回调,并notify回调。DecodeJob负责从缓存资源或者原始数据中读取资源,Glide中的脏累活基本都是这个DecodeJob干的

    先从engineJob.start去看

     public void start(DecodeJob<R> decodeJob) {
        this.decodeJob = decodeJob;
        GlideExecutor executor = decodeJob.willDecodeFromCache()
            ? diskCacheExecutor
            : getActiveSourceExecutor();
        executor.execute(decodeJob);
      }
    

    利用线程池执行decodeJob,那么decodeJob又有什么猫腻
    decodeJob中实现了Runnable看一下run方法

    @Override
      public void run() {
        // This should be much more fine grained, but since Java's thread pool implementation silently
        // swallows all otherwise fatal exceptions, this will at least make it obvious to developers
        // that something is failing.
        TraceCompat.beginSection("DecodeJob#run");
        try {
          if (isCancelled) {
            notifyFailed();
            return;
          }
          runWrapped();
        } catch (RuntimeException e) {
          if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "DecodeJob threw unexpectedly"
                + ", isCancelled: " + isCancelled
                + ", stage: " + stage, e);
          }
          // When we're encoding we've already notified our callback and it isn't safe to do so again.
          if (stage != Stage.ENCODE) {
            notifyFailed();
          }
          if (!isCancelled) {
            throw e;
          }
        } finally {
          if (currentFetcher != null) {
            currentFetcher.cleanup();
          }
          TraceCompat.endSection();
        }
      }
    

    貌似所有的逻辑处理都在runWrapped这个方法中

     private void runWrapped() {
         switch (runReason) {
          case INITIALIZE:
            stage = getNextStage(Stage.INITIALIZE);
            currentGenerator = getNextGenerator();
            runGenerators();
            break;
          case SWITCH_TO_SOURCE_SERVICE:
            runGenerators();
            break;
          case DECODE_DATA:
            decodeFromRetrievedData();
            break;
          default:
            throw new IllegalStateException("Unrecognized run reason: " + runReason);
        }
      }
    
    private DataFetcherGenerator getNextGenerator() {
        switch (stage) {
          case RESOURCE_CACHE:
            return new ResourceCacheGenerator(decodeHelper, this);
          case DATA_CACHE:
            return new DataCacheGenerator(decodeHelper, this);
          case SOURCE:
            return new SourceGenerator(decodeHelper, this);
          case FINISHED:
            return null;
          default:
            throw new IllegalStateException("Unrecognized stage: " + stage);
        }
      }
    
    private void runGenerators() {
        currentThread = Thread.currentThread();
        startFetchTime = LogTime.getLogTime();
        boolean isStarted = false;
        while (!isCancelled && currentGenerator != null
            && !(isStarted = currentGenerator.startNext())) {
          stage = getNextStage(stage);
          currentGenerator = getNextGenerator();
    
          if (stage == Stage.SOURCE) {
            reschedule();
            return;
          }
        }
        // We've run out of stages and generators, give up.
        if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
          notifyFailed();
        }
    
        // Otherwise a generator started a new load and we expect to be called back in
        // onDataFetcherReady.
      }
    
    

    因为runReason的默认值是INITIALIZE,首先调用getNextStage方法获取到stage值,然后执行getNextGenerator根据stage不同的值获取相应的generator
    一共有几种generators
    ResourceCacheGenerator:从处理过的缓存加载数据
    DataCacheGenerator:从原始缓存加载数据
    SourceGenerator:从数据源请求数据

    最后执行runGeneratores,currentGenerator.startNext()
    点进去看到时候发现有3个
    分别是:
    DataCacheGenerator
    ResourceCacheGenerator
    SourceGenerator
    这里主要看一下SourceGenerator的方法

     @Override
      public boolean startNext() {
        if (dataToCache != null) {
          Object data = dataToCache;
          dataToCache = null;
          cacheData(data);
        }
    
        if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
          return true;
        }
        sourceCacheGenerator = null;
    
        loadData = null;
        boolean started = false;
        while (!started && hasNextModelLoader()) {
          loadData = helper.getLoadData().get(loadDataListIndex++);
          if (loadData != null
              && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
              || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
            started = true;
            loadData.fetcher.loadData(helper.getPriority(), this);
          }
        }
        return started;
      }
    

    接下来我们看loadData方法

    /**
     * A DataFetcher that retrieves an {@link java.io.InputStream} for a Url.
     */
    public class HttpUrlFetcher implements DataFetcher<InputStream> {
      @Override
      public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
        long startTime = LogTime.getLogTime();
        final InputStream result;
        try {
          result = loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/,
              glideUrl.getHeaders());
        } catch (IOException e) {
          if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Failed to load data for url", e);
          }
          callback.onLoadFailed(e);
          return;
        }
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
          Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime)
              + " ms and loaded " + result);
        }
        callback.onDataReady(result);
    }
    
     private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
          Map<String, String> headers) throws IOException {
        if (redirects >= MAXIMUM_REDIRECTS) {
          throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
        } else {
          // Comparing the URLs using .equals performs additional network I/O and is generally broken.
          // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
          try {
            if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
              throw new HttpException("In re-direct loop");
    
            }
          } catch (URISyntaxException e) {
            // Do nothing, this is best effort.
          }
        }
    
        urlConnection = connectionFactory.build(url);
        for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
          urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
        }
        urlConnection.setConnectTimeout(timeout);
        urlConnection.setReadTimeout(timeout);
        urlConnection.setUseCaches(false);
        urlConnection.setDoInput(true);
    
        // Stop the urlConnection instance of HttpUrlConnection from following redirects so that
        // redirects will be handled by recursive calls to this method, loadDataWithRedirects.
        urlConnection.setInstanceFollowRedirects(false);
    
        // Connect explicitly to avoid errors in decoders if connection fails.
        urlConnection.connect();
        if (isCancelled) {
          return null;
        }
        final int statusCode = urlConnection.getResponseCode();
        if (statusCode / 100 == 2) {
          return getStreamForSuccessfulRequest(urlConnection);
        } else if (statusCode / 100 == 3) {
          String redirectUrlString = urlConnection.getHeaderField("Location");
          if (TextUtils.isEmpty(redirectUrlString)) {
            throw new HttpException("Received empty or null redirect url");
          }
          URL redirectUrl = new URL(url, redirectUrlString);
          return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
        } else if (statusCode == -1) {
          throw new HttpException(statusCode);
        } else {
          throw new HttpException(urlConnection.getResponseMessage(), statusCode);
        }
      }
    )
    private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
          throws IOException {
        if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
          int contentLength = urlConnection.getContentLength();
          stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
        } else {
          if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());
          }
          stream = urlConnection.getInputStream();
        }
        return stream;
      }
    

    到这我们是找到了网络通讯的代码了,通过网络下载获取到inputStream,然后把流给callback.onDataReady(result);
    我们再看代码:SourceGenerator

     @Override
      public void onDataReady(Object data) {
        DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
        if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
          dataToCache = data;
          // We might be being called back on someone else's thread. Before doing anything, we should
          // reschedule to get back onto Glide's thread.
          cb.reschedule();
        } else {
          cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
              loadData.fetcher.getDataSource(), originalKey);
        }
      }
    
    

    最后回调到decodejob的方法

    @Override
      public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
          DataSource dataSource, Key attemptedKey) {
        this.currentSourceKey = sourceKey;
        this.currentData = data;
        this.currentFetcher = fetcher;
        this.currentDataSource = dataSource;
        this.currentAttemptingKey = attemptedKey;
        if (Thread.currentThread() != currentThread) {
          runReason = RunReason.DECODE_DATA;
          callback.reschedule(this);
        } else {
          TraceCompat.beginSection("DecodeJob.decodeFromRetrievedData");
          try {
            decodeFromRetrievedData();
          } finally {
            TraceCompat.endSection();
          }
        }
      }
    
    private void decodeFromRetrievedData() {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          logWithTimeAndKey("Retrieved data", startFetchTime,
              "data: " + currentData
              + ", cache key: " + currentSourceKey
              + ", fetcher: " + currentFetcher);
        }
        Resource<R> resource = null;
        try {
          //把数据流编码成resource类型
          resource = decodeFromData(currentFetcher, currentData, currentDataSource);
        } catch (GlideException e) {
          e.setLoggingDetails(currentAttemptingKey, currentDataSource);
          exceptions.add(e);
        }
        if (resource != null) {
          //将resource返回
          notifyEncodeAndRelease(resource, currentDataSource);
        } else {
          runGenerators();
        }
      }
    
    private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
        if (resource instanceof Initializable) {
          ((Initializable) resource).initialize();
        }
        Resource<R> result = resource;
        LockedResource<R> lockedResource = null;
        if (deferredEncodeManager.hasResourceToEncode()) {
          lockedResource = LockedResource.obtain(resource);
          result = lockedResource;
        }
    
        notifyComplete(result, dataSource);
    
        stage = Stage.ENCODE;
        try {
          if (deferredEncodeManager.hasResourceToEncode()) {
            deferredEncodeManager.encode(diskCacheProvider, options);
          }
        } finally {
          if (lockedResource != null) {
            lockedResource.unlock();
          }
          onEncodeComplete();
        }
      }
    
    
    private void notifyComplete(Resource<R> resource, DataSource dataSource) {
        setNotifiedOrThrow();
        callback.onResourceReady(resource, dataSource);
      }
    

    最终把数据回调到onResourceReady

     @Override
      public void onResourceReady(Resource<R> resource, DataSource dataSource) {
        this.resource = resource;
        this.dataSource = dataSource;
        MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
      }
    

    之后发送了一条消息

    private static class MainThreadCallback implements Handler.Callback {
    
        @Synthetic
        MainThreadCallback() { }
    
        @Override
        public boolean handleMessage(Message message) {
          EngineJob<?> job = (EngineJob<?>) message.obj;
          switch (message.what) {
            case MSG_COMPLETE:
              job.handleResultOnMainThread();
              break;
            case MSG_EXCEPTION:
              job.handleExceptionOnMainThread();
              break;
            case MSG_CANCELLED:
              job.handleCancelledOnMainThread();
              break;
            default:
              throw new IllegalStateException("Unrecognized message: " + message.what);
          }
          return true;
        }
      }
    
    @Synthetic
      void handleResultOnMainThread() {
        stateVerifier.throwIfRecycled();
        if (isCancelled) {
          resource.recycle();
          release(false /*isRemovedFromQueue*/);
          return;
        } else if (cbs.isEmpty()) {
          throw new IllegalStateException("Received a resource without any callbacks to notify");
        } else if (hasResource) {
          throw new IllegalStateException("Already have resource");
        }
        engineResource = engineResourceFactory.build(resource, isCacheable);
        hasResource = true;
    
        // Hold on to resource for duration of request so we don't recycle it in the middle of
        // notifying if it synchronously released by one of the callbacks.
        engineResource.acquire();
        listener.onEngineJobComplete(key, engineResource);
    
        for (ResourceCallback cb : cbs) {
          if (!isInIgnoredCallbacks(cb)) {
            engineResource.acquire();
            cb.onResourceReady(engineResource, dataSource);
          }
        }
        // Our request is complete, so we can release the resource.
        engineResource.release();
    
        release(false /*isRemovedFromQueue*/);
      }
    

    之后回调到SingleRequest

     @SuppressWarnings("unchecked")
      @Override
      public void onResourceReady(Resource<?> resource, DataSource dataSource) {
        stateVerifier.throwIfRecycled();
        loadStatus = null;
        if (resource == null) {
          GlideException exception = new GlideException("Expected to receive a Resource<R> with an "
              + "object of " + transcodeClass + " inside, but instead got null.");
          onLoadFailed(exception);
          return;
        }
    
        Object received = resource.get();
        if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
          releaseResource(resource);
          GlideException exception = new GlideException("Expected to receive an object of "
              + transcodeClass + " but instead" + " got "
              + (received != null ? received.getClass() : "") + "{" + received + "} inside" + " "
              + "Resource{" + resource + "}."
              + (received != null ? "" : " " + "To indicate failure return a null Resource "
              + "object, rather than a Resource object containing null data."));
          onLoadFailed(exception);
          return;
        }
    
        if (!canSetResource()) {
          releaseResource(resource);
          // We can't put the status to complete before asking canSetResource().
          status = Status.COMPLETE;
          return;
        }
    
        onResourceReady((Resource<R>) resource, (R) received, dataSource);
      }
    
     private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
        // We must call isFirstReadyResource before setting status.
        boolean isFirstResource = isFirstReadyResource();
        status = Status.COMPLETE;
        this.resource = resource;
    
        if (glideContext.getLogLevel() <= Log.DEBUG) {
          Log.d(GLIDE_TAG, "Finished loading " + result.getClass().getSimpleName() + " from "
              + dataSource + " for " + model + " with size [" + width + "x" + height + "] in "
              + LogTime.getElapsedMillis(startTime) + " ms");
        }
    
        if (requestListener == null
            || !requestListener.onResourceReady(result, model, target, dataSource, isFirstResource)) {
          Transition<? super R> animation =
              animationFactory.build(dataSource, isFirstResource);
          target.onResourceReady(result, animation);
        }
    
        notifyLoadSuccess();
      }
    

    最后我们看 target.onResourceReady(result, animation);这个方法
    点进去一看

    public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z>
        implements Transition.ViewAdapter {
        @Override
      public void onResourceReady(Z resource, @Nullable Transition<? super Z> transition) {
        if (transition == null || !transition.transition(resource, this)) {
          setResourceInternal(resource);
        } else {
          maybeUpdateAnimatable(resource);
        }
      }
    }
     private void setResourceInternal(@Nullable Z resource) {
        maybeUpdateAnimatable(resource);
        setResource(resource);
      }
    protected abstract void setResource(@Nullable Z resource);
    

    至此就能把图片显示出来了,至于如何编码解码的并没有深入探讨分析,整体的过了一遍流程
    总体来说跟3.7.0流程上还是有很大的区别的,至于没弄的细节有时间再弄。

    相关文章

      网友评论

          本文标题:Gilde的源码学习

          本文链接:https://www.haomeiwen.com/subject/xwsmxftx.html