Glide4.9.0-原理篇

Glide4.9.0-原理篇

简介

Glide是一个快速高效的Android图片加载库,注重于平滑的滚动。Glide提供了易用的API,高性能、可扩展的图片解码管道(decode pipeline),以及自动的资源池技术。
虽然Glide 的主要目标是让任何形式的图片列表的滚动尽可能地变得更快、更平滑,但实际上,Glide几乎能满足你对远程图片的拉取/缩放/显示的一切需求。这里是基于目前最新的Glide4.9.0来介绍。


基本用法

1.添加依赖

目前最新的是4.9.0

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

2.加载图片

Glide的图片加载方法很简单,通过context然后加载url,将其放到imageView中显示出来。当然由于Glide是有缓存机制的,因此这个加载过程在第一次是比较明显的,之后的加载会很快的。

    Glide.with(mContext).load(URL).into(imageView);

with()

先是调用Glide.with()。这里with()方法中可传参有context、activity、fragment、FragmentActivity、View。with()方法主要是先去初始化Glide,并且创建RequestMannager对象,并绑定了SupportRequestManagerFragment生命周期函数。

来看源码如下:

  /**
   * Begin a load with Glide by passing in a context.
   *
   * <p> Any requests started using a context will only have the application level options applied
   * and will not be started or stopped based on lifecycle events. In general, loads should be
   * started at the level the result will be used in. If the resource will be used in a view in a
   * child fragment, the load should be started with {@link #with(android.app.Fragment)}} using that
   * child fragment. Similarly, if the resource will be used in a view in the parent fragment, the
   * load should be started with {@link #with(android.app.Fragment)} using the parent fragment. In
   * the same vein, if the resource will be used in a view in an activity, the load should be
   * started with {@link #with(android.app.Activity)}}. </p>
   *
   * <p> This method is appropriate for resources that will be used outside of the normal fragment
   * or activity lifecycle (For example in services, or for notification thumbnails). </p>
   *
   * @param context Any context, will not be retained.
   * @return A RequestManager for the top level application that can be used to start a load.
   * @see #with(android.app.Activity)
   * @see #with(android.app.Fragment)
   * @see #with(android.support.v4.app.Fragment)
   * @see #with(android.support.v4.app.FragmentActivity)
   */
  @NonNull
  public static RequestManager with(@NonNull Context context) {
    return getRetriever(context).get(context);
  }


详细的流程实现如下:



getRequestManagerFragment()方法的调用流程如下:


首先先根据tag调用findFragmentByTag()查找fragment;若找不到,则从SupportRequestManagerFragments队列中找;若还找不到,则会新建一个SupportRequestManagerFragment,并将这个新建的fragment放入到SupportRequestManagerFragments队列中。
调用lifecycleListener的onStart(),将Fragment和Activity绑定。之后使用handler发送ID_REMOVE_SUPPORT_FRAGMENT_MANAGER消息将从队列中移除Fragment




build()方法的实现流程如下:

通过getRequestManagerFragment()获取到SupportRequestManagerFragment对象,然后获取SupportRequestManagerFragment对象的Lifecycle对象。然后调用到build()方法去创建RequestManager对象的时候会将这个Lifecycle对象作为参数传入。
而RequestManager类实现了LifecycleListener接口,通过lifecycle.addListener(this)方法添加了监听器 ,并且实现接口方法onStart()/onStop()。
因此当SupportRequestManagerFragment的生命周期发生变化的时候,就会去调用Lifecycle对象中的生命周期方法,而该生命周期方法的实现是在RequestManager。这就使得Fragment生命周期和Glide请求之间形成互相监听绑定。原因:由于Glide无法监听Activity状态,而fragment和activity生命周期是同步的,因此通过一个Fragment 子类SupportRequestManagerFragment来实现监听,这样如果activity销毁了,那么就能通知调用stop方法停止加载了。

load()

接着来看RequestManager.load(),源码如下:

  /**
   * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(String)}.
   *
   * @return A new request builder for loading a {@link Drawable} using the given model.
   */
  @NonNull
  @CheckResult
  @Override
  public RequestBuilder<Drawable> load(@Nullable String string) {
    return asDrawable().load(string);
  }

也可以看到load()方法中传入的参数有多个类型:bitmap、Drawable、String、Uri、File、Integer(如resourceid)。最后调用到RequestBuilder类中的loadGeneric()方法,将传入的参数赋值给RequestBuiler.model,同时isModelSet置为true,最终返回的是一个RequestBuilder对象。

