Android Glide 攻略

141 阅读9分钟

攻略大全

1. 粘贴攻略

1.1 简单对比

image.png

1.2 简单使用

// 流式接口的调用方式
Glide
        // 绑定Context
        .with(Context context)
        // 绑定Activity
        .with(Activity activity)
        // 绑定FragmentActivity
        .with(FragmentActivity activity)
        // 绑定Fragment
        .with(Fragment fragment)
        // 清理磁盘缓存 需要在子线程中执行
        .clearDiskCache()
        // 清理内存缓存 可以在UI主线程中进行
        .clearMemory()
        // 要加载的资源
        .load("source")
        // 设置加载尺寸
        .override(800, 800)
        // 设置加载中图片
        .placeholder(R.mipmap.ic_launcher)
        // 设置加载失败图片
        .error(R.mipmap.ic_launcher)
        // 设置加载动画
        .animate(R.anim.item_alpha_in)
        // 设置图片裁剪方式
        .centerCrop()
        // 设置缩略图支持:先加载缩略图 然后在加载全图
        // 传了一个 0.1f 作为参数,Glide 将会显示原始图像的10%的大小。
        // 如果原始图像有 1000x1000 像素,那么缩略图将会有 100x100 像素。
        .thumbnail(0.1f)
        //显示gif静态图片
        .asBitmap()
        //显示gif动态图片
        .asGif()
        // 缓存参数说明
        // DiskCacheStrategy.NONE:不缓存任何图片,即禁用磁盘缓存
        // DiskCacheStrategy.ALL :缓存原始图片 & 转换后的图片(默认)
        // DiskCacheStrategy.SOURCE:只缓存原始图片(原来的全分辨率的图像,即不缓存转换后的图片)
        // DiskCacheStrategy.RESULT:只缓存转换后的图片(即最终的图像:降低分辨率后或者转换后 ,不缓存原始图片
        .diskCacheStrategy(DiskCacheStrategy.ALL)
        // 设置跳过内存缓存
        //这意味着 Glide 将不会把这张图片放到内存缓存中去
        //这里需要明白的是,这只是会影响内存缓存!Glide 将会仍然利用磁盘缓存来避免重复的网络请求。
        .skipMemoryCache(true)
        // 设置下载优先级
        .priority(Priority.NORMAL)
        // 图片最终要展示的地方
        .into(imageView);
        .into(new SimpleTarget<GlideDrawable>() {
    @Override
    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
        imageView.setImageDrawable(resource);
    }
});

2. 造火箭攻略

3. 拧螺丝攻略

3.1 with()

with()是为得到一个RequestManager对象,从而将Glide的加载图片周期与Activity 和Fragment的生命周期进行绑定。

3.1.1 获取RequestManagerRetriever

源码上,都会调用getRetriever()获得RequestManagerRetriever实例对象,进而调用RequestManagerRetriever实例对象的get()方法去构建RequestManager。

而getRetriever()的调用,涉及Gilde单例的初始化。

@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 Fragment fragment) {
    return getRetriever(fragment.getActivity()).get(fragment);
}

@NonNull
public static RequestManager with(@NonNull androidx.fragment.app.Fragment fragment) {
    return getRetriever(fragment.getActivity()).get(fragment);
}

@NonNull
public static RequestManager with(@NonNull View view) {
    return getRetriever(view.getContext()).get(view);
}
@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.
    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();

}
/**
 * Get the singleton.
 *
 * @return the singleton
 */
@NonNull
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;
}

3.1.2 构建RequestManager

通过context的不同类型构建出不同的RequestManager对象,即不同生命周期的Gilde。

@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) {
      return get(((ContextWrapper) context).getBaseContext());
    }
  }

  return getApplicationManager(context);
}

@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, null /*parentHint*/);
  }
}

@NonNull
public RequestManager get(@NonNull 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);
  }
}

