美文网首页
探究Glide的运行原理 (1)

探究Glide的运行原理 (1)

作者: 零星瓢虫 | 来源:发表于2020-04-11 23:43 被阅读0次

关于 Glide

Glide是一个快速高效的Android图片加载库,注重于平滑的滚动。Glide提供了易用的API,高性能、可扩展的图片解码管道(decode pipeline),以及自动的资源池技术。

Glide 支持拉取,解码和展示视频快照,图片,和GIF动画。Glide的Api是如此的灵活,开发者甚至可以插入和替换成自己喜爱的任何网络栈。默认情况下,Glide使用的是一个定制化的基于HttpUrlConnection的栈,但同时也提供了与Google Volley和Square OkHttp快速集成的工具库。

Glide的性能

Glide 充分考虑了Android图片加载性能的两个关键方面:

图片解码速度
解码图片带来的资源压力
为了让用户拥有良好的App使用体验,图片不仅要快速加载,而且还不能因为过多的主线程I/O或频繁的垃圾回收导致页面的闪烁和抖动现象。

Glide使用了多个步骤来确保在Android上加载图片尽可能的快速和平滑:

自动、智能地下采样(downsampling)和缓存(caching),以最小化存储开销和解码次数;
积极的资源重用,例如字节数组和Bitmap,以最小化昂贵的垃圾回收和堆碎片影响;
深度的生命周期集成,以确保仅优先处理活跃的Fragment和Activity的请求,并有利于应用在必要时释放资源以避免在后台时被杀掉。

Glide的使用

Glide 使用简明的流式语法API,这是一个非常棒的设计,因为它允许你在大部分情况下一行代码搞定需求:

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

Glide的使用example详解

1、库的引用

dependencies {
  implementation 'com.github.bumptech.glide:glide:4.11.0'
  annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
}

2、混淆文件

-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
  **[] $VALUES;
  public *;
}

# for DexGuard only
-keepresourcexmlelements manifest/application/meta-data@value=GlideModule

3、简单使用

// For a simple view:
@Override public void onCreate(Bundle savedInstanceState) {
  ...
  ImageView imageView = (ImageView) findViewById(R.id.my_image_view);

  Glide.with(this).load("http://goo.gl/gEgYUd").into(imageView);
}

// For a simple image list:
@Override public View getView(int position, View recycled, ViewGroup container) {
  final ImageView myImageView;
  if (recycled == null) {
    myImageView = (ImageView) inflater.inflate(R.layout.my_image_view, container, false);
  } else {
    myImageView = (ImageView) recycled;
  }

  String url = myUrls.get(position);

  Glide
    .with(myFragment)
    .load(url)
    .centerCrop()
    .placeholder(R.drawable.loading_spinner)
    .into(myImageView);

  return myImageView;
}
 ImageView imageView = new ImageView(this);
        String url = "http://www.$$$.com/100.png";
        Glide.with(getApplicationContext())//指定上下文环境
                .load(url)//指定图片的url
                .placeholder(R.mipmap.ic_launcher)//指定未加载成功前显示的图片
                .error(R.mipmap.ic_launcher)//指定加载失败时显示的图片
                .override(300,300)//指定图片的尺寸
                .fitCenter()//指定图片的缩放类型  缩放居中显示 完全显示全部图像,可能不被填满
                .centerCrop()//同上   填充整个view,会裁剪图片,可能不完全显示全部图像
                .skipMemoryCache(true)//是否跳过内存缓存
                .diskCacheStrategy(DiskCacheStrategy.NONE)//跳过磁盘缓存
                .diskCacheStrategy(DiskCacheStrategy.RESOURCE)//缓存原来全分辨率的图像
                .diskCacheStrategy(DiskCacheStrategy.DATA)//缓存最终的图像
                .diskCacheStrategy(DiskCacheStrategy.ALL)//缓存所有版本图像
                .priority(Priority.HIGH)//指定加载优先级
                .into(imageView);

Glide的加载图片原理分析

首先大概了解图片加载整体的流程:


零星瓢虫_001.png

Glide.with(getApplicationContext())