into()

最后看RequestBuilder.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);

    BaseRequestOptions<?> requestOptions = this;
    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.
      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.
      }
    }

详细的流程实现如下:


上面的流程图先到SingleRequest@begin()中的这段代码:

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

   ...

我们接着继续:


部分方法说明:

  1. ImageViewTargetFactory@buildTarget() ,传参clazz的传递是其实就是Bitmap.class/Drawable.class/File.class

  1. SingleRequest@setErrorPlaceholder()

这里就能看到,先是判断model是否为空,为空则调用getFallbackDrawable(),获取fallbackDrawable;如果没有获取到fallbackDrawable,则会调用getErrorDrawable(),获取errorDrawable;如果没有获取到errorDrawable,则会调用getPlaceholderDrawable(),获取placeholderDrawable。
发现了没有:这里就是占位符、错误符、后备回调符的加载实现的地方。

  private synchronized 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);
  }
  1. SourceGenerator@startNext()
  @Override
  public boolean startNext() {
    ......
    while (!started && hasNextModelLoader()) {
      loadData = helper.getLoadData().get(loadDataListIndex++);
      ....
    }
    return started;
  }

这里主要看一下loadData的获取流程:
通过getLoadData()获取LoadData,这里仅以输入为string类型的URL为例(当然输入类型不一样,所调用的fetcher也不同,但是实现逻辑基本是一样的)。

--> DecodeHelper.getLoadData()

 --> Registry.getModelLoaders()

 --> ModelLoaderRegistry.getModelLoaders()

 --> ModelLoaderRegistry.getModelLoadersForClass()
	
	这里是通过遍历multiModelLoaderFactory

 --> Glide.Glide().append()

	Glide的构造函数中会调用Registry.append()

 --> ModelLoaderRegistry.append()

	这时我们来看,如果我们传入的URL,那么这是一个String,那有可能是:

    .append(String.class, InputStream.class, new DataUrlLoader.StreamFactory<String>())
    .append(String.class, InputStream.class, new StringLoader.StreamFactory())
    .append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory())

	先来看DataUrlLoader的handlers()方法,需要String以"data:image"开头,很明显我们传入的URL不是这个样子的,第三个是序列化的String也不是的。那只能是第二个StringLoader

 --> StringLoader.StreamFactory()

	工厂类创建一个<Uri.class, InputStream.class>的modelLoader,这时再去Glide的构造函数中查找可以看到.append(Uri.class, InputStream.class, new UrlUriLoader.StreamFactory());同样去看他的工厂类实现是创建一个<GlideUrl.class, InputStream.class>的modelLoader,再去查找:
	.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())

	因此我们找到了modelLoader为HttpGlideUrlLoader

--> ModelLoader.buildLoadData()

--> HttpGlideUrlLoader.buildLoadData()

	这里获取传递过来的String,并且将DataFetcher设置为HttpUrlFetcher。这就使得后面在接口DataFetcher.loadData()方法的实现类是HttpUrlFetcher.loadData()
  1. Downsampler@decodeFromWrappedStreams()

这个方法就是真正的解码实现。

3.占位符

Glide允许用户指定三种不同类型的占位符,分别在三种不同场景使用:占位符、错误符、后备回调符。


占位符placeload:

**占位符是当请求正在执行时被展示的 Drawable 。当请求成功完成时,占位符会被请求到的资源替换。**如果被请求的资源是从内存中加载出来的,那么占位符可能根本不会被显示。如果请求失败并且没有设置error Drawable ,则占位符将被持续展示。类似地,如果请求的url/model为null,并且 error Drawable和 fallback drawable 都没有设置,那么占位符也会继续显示。


实现如下:

RequestOptions requestOptions = new RequestOptions().placeholder(R.mipmap.loading);
Glide.with(context)
     .load(IMAGE_URL)
     .apply(requestOptions)
     .into(imageView);

### 错误符error: **error Drawable在请求永久性失败时展示。**error Drawable 同样也在请求的url/model为null,且并没有设置 fallback drawable时展示。


实现如下:
    RequestOptions requestOptions = new RequestOptions().placeholder(R.mipmap.loading)
            .error(R.mipmap.error);

		Glide.with(context)
      		 .load(IMAGE_URL)
       		 .apply(requestOptions)
       		 .into(imageView);

后备回调符 fallback:

