美文网首页
Glide最新源码解析(一)-RequestManager创建

Glide最新源码解析(一)-RequestManager创建

作者: 烧伤的火柴 | 来源:发表于2019-10-08 11:25 被阅读0次

前言

关于Glide的优缺点和使用,这里不在阐述,具体请看GitHub上官方介绍。这里主要结合别人的文章和自己看源码的一些见解,分享一下自己的理解。Glide庞大的图片库做的封装性极好支持的格式也很多,开发者的代码设计严格遵守了软件设计的六大原则,一开始看的时候感觉结构好麻烦,好多接口,好多回调,后来细想了一下这些都是软件设计的六大原则约束,带来的好处就是低耦合,易维护,等等很多优点,我会在分析的时候讲一下里边设计的设计模式。

整个Glide的流程图

如下是一张大概的流程图


Glide时序图.png

发现的问题

自定义AppGlideModule中applyOptions方法设置默认图片格式PREFER_RGB_565无效,查看源码会发现在glide在解码图片的时候,会根据解析图片头得到的图片格式设置解码格式:Png格式图片设置为PREFER_ARGB_8888,jpg格式的图片设置为PREFER_RGB_565。
-验证方法

@GlideModule
public class CustomGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
          //设置格式为565  
          builder.setDefaultRequestOptions(RequestOptions.formatOf(DecodeFormat.PREFER_RGB_565));
    }
}

在MainActivity中通过如下方式打印一下图片的格式

 Glide.with(this).asBitmap().addListener(new RequestListener<Bitmap>() {
            @Override
            public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
                return false;
            }

            @Override
            public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
                Bitmap.Config config = resource.getConfig();
                Log.i("jawe", "onResourceReady: config="+config);
                return false;
            }
        }).load("http://d.lanrentuku.com/down/png/1904/international_food/fried_rice.png").into(imageView);

这里打印出来的config就是ARGB_8888
-查看源码
具体在Downsample.java的calculateConfig方法内

private void calculateConfig(
....){
....
...
   boolean hasAlpha = false;
    try {
      hasAlpha = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool).hasAlpha();
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(
            TAG,
            "Cannot determine whether the image has alpha or not from header"
                + ", format "
                + format,
            e);
      }
    }
    //这里根据是否有透明度设置解码格式
    optionsWithScaling.inPreferredConfig =
        hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
    if (optionsWithScaling.inPreferredConfig == Config.RGB_565) {
      optionsWithScaling.inDither = true;
    }
}

主流程分析

一般的使用如下:
Glide.with(this).load("http://d.lanrentuku.com/down/png/1904/international_food/fried_rice.png") .apply(new RequestOptions().error(R.drawable.ic_launcher_background).placeholder (R.mipmap.ic_launcher).override(500, 500)).into(iv);
越是看起来很简单的代码,背后的封装性很复杂,下面我们就一步步分析一下

Glide.with做的事情

 @NonNull
  public static RequestManager with(@NonNull FragmentActivity activity) {
    //1.getRetriever(activity)得到RequestManager检索器
    //2.通过检索器得到一个RequestManager
    return getRetriever(activity).get(activity);
  }
  @NonNull
  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.
    return Glide.get(context).getRequestManagerRetriever();
  }

其中Glide.get(context)得到的是Glide单例对象,然后getRequestManagerRetriever();得到检索器

private static volatile Glide glide;
  public static Glide get(@NonNull Context context) {
    if (glide == null) {
      GeneratedAppGlideModule annotationGeneratedModule =
          getAnnotationGeneratedGlideModules(context.getApplicationContext());
      synchronized (Glide.class) {
        if (glide == null) {
          checkAndInitializeGlide(context, annotationGeneratedModule);
        }
      }
    }

    return glide;
  }

这里采用的双重校验加锁(DCL)的方式实现单例模式,其中glide使用volatile修饰加入内存屏障,禁止指令重排序,使得其他线程的修改能被直接读取最新值,即高并发时候线程的可见性。
Glide在V4中推荐使用注解的方式配置Glide选项,Glide在创建Glide对象的时候会根据编译期生成的RequestManagerRetriever.RequestManagerFactory设置Glide然后调用Glide glide = builder.build(applicationContext);创建Glide对象。

private static void initializeGlide(
      @NonNull Context context, @Nullable GeneratedAppGlideModule generatedAppGlideModule) {
    initializeGlide(context, new GlideBuilder(), generatedAppGlideModule);
  }