进入第一段代码,Glide类中查看with方法:

  @NonNull
  public static RequestManager with(@NonNull Context context) {
    return getRetriever(context).get(context);
  }

 
  @NonNull
  public static RequestManager with(@NonNull Activity activity) {
    return getRetriever(activity).get(activity);
  }


  @NonNull
  public static RequestManager with(@NonNull FragmentActivity activity) {
    return getRetriever(activity).get(activity);
  }


  @NonNull
  public static RequestManager with(@NonNull View view) {
    return getRetriever(view.getContext()).get(view);
  }

可以看到Glide的with方法提供了好几种参数的重载方法方便使用。同时都用到了 getRetriever()方法,去看看getRetriever()方法都做了什么:

  @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.
    //检测是否context为空
    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();
  }

这里看到getRetriever()方法主要去获取RequestManagerRetriever类?获取之前我们看到了对context是否为空进行了检测。

继续查看RequestManagerRetriever是做什么的呢?进入RequestManagerRetriever 类中:

public class RequestManagerRetriever implements Handler.Callback {
  @VisibleForTesting static final String FRAGMENT_TAG = "com.bumptech.glide.manager";
  private static final String TAG = "RMRetriever";

  private static final int ID_REMOVE_FRAGMENT_MANAGER = 1;
  private static final int ID_REMOVE_SUPPORT_FRAGMENT_MANAGER = 2;

  // Hacks based on the implementation of FragmentManagerImpl in the non-support libraries that
  // allow us to iterate over and retrieve all active Fragments in a FragmentManager.
  private static final String FRAGMENT_INDEX_KEY = "key";

  /** The top application level RequestManager. */
  private volatile RequestManager applicationManager;

  /** Pending adds for RequestManagerFragments. */
  @SuppressWarnings("deprecation")
  @VisibleForTesting
  final Map<android.app.FragmentManager, RequestManagerFragment> pendingRequestManagerFragments =
      new HashMap<>();

  /** Pending adds for SupportRequestManagerFragments. */
  @VisibleForTesting
  final Map<FragmentManager, SupportRequestManagerFragment> pendingSupportRequestManagerFragments =
      new HashMap<>();

  /** Main thread handler to handle cleaning up pending fragment maps. */
  private final Handler handler;

  private final RequestManagerFactory factory;

  // Objects used to find Fragments and Activities containing views.
  private final ArrayMap<View, Fragment> tempViewToSupportFragment = new ArrayMap<>();
  private final ArrayMap<View, android.app.Fragment> tempViewToFragment = new ArrayMap<>();
  private final Bundle tempBundle = new Bundle();

  public RequestManagerRetriever(@Nullable RequestManagerFactory factory) {
    this.factory = factory != null ? factory : DEFAULT_FACTORY;
    handler = new Handler(Looper.getMainLooper(), this /* Callback */);
  }
}

// 获取application的RequestManager
@NonNull
  private RequestManager getApplicationManager(@NonNull 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.

          // TODO(b/27524013): Factor out this Glide.get() call.
          Glide glide = Glide.get(context.getApplicationContext());
          applicationManager =
              factory.build(
                  glide,
                  new ApplicationLifecycle(),
                  new EmptyRequestManagerTreeNode(),
                  context.getApplicationContext());
        }
      }
    }

    return applicationManager;
  }

// 获取RequestManager
  @NonNull
  public RequestManager get(@NonNull 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
          // Only unwrap a ContextWrapper if the baseContext has a non-null application context.
          // Context#createPackageContext may return a Context without an Application instance,
          // in which case a ContextWrapper may be used to attach one.
          && ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
        return get(((ContextWrapper) context).getBaseContext());
      }
    }

    return getApplicationManager(context);
  }

通过上述代码发现RequestManagerRetriever主要是一个工厂类用来生产RequestManager。

这里我们就获取到了RequestManagerRetriever,沿着上面的代码继续查看;getRetriever(activity).get(activity);我们以activity为参数来分析,进入RequestManagerRetriever 类查看get方法;

// 获取RequestManager
  @NonNull
  public RequestManager get(@NonNull 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
          // Only unwrap a ContextWrapper if the baseContext has a non-null application context.
          // Context#createPackageContext may return a Context without an Application instance,
          // in which case a ContextWrapper may be used to attach one.
          && ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
        return get(((ContextWrapper) context).getBaseContext());
      }
    }

    return getApplicationManager(context);
  }