fallback drawable在请求的url/model为null时展示。设计fallback drawable的主要目的是允许用户指示null是否为可接受的正常情况。例如,一个null的个人资料 url 可能暗示这个用户没有设置头像,因此应该使用默认头像。然而,null也可能表明这个元数据根本就是不合法的,或者取不到。 默认情况下Glide将null作为错误处理,所以可以接受null的应用应当显式地设置一个fallback drawable 。


实现如下:

    RequestOptions requestOptions = new RequestOptions().placeholder(R.mipmap.loading)
            .error(R.mipmap.error)
            .fallback(R.mipmap.fallback);

		Glide.with(context)
      		 .load(IMAGE_URL)
       		 .apply(requestOptions)
       		 .into(imageView);

Glide中的大部分设置项都可以通过RequestOptions类和apply()方法来应用到程序中。


4.缓存


默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:

  1. 活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片?
  2. 内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中?
  3. 资源类型(Resource) - 该图片是否之前曾被解码、转换并写入过磁盘缓存?
  4. 数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存?

前两步检查图片是否在内存中,如果是则直接返回图片。后两步则检查图片是否在磁盘上,以便快速但异步地返回图片。
如果四个步骤都未能找到图片,则Glide会返回到原始资源以取回数据(原始文件,Uri, Url等)。

(1).缓存KEY

这里Kye的生成实现是在Engine.load()中

	 EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

往下流程看到EngineKey的构造函数,可以看到传参有:
model(请求加载的URL、File、)、
singature(签名)、
width(宽度)、
height(高度)、
transformations(变换)、
resourceClass(要转换的资源类型)、
transcodeClass(要解码的资源,对应的参数传入是RequestManager.asBitmap())、
options(额外添加的选项,用以适应内存)

key至少要包含model和singature这两个元素。在EngineKey类中有2个重写方法equals和hasCode,其实就是用来判断key的每一个元素是否相同,只要有一个不同就不是同一个Key。

  @Override
  public boolean equals(Object o) {
    if (o instanceof EngineKey) {
      EngineKey other = (EngineKey) o;
      return model.equals(other.model)
          && signature.equals(other.signature)
          && height == other.height
          && width == other.width
          && transformations.equals(other.transformations)
          && resourceClass.equals(other.resourceClass)
          && transcodeClass.equals(other.transcodeClass)
          && options.equals(other.options);
    }
    return false;
  }

  @Override
  public int hashCode() {
    if (hashCode == 0) {
      hashCode = model.hashCode();
      hashCode = 31 * hashCode + signature.hashCode();
      hashCode = 31 * hashCode + width;
      hashCode = 31 * hashCode + height;
      hashCode = 31 * hashCode + transformations.hashCode();
      hashCode = 31 * hashCode + resourceClass.hashCode();
      hashCode = 31 * hashCode + transcodeClass.hashCode();
      hashCode = 31 * hashCode + options.hashCode();
    }
    return hashCode;
  }

(2).缓存类型

A.内存缓存

Glide使用LruResourceCache,这是MemoryCache接口的一个缺省实现,使用固定大小的内存和 LRU 算法。LruResourceCache的大小由 Glide 的MemorySizeCalculator类来决定,这个类主要关注设备的内存类型,设备 RAM 大小,以及屏幕分辨率。
默认情况下Glide是开启内存缓存的,因为BaseRequestOption.isCacheable是为true。若我们不需要内存缓存,则可以调用skipMemoryCache(true)。这样在Engine@load()方法的时候传入的isMemoryCacheable为false。这样在调用loadFromActiveResources()/loadFromCache()的时候会直接返回空,就不会从缓存中加载资源了。

    RequestOptions requestOptions = new RequestOptions().placeholder(R.mipmap.loading)
            .error(R.mipmap.error)
            .fallback(R.mipmap.fallback)
            .skipMemoryCache(true);

a.内存缓存读取逻辑
如上面所说,在图片加载的时候调用到loadFromActiveResources()/loadFromCache()来实现/跳过内存缓存。

先来看loadFromActiveResources()的源码,这里有看到一个弱引用Hashmap ResourceWeakReference来缓存EngineResource对象。

  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }

    return active;
  }
  ....
  final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
  ....
  @Nullable
  synchronized EngineResource<?> get(Key key) {
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }

    EngineResource<?> active = activeRef.get();
    if (active == null) {
      cleanupActiveReference(activeRef);
    }
    return active;
  }