private static void initializeGlide(
      @NonNull Context context,
      @NonNull GlideBuilder builder,
      @Nullable GeneratedAppGlideModule annotationGeneratedModule) {
...
    RequestManagerRetriever.RequestManagerFactory factory =
        annotationGeneratedModule != null
            ? annotationGeneratedModule.getRequestManagerFactory()
            : null;
    builder.setRequestManagerFactory(factory);
...
    Glide glide = builder.build(applicationContext);
...
    Glide.glide = glide;

这里边其中GlideBuilder内部实现了很多默认的Glide配置,我们一起看看

/** A builder class for setting default structural classes for Glide to use. */
@SuppressWarnings("PMD.ImmutableField")
public final class GlideBuilder {
  private final Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions = new ArrayMap<>();
  private Engine engine;//加载引擎
  private BitmapPool bitmapPool;//bitmap复用池
  private ArrayPool arrayPool;//array复用池
  private MemoryCache memoryCache;//内存缓存
  private GlideExecutor sourceExecutor;//source线程池
  private GlideExecutor diskCacheExecutor;//磁盘缓存线程池
  private DiskCache.Factory diskCacheFactory;//磁盘缓存工厂
  private MemorySizeCalculator memorySizeCalculator;//缓存大小计算器
  private ConnectivityMonitorFactory connectivityMonitorFactory;
  private int logLevel = Log.INFO;
  private RequestOptionsFactory defaultRequestOptionsFactory =
      new RequestOptionsFactory() {
        @NonNull
        @Override
        public RequestOptions build() {
          return new RequestOptions();
        }
      };
  @Nullable private RequestManagerFactory requestManagerFactory;//requestManager工厂
  private GlideExecutor animationExecutor;
...
}

通过注释可以很清楚的知道这个类的作用就是用来构建Glide,这里也可以看出使用了构建者模式(builder),将复杂对象的表示和它的实现分离。
我们看一下build方法

 Glide build(@NonNull Context context) {
    //GlideExecutor创建线程池,这里我把GlideExecutor理解是一个工具类
    if (sourceExecutor == null) {
      sourceExecutor = GlideExecutor.newSourceExecutor();
    }
    ...
    ...
    //根据设备内存大小计算最优的内存,复用池,磁盘缓存大小等
    if (memorySizeCalculator == null) {
      memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
    }

   ......
    if (engine == null) {
      engine =
          new Engine(
              memoryCache,
              diskCacheFactory,
              diskCacheExecutor,
              sourceExecutor,
              GlideExecutor.newUnlimitedSourceExecutor(),
              animationExecutor,
              isActiveResourceRetentionAllowed);
    }

    if (defaultRequestListeners == null) {
      defaultRequestListeners = Collections.emptyList();
    } else {
      defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners);
    }

    RequestManagerRetriever requestManagerRetriever =
        new RequestManagerRetriever(requestManagerFactory);

    return new Glide(
        context,
        engine,
        memoryCache,
        bitmapPool,
        arrayPool,
        requestManagerRetriever,
        connectivityMonitorFactory,
        logLevel,
        defaultRequestOptionsFactory,
        defaultTransitionOptions,
        defaultRequestListeners,
        isLoggingRequestOriginsEnabled,
        isImageDecoderEnabledForBitmaps,
        hardwareBitmapFdLimit,
        minHardwareDimension);
  }

这里体现了设计模式种的单一职责模式,builder里边主要做的事情:
1.根据不同的任务特性结合设备的cpu等特征设置不同的线程池
2.根据设备的内存和磁盘大小设置最优的内存和磁盘大小,以及构建复用池
3.构建加载引擎(Engine)
4.构建Glide对象
我们接下来看一下Glide的构造