可以看到会再次对context判断空,如果是Application环境,会调用getApplicationManager(context);否则调用get((Activity) context);先看一下getApplicationManager(context)方法:

  @NonNull
  private RequestManager getApplicationManager(@NonNull 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.

          // TODO(b/27524013): Factor out this Glide.get() call.
          Glide glide = Glide.get(context.getApplicationContext());
          applicationManager =
              factory.build(
                  glide,
                  new ApplicationLifecycle(),
                  new EmptyRequestManagerTreeNode(),
                  context.getApplicationContext());
        }
      }
    }

    return applicationManager;
  }

可以看到这里通过factory创建了applicationManager类。applicationManager其实就是RequestManager类。同时通过创建 new ApplicationLifecycle()传参对此次加载图像任务生命周期进行监听。因为传进来的context是application ,那么生命周期就是整个app运行的生命周期了;

class ApplicationLifecycle implements Lifecycle {
  @Override
  public void addListener(@NonNull LifecycleListener listener) {
    listener.onStart();
  }

  @Override
  public void removeListener(@NonNull LifecycleListener listener) {
    // Do nothing.
  }
}

接下来继续查看get((Activity) context)的调用:

  @SuppressWarnings("deprecation")
  @NonNull
  public RequestManager get(@NonNull Activity activity) {
    if (Util.isOnBackgroundThread()) { // 是否在后台
      return get(activity.getApplicationContext());// 调用application去获取requestManager
    } else {
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();
      return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }

这里通过拿到FragementManager会继续去调用fragmentGet()方法:

  @Deprecated
  @NonNull
  private RequestManager fragmentGet(
      @NonNull Context context,
      @NonNull android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    //创建Fragment
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
  //通过Fragment获取RequestManager;
    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;
  }

拿到了FragementManager之后,这里立马去创建了RequestManagerFragment,他其实就是一个Fragement:

@Deprecated
public class RequestManagerFragment extends Fragment {
  private static final String TAG = "RMFragment";
  private final ActivityFragmentLifecycle lifecycle;
  private final RequestManagerTreeNode requestManagerTreeNode =
      new FragmentRequestManagerTreeNode();

  @SuppressWarnings("deprecation")
  private final Set<RequestManagerFragment> childRequestManagerFragments = new HashSet<>();

  @Nullable private RequestManager 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();
    }
  }

  @Override
  public void removeListener(@NonNull LifecycleListener listener) {
    lifecycleListeners.remove(listener);
  }

  void onStart() {
    isStarted = true;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onStart();
    }
  }

  void onStop() {
    isStarted = false;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onStop();
    }
  }

  void onDestroy() {
    isDestroyed = true;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onDestroy();
    }
  }
}

看到这里基本上可以知道,创建这个Fragment其实就是为了对这个加载图片请求进行生命周期绑定的,因为Glide不能单单通过传进来的activity就判断生命周期走到了哪里;但是如果在activity上面加载一个fragment进行检测不是就能检测到生命周期运动,同时对Glide的加载做响应改变了嘛。

最后 Glide同时把Fragment和RequestManager进行的绑定:current.setRequestManager(requestManager);

这里可以看到Glide.with()方法主要是为了了生成一个RequestManager实现Glide与相关Context生命周期的绑定工作。

接下来 Glide.with().load(url);进行url的加载,查看RequestManager类的load方法:

  @NonNull
  @CheckResult
  @Override
  public RequestBuilder<Drawable> load(@Nullable String string) {
    return asDrawable().load(string);
  }

  @NonNull
  @CheckResult
  public RequestBuilder<Drawable> asDrawable() {
    return as(Drawable.class);
  }

  @NonNull
  @CheckResult
  public <ResourceType> RequestBuilder<ResourceType> as(
      @NonNull Class<ResourceType> resourceClass) {
    return new RequestBuilder<>(glide, this, resourceClass, context);
  }

最终会调用到RequestBuilder类中的创建方法,进入到RequestBuilder中:

  protected RequestBuilder(
      @NonNull Glide glide,
      RequestManager requestManager,
      Class<TranscodeType> transcodeClass,
      Context context) {
    this.glide = glide;
    this.requestManager = requestManager;
    this.transcodeClass = transcodeClass;
    this.context = context;
    this.transitionOptions = requestManager.getDefaultTransitionOptions(transcodeClass);
    this.glideContext = glide.getGlideContext();

    initRequestListeners(requestManager.getDefaultRequestListeners());// 加载的监听
    apply(requestManager.getDefaultRequestOptions());
  }