接着看loadFromCache()方法。这里getEngineResourceFromCache()函数获取的cache即是memoryCache,对应的LruResourceCache。获取到图片缓存后,调用activate()生成一个弱引用HashMap:activeEngineResources,并将图片缓存放到activeEngineResources中;而上面的loadFromActiveResources()方法就是从弱引用activeEngineResources中获取图片缓存。这就使得在activeEngineResources中的对象不会被LRU 算法回收。

	private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }

    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    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, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
    }
    return result;
	}

上面的cache是怎么来的?


这就印证了最开始说的,内存缓存是使用LruResourceCache


b.内存缓存写入逻辑
之前有讲到过RequestBuilder.into()的逻辑,最后的方法是在 DecodeJob@notifyEncodeAndRelease()。跟一下后续的流程。可以看到会将engineResource对象put到弱引用hashMap:activeEngineResources中。这样再下次调用的时候就会先从activeEngineResources中engineResource获取对象

c.关键类EngineResource

acquire():当调用此方法后会将计数器acquired自增1;
ResourceListener:监听器接口;
relese():释放资源,同时将计数器acquired自减1;

因此当acquired大于0的时候,说明资源正在使用;若acquired为0的时候会触发监听器接口函数onResourceReleased()会先将缓存图片从缓存中移除,然后将这个对象put到。这样正在使用中的图片放到弱引用对象中;不使用的会先缓存起来。

public class Engine implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {
  ....
	@Override
	public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isMemoryCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }
  ...
}

B.磁盘缓存

Glide 使用DiskLruCacheWrapper作为默认的磁盘缓存。DiskLruCacheWrapper是一个使用 LRU 算法的固定大小的磁盘缓存。默认磁盘大小为250M,位置是在应用的缓存文件夹中的一个特定目录。路径为:/data/data/{包名}/cache/image_manager_disk_cache 。

public interface DiskCache {

  /**
   * An interface for lazily creating a disk cache.
   */
  interface Factory {
    /** 250 MB of cache. */
    int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
    String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";

    /** Returns a new disk cache, or {@code null} if no disk cache could be created. */
    @Nullable
    DiskCache build();
  }
  ....
}

public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory {
  ....
  public InternalCacheDiskCacheFactory(final Context context, final String diskCacheName,
                                       long diskCacheSize) {
    super(new CacheDirectoryGetter() {
      @Override
      public File getCacheDirectory() {
        File cacheDirectory = context.getCacheDir();
        if (cacheDirectory == null) {
          return null;
        }
        if (diskCacheName != null) {
          return new File(cacheDirectory, diskCacheName);
        }
        return cacheDirectory;
      }
    }, diskCacheSize);
  }
  ....
}  

这些在Glide初始化的时候已经创建完成的。


a.缓存策略

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

	.......

	private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        // Skip loading from source if the user opted to only retrieve the resource from cache.
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
	}

这里都会调用到DiskCacheStrategy的decodeCachedResource()/decodeCachedData(),这两个方法是抽象方法,具体实现是:

public abstract class DiskCacheStrategy {
 public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
    @Override
    public boolean isDataCacheable(DataSource dataSource) {
      return dataSource == DataSource.REMOTE;
    }

    @Override
    public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
        EncodeStrategy encodeStrategy) {
      return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
          || dataSource == DataSource.LOCAL)
          && encodeStrategy == EncodeStrategy.TRANSFORMED;
    }

    @Override
    public boolean decodeCachedResource() {
      return true;
    }

    @Override
    public boolean decodeCachedData() {
      return true;
    }
	};
  ....
}

这里一共有五种缓存策略:

ALL、结合DATA和RESORCE的策略

NONE:不缓存

DATA:解码前将资源写入磁盘,缓存原始图片

RESOURCE:解码后将资源写入磁盘,缓存转换后的图片

AUTOMATIC:这是默认缓存策略。它会尝试对本地和远程图片使用最佳的策略。当你加载远程数据(比如,从URL下载)时,AUTOMATIC 策略仅会存储未被你的加载过程修改过(比如,变换,裁剪)的原始数据,因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图,因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。(这段来自官网描述)

因此实现磁盘缓存的方式就是加载不同的缓存策略:

        requestOptions = new RequestOptions().placeholder(R.mipmap.ic_launcher)
        .diskCacheStrategy(DiskCacheStrategy.RESOURCE);

b.磁盘缓存写入逻辑

还记得在into()流程中,实现网络请求的方法吗?没错,是在HttpUrlFetcher@loadData()中:

  @Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      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));
      }
    }
  }

上面可以看到会通过调用loadDataWithRedirects来获取输入流。之后会通过回调方法onDataReady传入这个输入流。我们需要看下onDataReady做了什么!