Glide(
      @NonNull Context context,
      @NonNull Engine engine,
      @NonNull MemoryCache memoryCache,
      @NonNull BitmapPool bitmapPool,
      @NonNull ArrayPool arrayPool,
      @NonNull RequestManagerRetriever requestManagerRetriever,
      @NonNull ConnectivityMonitorFactory connectivityMonitorFactory,
      int logLevel,
      @NonNull RequestOptionsFactory defaultRequestOptionsFactory,
      @NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions,
      @NonNull List<RequestListener<Object>> defaultRequestListeners,
      boolean isLoggingRequestOriginsEnabled,
      boolean isImageDecoderEnabledForBitmaps,
      int hardwareBitmapFdLimit,
      int minHardwareDimension) {
...
 registry
        /** 编码器 磁盘缓存使用的*/
        .append(ByteBuffer.class, new ByteBufferEncoder())
        .append(InputStream.class, new StreamEncoder(arrayPool))
        /* Bitmaps 注册解码器 使用byteBufferBitmapDecoder从ByteBuffer 解码成bitmap */
        .append(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder)
        .append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder)
        .append(
            Registry.BUCKET_BITMAP,
            ParcelFileDescriptor.class,
            Bitmap.class,
            parcelFileDescriptorVideoDecoder)
        .append(
            Registry.BUCKET_BITMAP,
            AssetFileDescriptor.class,
            Bitmap.class,
            VideoDecoder.asset(bitmapPool))
        .append(Bitmap.class, Bitmap.class, UnitModelLoader.Factory.<Bitmap>getInstance())
        .append(Registry.BUCKET_BITMAP, Bitmap.class, Bitmap.class, new UnitBitmapDecoder())
        /**注册 bitmap的编码器 用于磁盘缓存*/
        .append(Bitmap.class, bitmapEncoder)
        /* BitmapDrawables */
        .append(
            Registry.BUCKET_BITMAP_DRAWABLE,
            ByteBuffer.class,
            BitmapDrawable.class,
            new BitmapDrawableDecoder<>(resources, byteBufferBitmapDecoder))
        .append(
            Registry.BUCKET_BITMAP_DRAWABLE,
            InputStream.class,
            BitmapDrawable.class,
            new BitmapDrawableDecoder<>(resources, streamBitmapDecoder))
        .append(
            Registry.BUCKET_BITMAP_DRAWABLE,
            ParcelFileDescriptor.class,
            BitmapDrawable.class,
            new BitmapDrawableDecoder<>(resources, parcelFileDescriptorVideoDecoder))
        .append(BitmapDrawable.class, new BitmapDrawableEncoder(bitmapPool, bitmapEncoder))
        /* GIFs */
        .append(
            Registry.BUCKET_GIF,
            InputStream.class,
            GifDrawable.class,
            new StreamGifDecoder(imageHeaderParsers, byteBufferGifDecoder, arrayPool))
        .append(Registry.BUCKET_GIF, ByteBuffer.class, GifDrawable.class, byteBufferGifDecoder)
        .append(GifDrawable.class, new GifDrawableEncoder())
        /* GIF Frames */
        // Compilation with Gradle requires the type to be specified for UnitModelLoader here.
        .append(
            GifDecoder.class, GifDecoder.class, UnitModelLoader.Factory.<GifDecoder>getInstance())
        .append(
            Registry.BUCKET_BITMAP,
            GifDecoder.class,
            Bitmap.class,
            new GifFrameResourceDecoder(bitmapPool))
        /* Drawables */
        .append(Uri.class, Drawable.class, resourceDrawableDecoder)
        .append(
            Uri.class, Bitmap.class, new ResourceBitmapDecoder(resourceDrawableDecoder, bitmapPool))
        /* Files 文件解码器 */
        .register(new ByteBufferRewinder.Factory())
        .append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory())
        .append(File.class, InputStream.class, new FileLoader.StreamFactory())
        .append(File.class, File.class, new FileDecoder())
        .append(File.class, ParcelFileDescriptor.class, new FileLoader.FileDescriptorFactory())
        // Compilation with Gradle requires the type to be specified for UnitModelLoader here.
        .append(File.class, File.class, UnitModelLoader.Factory.<File>getInstance())
        /* Models 网络 */
        .register(new InputStreamRewinder.Factory(arrayPool))
        .append(int.class, InputStream.class, resourceLoaderStreamFactory)
        .append(int.class, ParcelFileDescriptor.class, resourceLoaderFileDescriptorFactory)
        .append(Integer.class, InputStream.class, resourceLoaderStreamFactory)
        .append(Integer.class, ParcelFileDescriptor.class, resourceLoaderFileDescriptorFactory)
        .append(Integer.class, Uri.class, resourceLoaderUriFactory)
        .append(int.class, AssetFileDescriptor.class, resourceLoaderAssetFileDescriptorFactory)
        .append(Integer.class, AssetFileDescriptor.class, resourceLoaderAssetFileDescriptorFactory)
        .append(int.class, Uri.class, resourceLoaderUriFactory)
        .append(String.class, InputStream.class, new DataUrlLoader.StreamFactory<String>())
        .append(Uri.class, InputStream.class, new DataUrlLoader.StreamFactory<Uri>())
        .append(String.class, InputStream.class, new StringLoader.StreamFactory())
        .append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory())
        .append(
            String.class, AssetFileDescriptor.class, new StringLoader.AssetFileDescriptorFactory())
        .append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
        .append(Uri.class, InputStream.class, new AssetUriLoader.StreamFactory(context.getAssets()))
        .append(
            Uri.class,
            ParcelFileDescriptor.class,
            new AssetUriLoader.FileDescriptorFactory(context.getAssets()))
        .append(Uri.class, InputStream.class, new MediaStoreImageThumbLoader.Factory(context))
        .append(Uri.class, InputStream.class, new MediaStoreVideoThumbLoader.Factory(context))
        .append(Uri.class, InputStream.class, new UriLoader.StreamFactory(contentResolver))
        .append(
            Uri.class,
            ParcelFileDescriptor.class,
            new UriLoader.FileDescriptorFactory(contentResolver))
        .append(
            Uri.class,
            AssetFileDescriptor.class,
            new UriLoader.AssetFileDescriptorFactory(contentResolver))
        .append(Uri.class, InputStream.class, new UrlUriLoader.StreamFactory())
        .append(URL.class, InputStream.class, new UrlLoader.StreamFactory())
        .append(Uri.class, File.class, new MediaStoreFileLoader.Factory(context))
        .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
        .append(byte[].class, ByteBuffer.class, new ByteArrayLoader.ByteBufferFactory())
        .append(byte[].class, InputStream.class, new ByteArrayLoader.StreamFactory())
        .append(Uri.class, Uri.class, UnitModelLoader.Factory.<Uri>getInstance())
        .append(Drawable.class, Drawable.class, UnitModelLoader.Factory.<Drawable>getInstance())
        .append(Drawable.class, Drawable.class, new UnitDrawableDecoder())
        /* Transcoders 转码器 */
        .register(Bitmap.class, BitmapDrawable.class, new BitmapDrawableTranscoder(resources))
        .register(Bitmap.class, byte[].class, bitmapBytesTranscoder)
        .register(
            Drawable.class,
            byte[].class,
            new DrawableBytesTranscoder(
                bitmapPool, bitmapBytesTranscoder, gifDrawableBytesTranscoder))
        .register(GifDrawable.class, byte[].class, gifDrawableBytesTranscoder);