通过RequestBuilder构建者模式初始化一些常量参数以及加载的监听;requestManager即为上一步Glide().with()方法生成的requestManager,transcodeClass则为asDrawable()传进来的Drawable.class;这边我们先主要看下requestManager.getDefaultTransitionOptions(transcodeClass)方法生成的transitionOptions是做什么的?进入到RequestManager类的getDefaultTransitionOptions方法:

  @NonNull
  <T> TransitionOptions<?, T> getDefaultTransitionOptions(Class<T> transcodeClass) {
    return glide.getGlideContext().getDefaultTransitionOptions(transcodeClass);
  }

glide.getGlideContext()获取到GildeContext类,进入GildeContext类中:

 public GlideContext(
      @NonNull Context context,
      @NonNull ArrayPool arrayPool,
      @NonNull Registry registry,
      @NonNull ImageViewTargetFactory imageViewTargetFactory,
      @NonNull RequestOptionsFactory defaultRequestOptionsFactory,
      @NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions,
      @NonNull List<RequestListener<Object>> defaultRequestListeners,
      @NonNull Engine engine,
      boolean isLoggingRequestOriginsEnabled,
      int logLevel) {
    super(context.getApplicationContext());
    this.arrayPool = arrayPool;
    this.registry = registry;
    this.imageViewTargetFactory = imageViewTargetFactory;
    this.defaultRequestOptionsFactory = defaultRequestOptionsFactory;
    this.defaultRequestListeners = defaultRequestListeners;
    this.defaultTransitionOptions = defaultTransitionOptions;
    this.engine = engine;
    this.isLoggingRequestOriginsEnabled = isLoggingRequestOriginsEnabled;
    this.logLevel = logLevel;
  }

 static final TransitionOptions<?, ?> DEFAULT_TRANSITION_OPTIONS =
      new GenericTransitionOptions<>();

private final Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions;

  @NonNull
  public <T> TransitionOptions<?, T> getDefaultTransitionOptions(@NonNull Class<T> transcodeClass) {
    TransitionOptions<?, ?> result = defaultTransitionOptions.get(transcodeClass);
    if (result == null) {
      for (Entry<Class<?>, TransitionOptions<?, ?>> value : defaultTransitionOptions.entrySet()) {
        if (value.getKey().isAssignableFrom(transcodeClass)) {
          result = value.getValue();
        }
      }
    }
    if (result == null) {
      result = DEFAULT_TRANSITION_OPTIONS;
    }
    return (TransitionOptions<?, T>) result;
  }

可以看到getDefaultTransitionOptions方法主要想从维护GlideContext维护的defaultTransitionOptions map中获取获取TransitionOptions实列,否则则取默认的TransitionOptions实例;那么TransitionOptions是干嘛的呢?

/**
 * A base class for setting a transition to use on a resource when a load completes.
 * 
 * @param <CHILD> The implementation of this class to return to chain methods.
 * @param <TranscodeType> The type of resource that will be animated.
 */