class SourceGenerator implements DataFetcherGenerator,
    DataFetcher.DataCallback<Object>,
    DataFetcherGenerator.FetcherReadyCallback {
	@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);
    }
  }
  ....
}

上面第6行是否又看到会获取DiskCacheStrategy对象。而在第七行的会调用isDataCacheable()作为判断条件。

public abstract class DiskCacheStrategy {
		public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
    @Override
    public boolean isDataCacheable(DataSource dataSource) {
      return dataSource == DataSource.REMOTE;
    }
    ....
}

这里我们以默认的AUTOMATIC来看,他实现的isDataCacheable()是通过传参和DataSource.REMOTE来做对比。这时我们需要看下DataSource中各属性的含义。

public enum DataSource {
  /**
   * Indicates data was probably retrieved locally from the device, although it may have been
   * obtained through a content provider that may have obtained the data from a remote source.
   */
  LOCAL,
  /**
   * Indicates data was retrieved from a remote source other than the device.
   */
  REMOTE,
  /**
   * Indicates data was retrieved unmodified from the on device cache.
   */
  DATA_DISK_CACHE,
  /**
   * Indicates data was retrieved from modified content in the on device cache.
   */
  RESOURCE_DISK_CACHE,
  /**
   * Indicates data was retrieved from the in memory cache.
   */
  MEMORY_CACHE,
}

可以看到DataSource就是分类了数据来源类型。RMOTE是代表来源于外部,那这里isDataCacheable()的返回值就是true。回溯到上面的onDataReady()方法中的if判断条件成立,那么就将这个资源对象赋值给dataToCache对象。之后按照之前的流程看到又会走到SourceGenerator@startNext()中:

class SourceGenerator implements DataFetcherGenerator,
    DataFetcher.DataCallback<Object>,
    DataFetcherGenerator.FetcherReadyCallback {
  @Override
  public boolean startNext() {
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }
    ....
   }
   ....
}

这里看到了吗?会先去判断dataToCache中是否不为空。当然这里是有对象的,所以会调用cacheData():

  private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      helper.getDiskCache().put(originalKey, writer);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished encoding source to cache"
            + ", key: " + originalKey
            + ", data: " + dataToCache
            + ", encoder: " + encoder
            + ", duration: " + LogTime.getElapsedMillis(startTime));
      }
    } finally {
      loadData.fetcher.cleanup();
    }

    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }

这里是不是很明显看到了:会获取当前的DiskCache,然后将DataCacherWriter和Key 以键值对方式put到DiskCache中。

c.磁盘缓存读取逻辑
在DecodeJob启动子线程操作的时候我们记得还是有一个Stage枚举吗?

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
    Runnable,
    Comparable<DecodeJob<?>>,
    Poolable {
	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 Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        // Skip loading from source if the user opted to only retrieve the resource from cache.
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
  }
  ....
  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,对应的默认缓存策略是AUTOMATIC,所以返回的是RESOURCE_CACHE;再调用getNextGenerator()来获取currentGenerator,返回ResourceCacheGenerator。这时调用SourceCacheGenerator.startNext() ,由于首次加载磁盘缓存为空,所以返回false,进入while循环;
继续RunReason为RESOURCE_CACHE,首先调用getNextStage()来获取stage,对应的默认缓存策略是AUTOMATIC,所以返回的是DATA_CACHE;再调用getNextGenerator()来获取currentGenerator,返回DataCacheGenerator。这时调用DataCacheGenerator.startNext() ,由于首次加载内存缓存为空,所以返回false,继续while循环;
继续RunReason为DATA_CACHE,首先调用getNextStage()来获取stage,对应的默认缓存策略是AUTOMATIC,所以返回的是SOURCE;再调用getNextGenerator()来获取currentGenerator,返回SourceGenerator。这时调用SourceGenerator.startNext(),该方法会去通过http请求获取到资源,并且在缓存策略下会将资源对象缓存到缓存中。同时Runreason赋值为SWITCH_TO_SOURCE_SERVICE,且跳出while循环,并且再次reschedule()会再次调动触发DecodeJob.run();
这时调用到runWrapped()方法,Runreason为SWITCH_TO_SOURCE_SERVICE,会直接调用runGenerators(),此时的currentGenerator为SourceGenerator,调用其startNext()。这里在上一小节中讲述磁盘缓存写入逻辑中有说明,这时的缓存中是有对象的,所以该方法返回true,就不会进入到while循环中了。