@NonNull
public RequestManager get(@NonNull Activity activity) {
  if (Util.isOnBackgroundThread()) {
    return get(activity.getApplicationContext());
  } else {
    assertNotDestroyed(activity);
    android.app.FragmentManager fm = activity.getFragmentManager();
    return fragmentGet(activity, fm, null /*parentHint*/);
  }
}

@NonNull
public RequestManager get(@NonNull View view) {
  if (Util.isOnBackgroundThread()) {
    return get(view.getContext().getApplicationContext());
  }

  Preconditions.checkNotNull(view);
  Preconditions.checkNotNull(view.getContext(),
      "Unable to obtain a request manager for a view without a Context");
  Activity activity = findActivity(view.getContext());
  // The view might be somewhere else, like a service.
  if (activity == null) {
    return get(view.getContext().getApplicationContext());
  }

  // Support Fragments.
  // Although the user might have non-support Fragments attached to FragmentActivity, searching
  // for non-support Fragments is so expensive pre O and that should be rare enough that we
  // prefer to just fall back to the Activity directly.
  if (activity instanceof FragmentActivity) {
    Fragment fragment = findSupportFragment(view, (FragmentActivity) activity);
    return fragment != null ? get(fragment) : get(activity);
  }

  // Standard Fragments.
  android.app.Fragment fragment = findFragment(view, activity);
  if (fragment == null) {
    return get(activity);
  }
  return get(fragment);
}
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;
}

3.1.3 揭开绑定生命周期的神秘面纱

3.1.3.1 RequestManger:监听生命周期的回调进而管理图片的加载

image.png

image.png

image.png

RequestManger实现了LifecycleListener,在构造函数中将自己加入了lifecycle的监听器列表中,进而根据所回调的生命周期方法去管理图片的加载。

3.1.3.2 RequestManagerFragment:连接生命周期方法并扩散传递

image.png

image.png

image.png

RequestManagerFragment在收到生命周期的回调时,会遍历lifecycle中所添加的生命周期监听器,从而扩散生命周期的回调。

3.1.3.2 RequestManagerRetriever:将RequestManagerFragment与RequestManger绑定

image.png

image.png

image.png

RequestManagerRetriever会创建出一个无UI界面的RequestManagerFragment依附到Activity中,借此感知到当前界面的生命周期,进而回调Lifecycle中的监听器,达到生命周期回调的传递。

3.1.3.4 RequestTracker:图片请求管理的执行者

image.png

image.png

/**
 * Starts any not yet completed or failed requests.
 */
public void resumeRequests() {
  // 将界面状态isPaused置为false
  isPaused = false;
  // 遍历图片请求集合
  for (Request request : Util.getSnapshot(requests)) {
    // 如果这个图片请求没有完成&&没有被关闭&&没有正在执行
    // 则启动异步请求
    if (!request.isComplete() && !request.isCancelled() && !request.isRunning()) {
      request.begin();
    }
  }
  // 清空掉等待请求的集合
  pendingRequests.clear();
}

RequestManager监听到生命周期的回调后,会回调到RequestTracker中,进行对应的管理。

3.2 load()

load方法得到了一个RequestBuilder图片请求构建器,即创建图片请求。

image.png

3.2.1 跟进as()方法

构建不同类型的RequestBuilder。

image.png

// We only override the method to change the return type, not the functionality.
@SuppressLint("CheckResult")
@SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
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());
}

3.2.2 跟进load()方法

简单地进行了一些赋值配置操作,而后将RequestBuilder实例对象返回。

image.png image.png

image.png

3.3 into()

3.3.1 阶段一

3.3.1.1 查看into()