...
 glideContext =
        new GlideContext(
            context,
            arrayPool,
            registry,
            imageViewTargetFactory,
            defaultRequestOptionsFactory,
            defaultTransitionOptions,
            defaultRequestListeners,
            engine,
            isLoggingRequestOriginsEnabled,
            logLevel);

构造方法内部核心就是把所需要的编码器,解码器,加载模型,转码器都注册到注册表中,以供后边加载图片 缓存图片的时候使用。这里解释一下Glide中的组件以及作用:
1.ModelLoader, 将自定义的 Model(Url,File, Uri,任意的 POJO )加载成 Data(InputStreams, FileDescriptors)。
2.ResourceDecoder, 将 Data 类型(InputStreams, FileDescriptors)进行解码成新的 Resources(Drawables, Bitmaps)
3.Encoder, 用于向 Glide 的磁盘缓存写 Data (InputStreams, FileDesciptors)。
4.ResourceTranscoder,用于在不同的资源类型之间做转换,例如,从 BitmapResource 转换为 DrawableResource 。
5.ResourceEncoder,用于向 Glide 的磁盘缓存写 Resources(BitmapResource, DrawableResource)。

RequestManagerRetriever#get(activity)

构建RequestManger对象

 @NonNull
  public RequestManager get(@NonNull FragmentActivity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else {
      assertNotDestroyed(activity);
      FragmentManager fm = activity.getSupportFragmentManager();
      return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }

这里是在主线程中执行的,所以执行else条件

  @NonNull
    private RequestManager supportFragmentGet(
      @NonNull Context context,
      @NonNull FragmentManager fm,
      @Nullable Fragment parentHint,
      boolean isParentVisible) {
    SupportRequestManagerFragment current =
        getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      // TODO(b/27524013): Factor out this Glide.get() call.
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }

这里可以看出RequestManager 绑定一个SupportRequestManagerFragment

  @NonNull
  private SupportRequestManagerFragment getSupportRequestManagerFragment(
      @NonNull final FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) {
    SupportRequestManagerFragment current =
        (SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
    if (current == null) {
      current = pendingSupportRequestManagerFragments.get(fm);
      if (current == null) {
        current = new SupportRequestManagerFragment();
        current.setParentFragmentHint(parentHint);
        if (isParentVisible) {
          current.getGlideLifecycle().onStart();
        }
        pendingSupportRequestManagerFragments.put(fm, current);
        fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
        handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
      }
    }
    return current;
  }

当前的页面绑定一个隐藏无界面的Fragment,来感知组件的生命周期,这样做的目的是避免内存泄漏,如果activity页面中使用Glide加载一张图片,activity被用户关掉了,但是Glide单例中的ViewTarget还在引用ImageView对象,而ImageView对象又引用activity对象,那么这个activity对象就不会被回收掉,也就是内存泄漏了。这里可以看出一个fragmentManager只有一个fragment。

注意这里有一个疑问,为什么要用handler发送消息的形式移除fm,这里看起来没有线程切换,完全可以直接移除。关于这个问题我问了我的同事,同事的解释是正确的,因为commitAllowingStateLoss 这个事务是异步执行的,为了保证一个fragmentManager只有一个绑定的SupportRequestManagerFragment,所以添加一个集合控制逻辑,当第一次提交事务 异步执行的时候,如果用户接着第二次get的时候,findFragmentByTag是null(前边的fragment还没有挂载到activity上)这时候从集合中查到了,就返回去。但是如果不从集合中移出去就会内存泄漏,为了保证是在挂载成功后,从集合中移掉fm,所以使用handler发送消息,利用handler的消息机制,消息队列排队取出消息处理的,所以移除的动作是在commit后执行的。

RequestManager

  • 介绍:RequestManager是Glide中管理和启动Request的一个类,可以感知组件生命周期,不同的周期采用不同的管理策略。
  • 分析:前边的分析看到RequestManager是通过工厂构建产生的,注解生成默认的工厂是new方式创建对象
 RequestManager(
      Glide glide,
      Lifecycle lifecycle,
      RequestManagerTreeNode treeNode,
      RequestTracker requestTracker,
      ConnectivityMonitorFactory factory,
      Context context) {
    this.glide = glide;
    this.lifecycle = lifecycle;
    this.treeNode = treeNode;
    this.requestTracker = requestTracker;
    this.context = context;

    connectivityMonitor =
        factory.build(
            context.getApplicationContext(),
            new RequestManagerConnectivityListener(requestTracker));

    // If we're the application level request manager, we may be created on a background thread.
    // In that case we cannot risk synchronously pausing or resuming requests, so we hack around the
    // issue by delaying adding ourselves as a lifecycle listener by posting to the main thread.
    // This should be entirely safe.
    if (Util.isOnBackgroundThread()) {
      mainHandler.post(addSelfToLifecycle);
    } else {
      lifecycle.addListener(this);//1
    }
    lifecycle.addListener(connectivityMonitor);

    defaultRequestListeners =
        new CopyOnWriteArrayList<>(glide.getGlideContext().getDefaultRequestListeners());
    setRequestOptions(glide.getGlideContext().getDefaultRequestOptions());

    glide.registerRequestManager(this);
  }

这里最重要的就是注释1处,把requestManger当前对象添加到lifecycle的监听接口中,这个lifecycle就是前边在构建SupportRequestManagerFragment对象的时候构建出来的ActivityFragmentLifecycle,而ActivityFragmentLifecycle 就是一个观察者,观察Fragment的生命周期,然后通知所有的订阅者(LifecycleListener),订阅者其中就有RequestManager,看一下ActivityFragmentLifecycle

class ActivityFragmentLifecycle implements Lifecycle {
  private final Set<LifecycleListener> lifecycleListeners =
      Collections.newSetFromMap(new WeakHashMap<LifecycleListener, Boolean>());
  private boolean isStarted;
  private boolean isDestroyed;
...
  @Override
  public void addListener(@NonNull LifecycleListener listener) {
    lifecycleListeners.add(listener);

    if (isDestroyed) {
      listener.onDestroy();
    } else if (isStarted) {
      listener.onStart();
    } else {
      listener.onStop();
    }
  }
...
}

其中addListener内部就像一个粘性事件一样,添加订阅的时候,能够订阅到订阅前的信息。

内存管理

Android是一个内存很敏感的应用,当系统内存不足的时候,就会杀死一些进程回收内存。为了让应用存活更久,Glide做了内存优化,其中Glide 实现了ComponentCallbacks2接口并且在onTrimMemory(int level)中根据不同的级别做出相应的内存管理。

public void trimMemory(int level) {
    // Engine asserts this anyway when removing resources, fail faster and consistently
    Util.assertMainThread();
    // Request managers need to be trimmed before the caches and pools, in order for the latter to
    // have the most benefit.
    for (RequestManager manager : managers) {
      manager.onTrimMemory(level);
    }
    // memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687.
    memoryCache.trimMemory(level);
    bitmapPool.trimMemory(level);
    arrayPool.trimMemory(level);
  }

总结

Glide.with(activity)采用的外观设计模式,创建Request Manger对象 能够根据组件生命周期的不同阶段,来暂停,启动,停止请求,请求的处理流程requestTrack中管理的。Glide.with的流程图如下:


Glide.with.png

相关文章

网友评论

      本文标题:Glide最新源码解析(一)-RequestManager创建

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