大致流程如下:


非首次加载图片:
这个时候缓存中有对象,所以在最开始的SourceCacheGenerator/DataCacheGenerator的startNext()会返回true,且从其中读取到缓存中的对象来加载显示了。

(3).资源管理

Glide 的磁盘和内存缓存都是 LRU ,这意味着在达到使用限制或持续接近限制值之前,它们将占用持续增加的内存或磁盘空间。为了增加额外的灵活性,Glide 提供了一些额外的方式来让你可以管理你的应用使用的资源。(这部分来自于官方文档中描述)

  1. 内存缓存

默认情况下,Glide使用 LruResourceCache ,这是 MemoryCache 接口的一个缺省实现,使用固定大小的内存和 LRU 算法。LruResourceCache 的大小由 Glide 的 MemorySizeCalculator 类来决定,这个类主要关注设备的内存类型,设备 RAM 大小,以及屏幕分辨率。


在AppGlideModule 中使用 applyOptions(Context, GlideBuilder) 方法配置 MemorySizeCalculator

	@GlideModule
	public class YourAppGlideModule extends AppGlideModule {
	@Override
	public void applyOptions(Context context, GlideBuilder builder) {
    MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context)
        .setMemoryCacheScreens(2)
        .build();
    builder.setMemoryCache(new LruResourceCache(calculator.getMemoryCacheSize()));
	}
	}


也可以直接覆写缓存大小:

	@GlideModule
	public class YourAppGlideModule extends AppGlideModule {
	  @Override
	  public void applyOptions(Context context, GlideBuilder builder) {
    builder.setMemoryCache(new YourAppMemoryCacheImpl());
	}
	}

若只是临时的改变内存大小,可以调用setMemoryCategory

  1. 磁盘缓存

从上面的讲述中可知磁盘缓存大小为250M,且会存放在一个指定的目录中。因此我们可以去自定义这个size和路径:

	@GlideModule
	public class YourAppGlideModule extends AppGlideModule {
	@Override
	public void applyOptions(Context context, GlideBuilder builder) {
    int diskCacheSizeBytes = 1024  1024  100;  100 MB
    builder.setDiskCache(new InternalCacheDiskCacheFactory(context, diskCacheSizeBytes));
	}
	}

若是要清理磁盘,可以调用clearDiskCache()即可:

	new AsyncTask<Void, Void, Void> {
	@Override
	protected Void doInBackground(Void... params) {
    // This method must be called on a background thread.
    Glide.get(applicationContext).clearDiskCache();
    return null;
	}
	}

5.变换

在Glide中,Transformations 可以获取资源并修改它,然后返回被修改后的资源。通常变换操作是用来完成剪裁或对位图应用过滤器,但它也可以用于转换GIF动画,甚至自定义的资源类型。


(1).实现流程

还记得在into()方法的时候,会先有一段根据ImageView的getScaleType()方法来判断图片宽高并给requestOptions赋值吗?

	@NonNull
	public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    Util.assertMainThread();
    Preconditions.checkNotNull(view);

    BaseRequestOptions<?> requestOptions = this;
    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.
      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,
        Executors.mainThreadExecutor());
	}

我们以ImageView的默认ScaleType:FIT_CENTER 为例。此时会走到第23行。而23行代码实现流程如下:


接着在解码资源的时候DecodePath.decode()中会通过回调调用DecodeJob.onResourceDecoded()

	public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
      @NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
    return transcoder.transcode(transformed, options);
	}
	....

	<Z> Resource<Z> onResourceDecoded(DataSource dataSource,
      @NonNull Resource<Z> decoded) {
    @SuppressWarnings("unchecked")
    Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
    Transformation<Z> appliedTransformation = null;
    Resource<Z> transformed = decoded;
    if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
      appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
      transformed = appliedTransformation.transform(glideContext, decoded, width, height);
    }
    // TODO: Make this the responsibility of the Transformation.
    if (!decoded.equals(transformed)) {
      decoded.recycle();
    }

    final EncodeStrategy encodeStrategy;
    final ResourceEncoder<Z> encoder;
    if (decodeHelper.isResourceEncoderAvailable(transformed)) {
      encoder = decodeHelper.getResultEncoder(transformed);
      encodeStrategy = encoder.getEncodeStrategy(options);
    } else {
      encoder = null;
      encodeStrategy = EncodeStrategy.NONE;
    }

    Resource<Z> result = transformed;