/**
 * Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into
 * the view, and frees any resources Glide may have previously loaded into the view so they may be
 * reused.
 *
 * @see RequestManager#clear(Target)
 *
 * @param view The view to cancel previous loads for and load the new resource into.
 * @return The
 * {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link ImageView}.
 */
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
  Util.assertMainThread();
  Preconditions.checkNotNull(view);

  RequestOptions requestOptions = this.requestOptions;
  if (!requestOptions.isTransformationSet()
      && requestOptions.isTransformationAllowed()
      && view.getScaleType() != null) {
    // Clone in this method so that if we use this RequestBuilder to load into a View and then
    // into a different target, we don't retain the transformation applied based on the previous
    // View's scale type.
    // 根据我们设置的裁剪方式构建不同的requestOptions
    switch (view.getScaleType()) {
      case CENTER_CROP:
        requestOptions = requestOptions.clone().optionalCenterCrop();
        break;
      case CENTER_INSIDE:
        requestOptions = requestOptions.clone().optionalCenterInside();
        break;
      case FIT_CENTER:
      case FIT_START:
      case FIT_END:
        requestOptions = requestOptions.clone().optionalFitCenter();
        break;
      case FIT_XY:
        requestOptions = requestOptions.clone().optionalCenterInside();
        break;
      case CENTER:
      case MATRIX:
      default:
        // Do nothing.
    }
  }

  return into(
      glideContext.buildImageViewTarget(view, transcodeClass),
      /*targetListener=*/ null,
      requestOptions);
}

3.3.1.2 继续跟进into()的重载方法

private <Y extends Target<TranscodeType>> Y into(
    @NonNull Y target,
    @Nullable RequestListener<TranscodeType> targetListener,
    @NonNull RequestOptions options) {
  Util.assertMainThread();
  Preconditions.checkNotNull(target);
  if (!isModelSet) {
    // 调用链检测
    throw new IllegalArgumentException("You must call #load() before calling #into()");
  }

  options = options.autoClone();
  // 构建图片请求
  Request request = buildRequest(target, targetListener, options);
  // 获取Target载体已有的请求
  Request previous = target.getRequest();
  // 如果两个请求是等效的 && 之前的请求是忽略内存缓存的而且该请求还没有完成的话
  if (request.isEquivalentTo(previous)
      && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
    // 那就释放掉新构建的请求  
    request.recycle();
    // If the request is completed, beginning again will ensure the result is re-delivered,
    // triggering RequestListeners and Targets. If the request is failed, beginning again will
    // restart the request, giving it another chance to complete. If the request is already
    // running, we can let it continue running without interruption.
    // 如果之前构建的请求还没有开启
    if (!Preconditions.checkNotNull(previous).isRunning()) {
      // Use the previous request rather than the new one to allow for optimizations like skipping
      // setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
      // that are done in the individual Request.
      // 则就此启动异步请求
      previous.begin();
    }
    return target;
  }
  // 清除target旧的request,并释放掉旧的request
  requestManager.clear(target);
  // 绑定新的图片请求
  target.setRequest(request);
  // 启动异步请求
  requestManager.track(target, request);
  
  return target;
}

3.3.1.3 跟进 requestManager.track()

image.png

/**
 * Starts tracking the given request.
 */
public void runRequest(Request request) {
  // 加入请求集合中
  requests.add(request);
  // 如果当前不是暂停状态
  // RequestManger收到生命周期的回调时,会修改该状态
  // 这也是为什么Glide能根据生命周期的变化来管理图片的加载周期
  if (!isPaused) {
    // 开始请求图片
    request.begin();
  } else {
    // 否则加入待请求的集合中
    pendingRequests.add(request);
  }
}

3.3.2 阶段二

image.png

3.3.2.1 查看SingleRequest中的begin()