public abstract class TransitionOptions<
        CHILD extends TransitionOptions<CHILD, TranscodeType>, TranscodeType>
    implements Cloneable {

A base class for setting a transition to use on a resource when a load completes.
意思大概就是对资源加载转换的一个工具,个人认为类似于解码器东西;

同时RequestBuilder的构建方法会调用到apply(requestManager.getDefaultRequestOptions())方法;

  @NonNull
  @CheckResult
  @Override
  public RequestBuilder<TranscodeType> apply(@NonNull BaseRequestOptions<?> requestOptions) {
    Preconditions.checkNotNull(requestOptions);
    return super.apply(requestOptions);
  }

 @NonNull
  @CheckResult
  public T apply(@NonNull BaseRequestOptions<?> o) {
    if (isAutoCloneEnabled) {
      return clone().apply(o);
    }
    BaseRequestOptions<?> other = o;

    if (isSet(other.fields, SIZE_MULTIPLIER)) {
      sizeMultiplier = other.sizeMultiplier;
    }
    if (isSet(other.fields, USE_UNLIMITED_SOURCE_GENERATORS_POOL)) {
      useUnlimitedSourceGeneratorsPool = other.useUnlimitedSourceGeneratorsPool;
    }
    if (isSet(other.fields, USE_ANIMATION_POOL)) {
      useAnimationPool = other.useAnimationPool;
    }
    if (isSet(other.fields, DISK_CACHE_STRATEGY)) {
      diskCacheStrategy = other.diskCacheStrategy;
    }
    if (isSet(other.fields, PRIORITY)) {
      priority = other.priority;
    }
    if (isSet(other.fields, ERROR_PLACEHOLDER)) {
      errorPlaceholder = other.errorPlaceholder;
      errorId = 0;
      fields &= ~ERROR_ID;
    }
    if (isSet(other.fields, ERROR_ID)) {
      errorId = other.errorId;
      errorPlaceholder = null;
      fields &= ~ERROR_PLACEHOLDER;
    }
    if (isSet(other.fields, PLACEHOLDER)) {
      placeholderDrawable = other.placeholderDrawable;
      placeholderId = 0;
      fields &= ~PLACEHOLDER_ID;
    }
    if (isSet(other.fields, PLACEHOLDER_ID)) {
      placeholderId = other.placeholderId;
      placeholderDrawable = null;
      fields &= ~PLACEHOLDER;
    }
    if (isSet(other.fields, IS_CACHEABLE)) {
      isCacheable = other.isCacheable;
    }
    if (isSet(other.fields, OVERRIDE)) {
      overrideWidth = other.overrideWidth;
      overrideHeight = other.overrideHeight;
    }
    if (isSet(other.fields, SIGNATURE)) {
      signature = other.signature;
    }
    if (isSet(other.fields, RESOURCE_CLASS)) {
      resourceClass = other.resourceClass;
    }
    if (isSet(other.fields, FALLBACK)) {
      fallbackDrawable = other.fallbackDrawable;
      fallbackId = 0;
      fields &= ~FALLBACK_ID;
    }
    if (isSet(other.fields, FALLBACK_ID)) {
      fallbackId = other.fallbackId;
      fallbackDrawable = null;
      fields &= ~FALLBACK;
    }
    if (isSet(other.fields, THEME)) {
      theme = other.theme;
    }
    if (isSet(other.fields, TRANSFORMATION_ALLOWED)) {
      isTransformationAllowed = other.isTransformationAllowed;
    }
    if (isSet(other.fields, TRANSFORMATION_REQUIRED)) {
      isTransformationRequired = other.isTransformationRequired;
    }
    if (isSet(other.fields, TRANSFORMATION)) {
      transformations.putAll(other.transformations);
      isScaleOnlyOrNoTransform = other.isScaleOnlyOrNoTransform;
    }
    if (isSet(other.fields, ONLY_RETRIEVE_FROM_CACHE)) {
      onlyRetrieveFromCache = other.onlyRetrieveFromCache;
    }

    // Applying options with dontTransform() is expected to clear our transformations.
    if (!isTransformationAllowed) {
      transformations.clear();
      fields &= ~TRANSFORMATION;
      isTransformationRequired = false;
      fields &= ~TRANSFORMATION_REQUIRED;
      isScaleOnlyOrNoTransform = true;
    }

    fields |= other.fields;
    options.putAll(other.options);

    return selfOrThrowIfLocked();
  }

这里看到对用户设置的相关内容进行了赋值操作,例如placeholderId 、overrideWidth 、overrideHeight等。

接下来的RequestBuilder的load()方法同样传参并进行了赋值设置;

  @NonNull
  @Override
  @CheckResult
  public RequestBuilder<TranscodeType> load(@Nullable String string) {
    return loadGeneric(string);
  }

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

到这里可以看到Glide在调用到load(url)的时候主要对相关用户定义的参数进行了设置;

本篇文章先讲到这里,我们对Glide.with().load(url)进行了梳理,总结下主要做了哪些工作:
1 获取RequestManager创建RequestFragment从而对Glide的生命周期进行管理;
2 初始化相关的数据信息以及参数;

下面同时通过UML图简单查看相关使用过的类:

零星瓢虫_001.png

接下来我们将继续在探究Glide的运行原理(2)
)中接着对Glide.with().load(url).into(view)的into方法进行梳理

相关文章

网友评论

      本文标题:探究Glide的运行原理 (1)

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