可以看到第15-18行的实现:当dataSource不是来自于缓存中的话,就会先去获取appliedTransformation对象(它是根据resourceSubClass来判断的,这个在into()方法最开始的时候就会去设置了:BaseRequestOptions.optionalTransform()!!),若我们传入的是Bitmap,则就是调用的BitmapTransformation;接着调用重写的transform()方法。

	public final Resource<Bitmap> transform(
      @NonNull Context context, @NonNull Resource<Bitmap> resource, int outWidth, int outHeight) {
    if (!Util.isValidDimensions(outWidth, outHeight)) {
      throw new IllegalArgumentException(
          "Cannot apply transformation on width: " + outWidth + " or height: " + outHeight
              + " less than or equal to zero and not Target.SIZE_ORIGINAL");
    }
    BitmapPool bitmapPool = Glide.get(context).getBitmapPool();
    Bitmap toTransform = resource.get();
    int targetWidth = outWidth == Target.SIZE_ORIGINAL ? toTransform.getWidth() : outWidth;
    int targetHeight = outHeight == Target.SIZE_ORIGINAL ? toTransform.getHeight() : outHeight;
    Bitmap transformed = transform(bitmapPool, toTransform, targetWidth, targetHeight);

    final Resource<Bitmap> result;
    if (toTransform.equals(transformed)) {
      result = resource;
    } else {
      result = BitmapResource.obtain(transformed, bitmapPool);
    }
    return result;
	}


可以看到这里会去获取期望的width和height,并作为参数传入到transform()方法中。这个方法是一个接口函数。来看他的实现类。这里我们以CenterCrop为例:

	@Override
	protected Bitmap transform(
      @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
    return TransformationUtils.centerCrop(pool, toTransform, outWidth, outHeight);
	}


TransformationUtils类里就是对于图片变化操作的具体实现方法,具体实现我们在后面看。

(2).Transformation API

其实还是对transform()进行了封装,最终实现都是在TransformationUtils类中。

  1. CenterCrop
	public static Bitmap centerCrop(@NonNull BitmapPool pool, @NonNull Bitmap inBitmap, int width,
      int height) {
    if (inBitmap.getWidth() == width && inBitmap.getHeight() == height) {
      return inBitmap;
    }
    // From ImageView/Bitmap.createScaledBitmap.
    final float scale;
    final float dx;
    final float dy;
    Matrix m = new Matrix();
    if (inBitmap.getWidth() * height > width * inBitmap.getHeight()) {
      scale = (float) height / (float) inBitmap.getHeight();
      dx = (width - inBitmap.getWidth() * scale) * 0.5f;
      dy = 0;
    } else {
      scale = (float) width / (float) inBitmap.getWidth();
      dx = 0;
      dy = (height - inBitmap.getHeight() * scale) * 0.5f;
    }

    m.setScale(scale, scale);
    m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));

    Bitmap result = pool.get(width, height, getNonNullConfig(inBitmap));
    // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given.
    TransformationUtils.setAlpha(inBitmap, result);

    applyMatrix(inBitmap, result, m);
    return result;
	}

这里的width/height是如果获取默认的话,则是-1。当然我们如果想要自定义的话,只需要调用override()来实现即可。

	public abstract class BaseRequestOptions<T extends BaseRequestOptions<T>> implements Cloneable {
	private static final int UNSET = -1;

	private int overrideWidth = UNSET;

	public final int getOverrideWidth() {
    return overrideWidth;
	}

	}

因此上面的if语句来看就是如果图片高度大于宽度,就会一是计算出缩放比例scale,另一个就是缩短x坐标距离;若图片宽度大于高度,则一是计算是缩放比例scale,另一个就是缩短y坐标距离。
这是再对Matrix对象进行缩放和平移操作。(这里可以深入看下实现)因此上面的if语句来看就是如果图片高度大于宽度,就会一是计算出缩放比例scale,另一个就是缩短x坐标距离;若图片宽度大于高度,则一是计算是缩放比例scale,另一个就是缩短y坐标距离。这时再对Matrix对象进行缩放和平移操作。(这里可以深入看下实现)

  1. circleCrop

圆形裁剪,即最后生成的图片是圆形的。

  1. MultiTransformation