@Override
public void begin() {
  assertNotCallingCallbacks();
  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;
  }

  if (status == Status.RUNNING) {
    throw new IllegalArgumentException("Cannot restart a running request");
  }

  // If we're restarted after we're complete (usually via something like a notifyDataSetChanged
  // that starts an identical request into the same Target or View), we can simply use the
  // resource and size we retrieved the last time around and skip obtaining a new size, starting a
  // new load etc. This does mean that users who want to restart a load because they expect that
  // the view size has changed will need to explicitly clear the View or Target before starting
  // the new load.
  if (status == Status.COMPLETE) {
    onResourceReady(resource, DataSource.MEMORY_CACHE);
    return;
  }

  // Restarts for requests that are neither complete nor running can be treated as new requests
  // and can run again from the beginning.




  /**关键代码**/
  
  status = Status.WAITING_FOR_SIZE;
  // 如果已经指定了宽高
  if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
    // 则进行下一步
    onSizeReady(overrideWidth, overrideHeight);
  } else {
    // 否则去获取宽高,最后还是会走onSizeReady()
    target.getSize(this);
  }
  if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
      && canNotifyStatusChanged()) {
    // 回调至上层
    target.onLoadStarted(getPlaceholderDrawable());
  }
  if (IS_VERBOSE_LOGGABLE) {
    logV("finished run method in " + LogTime.getElapsedMillis(startTime));
  }
}

3.3.2.2 跟进onSizeReady()

image.png

3.3.2.2 跟进engine.load()

public <R> LoadStatus load(...) {

  Util.assertMainThread();
  long startTime = LogTime.getLogTime();
  
  // 构建缓存key
  EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
      resourceClass, transcodeClass, options);
      
  // 根据缓存的配置规则通过key去获取缓存
  
  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;
  }

  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;
  }

  EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
  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对象 
  // 作用:开启线程(作异步加载图片)
  EngineJob<R> engineJob =
      engineJobFactory.build(
          key,
          isMemoryCacheable,
          useUnlimitedSourceExecutorPool,
          useAnimationPool,
          onlyRetrieveFromCache);

  // 创建DecodeJob对象 
  // 作用:对图片解码
  DecodeJob<R> decodeJob =
      decodeJobFactory.build(
          glideContext,
          model,
          key,
          signature,
          width,
          height,
          resourceClass,
          transcodeClass,
          priority,
          diskCacheStrategy,
          transformations,
          isTransformationRequired,
          isScaleOnlyOrNoTransform,
          onlyRetrieveFromCache,
          options,
          engineJob);

  jobs.put(key, engineJob);
  
  // 加入资源回调
  engineJob.addCallback(cb);
  // DecodeJob实现了Runable
  engineJob.start(decodeJob);

  if (Log.isLoggable(TAG, Log.VERBOSE)) {
    logWithTimeAndKey("Started new load", startTime, key);
  }
  return new LoadStatus(cb, engineJob);
}

3.3.2.3 跟进DecodeJob的run()

DecodeJob是一个Runable,看看他的run方法,调用链如下:

DecodeJob.run -> DecodeJob.runWrapped -> DecodeJob.runGenerators ->

SourceGenerator.startNext -> SourceGenerator.startNextLoad ->

MultiModelLoader#MultiFetcher.loadData ->

HttpUrlFetcher.loadData

@Override
public void loadData(@NonNull Priority priority,
    @NonNull DataCallback<? super InputStream> callback) {
  long startTime = LogTime.getLogTime();
  try {
    // 获取输入流,没有引入okhttp,则使用HttpURLConnection
    InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
    // 回调回去
    // callBack
    callback.onDataReady(result);
  } catch (IOException e) {
    if (Log.isLoggable(TAG, Log.DEBUG)) {
      Log.d(TAG, "Failed to load data for url", e);
    }
    callback.onLoadFailed(e);
  } finally {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
    }
  }
}

image.png

image.png

最终回调至View,将图片资源设置给了View。

3.3.3 两个阶段的流程

image.png

3.4 总结

image.png

整个图片加载过程,就是with得到RequestManager,load得到RequestBuilder,然后into开启加载: 创建Request、开启Engine、运行DecodeJob线程、HttpUrlFetcher加载网络数据、回调给载体Target、载体为ImageView设置展示图片。

4. 复制攻略

4.1 面试官:简历上最好不要写Glide,不是问源码那么简单