多重变换。即可以按序执行多个变换。注意的是:向 MultiTransformation 的构造器传入变换参数的顺序,决定了这些变换的应用顺序.


	Glide.with(this)
	.load(url)
	.transform(new MultiTransformation(new FitCenter(), new CircleCrop())
	.into(imageView);

(3).自定义Transformation

尽管 Glide 提供了各种各样的内置 Transformation 实现,如果你需要额外的功能,你也可以实现你自己的 Transformation。
如果你只需要变换 Bitmap,最好是从继承 BitmapTransformation 开始。BitmapTransformation 为我们处理了一些基础的东西,例如,如果你的变换返回了一个新修改的 Bitmap ,BitmapTransformation将负责提取和回收原始的 Bitmap。
类似如下:

	public class FillSpace extends BitmapTransformation {
    private static final String ID = "com.bumptech.glide.transformations.FillSpace";
    private static final String ID_BYTES = ID.getBytes(STRING_CHARSET_NAME);

    @Override
    public Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        if (toTransform.getWidth() == outWidth && toTransform.getHeight() == outHeight) {
            return toTransform;
        }

        return Bitmap.createScaledBitmap(toTransform, outWidth, outHeight, /*filter=*/ true);
    }

    @Override
    public void equals(Object o) {
      return o instanceof FillSpace;
    }

    @Override
    public int hashCode() {
      return ID.hashCode();
    }

    @Override
    public void updateDiskCacheKey(MessageDigest messageDigest)
        throws UnsupportedEncodingException {
      messageDigest.update(ID_BYTES);
    }
	}

注意:
对于任何 Transformation 子类,包括 BitmapTransformation,你都有三个方法你 必须 实现它们,以使得磁盘和内存缓存正确地工作:
equals()
hashCode()
updateDiskCacheKey
如果你的 Transformation 没有参数,通常使用一个包含完整包限定名的 static final String 来作为一个 ID,它可以构成 hashCode() 的基础,并可用于更新 updateDiskCacheKey() 传入的 MessageDigest。如果你的 Transformation 需要参数而且它会影响到 Bitmap 被变换的方式,它们也必须被包含到这三个方法中。

补充

1.Android Studio抛错:Error: Program type already present: android.support.v4.os.ResultReceiver

解决方法:
在gradle.properties中添加:

android.useAndroidX=true
android.enableJetifier=true

2.Glide加载不出图片

碰到这个问题定位方式如下可以添加一个方法

.listener(mRequestListener)

接着重写RequestListener,可以调用GlideException.logRootCauses(Stirng) 将异常信息打印出来

    RequestListener mRequestListener = new RequestListener() {
        @Override
        public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
            e.logRootCauses("xw");
            return false;
        }

        @Override
        public boolean onResourceReady(Object resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) {
            return false;
        }
    };

抛错信息如下:

2019-03-17 17:46:44.568 8310-8310/rose.android.com.mytestsdk I/xw: Root cause (1 of 1)
    java.io.IOException: Cleartext HTTP traffic to b-ssl.duitang.com not permitted
        at com.android.okhttp.HttpHandler$CleartextURLFilter.checkURLPermitted(HttpHandler.java:115)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:458)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:127)
        at com.bumptech.glide.load.data.HttpUrlFetcher.loadDataWithRedirects(HttpUrlFetcher.java:104)
        at com.bumptech.glide.load.data.HttpUrlFetcher.loadData(HttpUrlFetcher.java:59)
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.loadData(MultiModelLoader.java:100)
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.startNextOrFail(MultiModelLoader.java:164)
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.onLoadFailed(MultiModelLoader.java:154)
        at com.bumptech.glide.load.data.HttpUrlFetcher.loadData(HttpUrlFetcher.java:65)
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.loadData(MultiModelLoader.java:100)
        at com.bumptech.glide.load.engine.SourceGenerator.startNext(SourceGenerator.java:62)
        at com.bumptech.glide.load.engine.DecodeJob.runGenerators(DecodeJob.java:309)
        at com.bumptech.glide.load.engine.DecodeJob.runWrapped(DecodeJob.java:279)
        at com.bumptech.glide.load.engine.DecodeJob.run(DecodeJob.java:235)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)
        at com.bumptech.glide.load.engine.executor.GlideExecutor$DefaultThreadFactory$1.run(GlideExecutor.java:446)

Android P 禁止所有http。所以需要修改url的写法即可。

3.Glide参考资料

Glide官方文档:https://muyangmin.github.io/glide-docs-cn/

Glide github:https://github.com/bumptech/glide

郭霖大神的博客文章(基于Glide3):https://blog.csdn.net/sinyu890807/column/info/15318

最后,有不足的地方还希望大佬们多指教指正。感谢。

发布了18 篇原创文章 · 获赞 5 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/zplxl99/article/details/88730987
今日推荐