阅读 1440

Glide 提炼总结

使用

一个简单的使用示例如下:

// 需要注意的是,占位符不是异步加载的
RequestOptions options = new RequestOptions()
        .placeholder(R.drawable.ic_launcher_background)
        .error(R.drawable.error)
        .diskCacheStrategy(DiskCacheStrategy.NONE);
        
Glide.with(this)
     .load(url)
     .apply(options)
     .into(imageView);
复制代码

如果希望只获取资源,而不显示,可以使用 CustomTarget:

Glide.with(context
    .load(url)
    .into(new CustomTarget<Drawable>() {
        @Override
        public void onResourceReady(Drawable resource, Transition<Drawable> transition) {
            // Do something with the Drawable here.
        }

        @Override
        public void onLoadCleared(@Nullable Drawable placeholder) {
            // Remove the Drawable provided in onResourceReady from any Views and ensure
            // no references to it remain.
        }
    });
复制代码

如果需要在后台线程加载,可以使用 submit 获取一个 FutureTarget:

FutureTarget<Bitmap> futureTarget = Glide.with(context)
    .asBitmap()
    .load(url)
    .submit(width, height);

Bitmap bitmap = futureTarget.get();

// Do something with the Bitmap and then when you're done with it:
Glide.with(context).clear(futureTarget);
复制代码

代码架构

关键接口

Glide 源码中最关键的 5 个接口如下:

  1. Request,用于代表一个图片加载请求,相关的类有 RequestOptions(用于指定圆角、占位图等选项)、RequestListener(用于监听执行结果)、RequestCoordinator(用于协调主图、缩略图、出错图的加载工作)、RequestManager(用于在生命周期回调时控制图片加载请求的执行)等

  2. DataFecher,用于获取数据,数据源可能是本地文件、Asset 文件、服务器文件等

  3. Resource,代表一个具体的图片资源,比如 Bitmap、Drawable 等

  4. ResourceDecoder,用于读取数据并转为对应的资源类型,例如将文件转化为 Bitmap

  5. Target,图片加载的目标,比如 ImageViewTarget

缓存接口

缓存相关的接口如下:

  1. MemoryCache,内存缓存,使用 LruCache 实现
  2. DiskCache,本地文件缓存,使用 DiskLruCache 实现
  3. BitmapPool,用于缓存 Bitmap 对象
  4. ArrayPool,用于缓存数据对象
  5. Encoder,用于将数据持久化,例如将 Bitmap 写到本地文件

辅助接口

辅助接口如下:

  1. LifeCycleListener,用于监听 Activity/Fragment 的生命周期,以便 Target 控制动画效果、清除资源
  2. ConnectivityListener,用于监听设备网络情况,以执行对应的操作,比如重新开始加载图片
  3. Transformation,用于执行圆角、旋转等图片变换操作,需要注意的是,这些变换不会应用到占位符中
  4. Transition,用于执行过渡动画效果
  5. ResourceTranscoder,用于将转换资源类型,比如将 Bitmap 转换为 Drawable

其它

还有一些重要的类如下:

  1. Glide,用于提供统一的对外接口
  2. Engine,用于统筹所有的图片加载工作,相关的类有 EngineJob、EngineKey 等
  3. MemorySizeCalculator,用于根据设备分辨率计算内存缓存、BitmapPool、ArrayPool 的大小

因此,一个完整的图片加载流程大致如下:

图片加载流程

添加 Fragment,监听生命周期

方法 Glide.with 用于返回一个 RequestManager,同时添加一个 Fragment 到对应的 Activity/Fragment 上,以监听生命周期:

public class RequestManagerRetriever implements Handler.Callback {

    static final String FRAGMENT_TAG = "com.bumptech.glide.manager";

    @NonNull
    private RequestManagerFragment getRequestManagerFragment(...) {
        RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
        if (current == null) {
            // 创建 Fragment,并添加到当前 Activity/Fragment 上
            current = new RequestManagerFragment();
            fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
        }
        return current;
    }
}
复制代码

这样,RequestManager 就能在 Activity/Fragment 生命周期回调时执行相应的操作,比如暂停/恢复执行网络加载请求、清理资源等:

public class RequestManager implements LifecycleListener {
    @Override
    public synchronized void onStart() {
        resumeRequests(); // 恢复加载
    }

    @Override
    public synchronized void onStop() { 
        pauseRequests(); // 暂停加载
    }

    @Override
    public synchronized void onDestroy() {
        // 清除资源
        for (Target<?> target : targetTracker.getAll()) {
            clear(target);
        }
        requestTracker.clearRequests();
        lifecycle.removeListener(this);
    }
}
复制代码

协调图层

RequestManager 的 load 方法用于返回一个 RequestBuilder,在执行 into 方法时才正式构建请求并执行。

因为一个图片加载请求可能附加了缩略图或出错时显示的图片,为了协调多张图片请求,Glide 将加载请求分为 SingleRequest 、ErrorRequestCoordinator、ThumbnailRequestCoordinator 三种。

协调的方法很简单:

  1. 缩略图和主图同时加载,如果主图先加载完成,则取消缩略图的加载请求,否则先显示缩略图,等主图加载完成后再清除
  2. 对于出错图,则是在主图加载失败之后,才开始加载并显示

其中,缩略图、错图都是一个独立的 SingleRequest,都有一套完整的加载流程,并且在加载完成后将资源发送给 Target。

为什么不需要协调占位图?因为占位图是直接在主线程中从 Android Resource 中加载的,并且第一时间就会加载并显示,因此不需要协调。

获取目标尺寸,设置占位图

图片的加载流程是从 Request 的 begin 方法开始的,在正式加载数据之前,Glide 需要获取 Target 的尺寸,以便在加载完成之后对图片进行缩放。此外,占位图也是在这时设置的:

public final class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback {

    @Override
    public void begin() {
        status = Status.WAITING_FOR_SIZE;
        // 获取 Target 的尺寸,获取完成后回调 SizeReadyCallback
        target.getSize(this);
        // 设置占位图
        target.onLoadStarted(getPlaceholderDrawable());
    }
}
复制代码

以 ViewTarget 为例,它是通过监听 ViewTreeObserver 来拿到自身宽高的:

public abstract class ViewTarget<T extends View, Z> extends BaseTarget<Z> {

    void getSize(@NonNull SizeReadyCallback cb) {
        cbs.add(cb);
        // 在 View 即将被渲染时回调
        ViewTreeObserver observer = view.getViewTreeObserver();
        observer.addOnPreDrawListener(layoutListener);
    }

    // 获取尺寸并回调
    void onPreDraw() {
        int currentWidth = getTargetWidth();
        int currentHeight = getTargetHeight();
        notifyCbs(currentWidth, currentHeight);
    }
}
复制代码

获取到尺寸之后,就可以正式执行加载操作了:

@Override
public void onSizeReady(int width, int height) {
    status = Status.RUNNING;
    // 加载
    engine.load(...);
}
复制代码

内存缓存

加载的第一步是尝试从内存缓存中获取:

public class Engine {

    public <R> LoadStatus load(...) {
        EngineKey key = keyFactory.buildKey(...);

        // 检查内存缓存
        EngineResource<?> memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
        if (memoryResource != null) { // 如果能找到,直接返回即可
            cb.onResourceReady(...);
            return null
        }

        // 否则创建新的加载工作
        return waitForExistingOrStartNewJob(...);
    }
}
复制代码

Glide 的内存缓存包括两部分:

  1. 活跃的资源,即加载完成后回调给 Target 并且未被释放的资源,可以简单理解为正在显示的图片
  2. 内存缓存,即不再显示,但仍处于内存中未被 LRU 算法移除的资源
@Nullable
private EngineResource<?> loadFromMemory(...) {
    // 活跃的资源
    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
        return active;
    }

    // 内存缓存
    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
        return cached;
    }

    return null;
}
复制代码

检查是否有相同的工作正在执行

Glide 使用 Job 代表一个图片加载工作,在创建新的 Job 对象之前,需要先检查是否有相同的工作正在执行,如果有,则添加回调并返回,否则创建新的 Job 对象并执行:

private <R> LoadStatus waitForExistingOrStartNewJob(...) {
    // 检查是否有正在执行的相同的工作
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) { // 如果有,则添加回调并返回
        current.addCallback(cb, callbackExecutor);
        return new LoadStatus(cb, current);
    }

    // 否则创建新的加载工作并执行
    EngineJob<R> engineJob = engineJobFactory.build(...);
    // 执行
    engineJob.start(decodeJob);
    return new LoadStatus(cb, engineJob);
}
复制代码

磁盘缓存

图片加载的第二步是尝试从磁盘缓存中获取数据,Glide 的磁盘缓存分为两类:

  1. ResourceCache,指执行了缩放、变换等操作后的图片
  2. DataCache,指原始图片缓存

Glide 首先会从缩放、变换后的文件缓存中查找,如果找不到,再到原始的图片缓存中查找。

以 ResourceCache 为例,它首先会通过 DiskCache 获取文件:

class ResourceCacheGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object> {

    @Override
    public boolean startNext() {
        currentKey = new ResourceCacheKey(...);
        // 获取文件
        cacheFile = helper.getDiskCache().get(currentKey);
        // 获取数据
        loadData.fetcher.loadData(helper.getPriority(), this);

        return started;
    }
}
复制代码

接着再通过 DataFetcher 获取数据:

private static final class FileFetcher<Data> implements DataFetcher<Data> {

    @Override
    public void loadData(...) {
        try {
            data = new InputStream(file);
            callback.onDataReady(data);
        } catch (FileNotFoundException e) {
            callback.onLoadFailed(e);
        }
    }
}
复制代码

获取服务器数据

如果磁盘缓存中找不到对应的图片,则需要到服务器中获取数据。以 OkHttp 为例:

public class OkHttpStreamFetcher implements DataFetcher<InputStream>, okhttp3.Callback {
    @Override
    public void loadData(...) {
    	// 执行网络请求
        Request request = new Request.Builder().url(url.toStringUrl()).build;
        call = client.newCall(request);
        call.enqueue(this);
    }

    @Override
    public void onResponse(@NonNull Call call, @NonNull Response response) {
    	// 获取 socket I/O 流
        responseBody = response.body();
        if (response.isSuccessful()) {
            long contentLength = responseBody.contentLength();
            stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
            callback.onDataReady(stream);
        } else {
            callback.onLoadFailed(new HttpException(response.message(), response.code()));
        }
    }
}
复制代码

缓存原始数据

成功获取到数据之后,DataFetcher 的 Callback 就会将原始数据回调给 SourceGenerator,并缓存到磁盘文件中:

class SourceGenerator implements DataFetcherGenerator, DataFetcherGenerator.FetcherReadyCallback {

    void onDataReadyInternal(LoadData<?> loadData, Object data) {
        // 根据缓存策略判断是否可以缓存
        DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
        if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
            dataToCache = data;
            cb.reschedule();
        }
    }

    @Override
    public boolean startNext() {
        if (dataToCache != null) {
            Object data = dataToCache;
            dataToCache = null;
            cacheData(data);
        }
        ...
    }

    // 写入到磁盘
    private void cacheData(Object dataToCache) {
        helper.getDiskCache().put(originalKey, writer);
    }
}
复制代码

解码

从磁盘缓存/远程服务器中获取到的是数据流,还需要解码为 Bitmap、Gif 等资源类型。这个工作是 ResourceDecoder 来完成的:

public class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {
    @Override
    public Resource<Bitmap> decode(...) {
        return downsampler.decode(...); // 缩放
    }
}
复制代码
public final class Downsampler {

    private Resource<Bitmap> decode(...) {
        Bitmap result = decodeFromWrappedStreams(...);
        return BitmapResource.obtain(result, bitmapPool);
    }
}
复制代码

在解码过程中,如果检查到设备支持,则使用 GPU 存储 Bitmap 数据:

boolean setHardwareConfigIfAllowed(...) {
    boolean result = isHardwareConfigAllowed(...);
    if (result) {
        optionsWithScaling.inPreferredConfig = Bitmap.Config.HARDWARE;
        optionsWithScaling.inMutable = false;
    }
    return result;
}
复制代码

否则使用 ARGB_8888 存储:

public enum DecodeFormat {
    PREFER_ARGB_8888,

    PREFER_RGB_565;

    public static final DecodeFormat DEFAULT = PREFER_ARGB_8888;
}
复制代码

值得注意的是,此时 Glide 会判断数据的来源,如果是不是从变换后的文件缓存中获取的,就需要将圆角、旋转等效果应用到 Bitmap 上:

class DecodeJob<R> {

    <Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) {
        Resource<Z> transformed = decoded;
        if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
            appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
            transformed = appliedTransformation.transform(glideContext, decoded, width, height);
        }
        return result;
    }
}
复制代码

收尾、显示

解码完成之后,首先会将结果回调给 Engine、Target 等对象,接着将变换后的图片数据写入到磁盘缓存,最后清理资源:

class DecodeJob<R> {

    private void decodeFromRetrievedData() {
        Resource<R> resource = decodeFromData(currentFetcher, currentData, currentDataSource);
        notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
    }

    private void notifyEncodeAndRelease(...) {
        // 将结果回调给 Engine、SingleRequest 等对象
        notifyComplete(result, dataSource, isLoadedFromAlternateCacheKey);
        // 将数据写入到磁盘缓存
        deferredEncodeManager.encode(diskCacheProvider, options);
        // 清理资源
        onEncodeComplete();
    }

    private static class DeferredEncodeManager<Z> {

        void encode(DiskCacheProvider diskCacheProvider, Options options) {
            // 这里缓存的是变换后的图片数据
            diskCacheProvider
                .getDiskCache()
                .put(key, new DataCacheWriter<>(encoder, toEncode, options));
        }
    }
}
复制代码

Engine 收到回调后会使用 ActiveResources 记录该资源,并移除当前加载工作:

public class Engine {

    @Override
    public synchronized void onEngineJobComplete(...) {
        activeResources.activate(key, resource);
        jobs.removeIfCurrent(key, engineJob);
    }
}
复制代码

以 ImageView 为例,Target 收到回调后首先判断是否需要执行过渡动画,接着将图片资源设置到 View 里面,最后判断 Resource 是否为 gif 动画,如果是,则开始执行动画:

public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z> {

    @Override
    public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
        if (transition == null || !transition.transition(resource, this)) { // 如果没有过渡动画,则直接设置图片资源
            setResourceInternal(resource);
        } else { // 否则执行动画
            maybeUpdateAnimatable(resource);
        }
    }

    private void setResourceInternal(@Nullable Z resource) {
        setResource(resource);
        maybeUpdateAnimatable(resource);
    }

    private void maybeUpdateAnimatable(@Nullable Z resource) {
        if (resource instanceof Animatable) { // gif 动画
            animatable = (Animatable) resource;
            animatable.start();
        } else {
            animatable = null;
        }
    }

    protected abstract void setResource(@Nullable Z resource);
}
复制代码

缓存 & 资源重用机制

分配内存

在使用内存缓存、对象池之前,Glide 首先会根据设备情况分配对应的内存:

public final class GlideBuilder {

    Glide build(@NonNull Context context) {
        bitmapPool = new LruBitmapPool(memorySizeCalculator.getBitmapPoolSize());
        arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
        memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }
}
复制代码

可以看到,具体的内存大小是 MemorySizeCaculator 计算的。

对于 ArrayPool,如果是低内存设备,则默认分配 2MB,否则分配 4MB:

public final class MemorySizeCalculator {

    private static final int LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR = 2;

    MemorySizeCalculator(MemorySizeCalculator.Builder builder) {

        arrayPoolSize = isLowMemoryDevice(builder.activityManager)
                ? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
                : builder.arrayPoolSizeBytes;
    }

    static boolean isLowMemoryDevice(ActivityManager activityManager) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            return activityManager.isLowRamDevice();
        } else {
            return true;
        }
    }

    public static final class Builder {
        // 4MB.
        static final int ARRAY_POOL_SIZE_BYTES = 4 * 1024 * 1024;

        int arrayPoolSizeBytes = ARRAY_POOL_SIZE_BYTES;
    }
}
复制代码

对于 Bitmap 对象池,因为 Glide 在 API 26 以上统一使用硬件(GPU)存储 Bitmap,因此对内存的需求小了很多,只需要一个屏幕分辨率(ARGB),对于 1920x1080 的手机,大约是 8MB,而在 API 26 之前,则需要 4 个屏幕分辨率,也就是 32 MB。

static final int BITMAP_POOL_TARGET_SCREENS =
    Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 4 : 1;

int widthPixels = builder.screenDimensions.getWidthPixels();
int heightPixels = builder.screenDimensions.getHeightPixels();
int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;

int targetBitmapPoolSize = Math.round(screenSize * BITMAP_POOL_TARGET_SCREENS);
复制代码

内存缓存则默认使用 2 个屏幕分辨率,对于 1920x1080 的手机,大约是 16MB:

static final int MEMORY_CACHE_TARGET_SCREENS = 2;
int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
复制代码

另外,Bitmap 对象池和内存缓存受到应用最大可用缓存的限制:

public class ActivityManager {

    public int getMemoryClass() {
        return staticGetMemoryClass();
    }
}
复制代码

但现在的手机性能已经很高了,这个值一般比较大,即使 Glide 会在此基础之上乘以一个系数(0.4 或 0.33),也基本够用,因此就不考虑了。

重用 Bitmap

BitmapPool 使用 LRU 算法实现,主要接口如下:

public interface BitmapPool {

    void put(Bitmap bitmap);

    // 返回宽高、config 一模一样的对象,同时将像素数据清除,如果找不到,就分配一个新的
    Bitmap get(int width, int height, Bitmap.Config config);

    // 返回宽高、config 一模一样的对象,如果找不到,就分配一个新的
    Bitmap getDirty(int width, int height, Bitmap.Config config);

    // 清除所有对象,在设备内存低时(onLowMemory)调用
    void clearMemory();

    // 根据内存情况选择移除一半或所有对象
    void trimMemory(int level);
}
复制代码

在回收 Bitmap 对象时就会将它移入对象池中:

public class BitmapResource implements Resource<Bitmap>, Initializable {
    @Override
    public void recycle() {
        bitmapPool.put(bitmap);
    }
}
复制代码

重用时机主要是在执行变换效果时:

public final class TransformationUtils {
    public static Bitmap centerCrop(...) {
        ...
        Bitmap result = pool.get(width, height, getNonNullConfig(inBitmap));
        applyMatrix(inBitmap, result, m);
        return result;
    }
}
复制代码

重用活跃的资源

活跃的资源指已加载完成,并且未被释放的资源:

public class Engine {

    private final ActiveResources activeResources;

    @Override
    public synchronized void onEngineJobComplete(...) {
        activeResources.activate(key, resource);
    }

    @Override
    public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
        activeResources.deactivate(cacheKey);
    }
}
复制代码

释放的时机主要有三个:

  1. 生命周期方法 onStop 回调
  2. 生命周期方法 onDestroy 回调
  3. onTrimMemory 回调,级别为 MODERATE

此外,缩略图会在主图加载完成后释放。因此,活跃的资源可以简单理解为正在显示的资源。

这些资源使用引用计数的方式管理,计数为 0 时释放:

class EngineResource<Z> implements Resource<Z> {

    private int acquired;

    synchronized void acquire() {
        ++acquired;
    }

    void release() {
        boolean release = false;
        synchronized (this) {
            if (--acquired == 0) {
                release = true;
            }
        }
        if (release) {
            listener.onResourceReleased(key, this);
        }
    }
}
复制代码

内存缓存

MemoryCache 的接口和 BitmapPool、ArrayPool 基本一样:

public interface MemoryCache {

    Resource<?> remove(@NonNull Key key);

    Resource<?> put(@NonNull Key key, @Nullable Resource<?> resource);

    // 移除所有对象,在设备内存低时(onLowMemory)调用
    void clearMemory();

    // 根据内存情况选择移除一半或所有对象
    void trimMemory(int level);
}
复制代码

在资源释放时,如果可以使用内存缓存(可通过 RequestOptions 配置,默认可以),则缓存,否则回收:

public class Engine {

    @Override
    public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
        activeResources.deactivate(cacheKey);
        if (resource.isMemoryCacheable()) {
            cache.put(cacheKey, resource);
        } else {
            resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
        }
    }
}
复制代码

回收时会根据资源类型执行不同的操作,如果是 Bitmap,则移入 Bitmap 对象池中,如果不是,要么清除资源,要么什么都不做。

内存缓存使用的 Key 和活跃资源一样,都是 EngineKey,主要根据图片宽高、变换信息、资源类型信息来区分:

class EngineKey implements Key {
    private final int width;
    private final int height;
    private final Class<?> resourceClass;
    private final Class<?> transcodeClass;
    private final Map<Class<?>, Transformation<?>> transformations;
}
复制代码

磁盘缓存

DiskCache 主要提供了三个接口:

public interface DiskCache {

    interface Factory {
        /**
         * 默认缓存大小为 250 MB
         */
        int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;

        /**
         * 默认缓存文件夹
         */
        String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";

        DiskCache build();
    }

    File get(Key key);

    void put(Key key, Writer writer);

    void clear();
}
复制代码

其中,缓存的文件分为两种:

  1. 原始图片
  2. 变换后的图片

具体的文件写入操作是由 Encoder 完成的,以 Bitmap 为例,缓存 Bitmap 时通过 compress 方法写入到文件即可:

public class BitmapEncoder implements ResourceEncoder<Bitmap> {

    @Override
    public boolean encode(...) {
        final Bitmap bitmap = resource.get();
        boolean success = false;
        OutputStream os = new FileOutputStream(file);
        bitmap.compress(format, quality, os);
        os.close();
    }
}
复制代码

如果需要清除文件缓存,可以调用 Glide 的 tearDown 方法:

public class Glide {

    public static void tearDown() {
        glide.engine.shutdown();
    }
}
复制代码
public class Engine {
    public void shutdown() {
        diskCacheProvider.clearDiskCacheIfCreated();
    }

    private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {
        synchronized void clearDiskCacheIfCreated() {
            diskCache.clear();
        }
    }
}
复制代码

磁盘缓存使用的 Key 可以分为三类:

  1. GlideUrl,在获取远程图片数据的时候使用,主要根据 url 地址和 headers 信息来区分
  2. ObjectKey,在获取本地图片数据的时候使用,主要根据 Uri、File 来区分
  3. ResourceCacheKey,在获取变换后的图片缓存时使用,主要根据图片宽高、变换信息、资源类型信息来区分

线程池

Glide 主要分了三个线程池:

  1. 用于获取数据,线程数为 CPU 个数,但不大于 4 个
  2. 用于执行读写缓存,线程数为 1

) 用于执行 gif 动画,在 CPU 个数大于等于 4 时,线程数为 2,否则为 1

public final class GlideBuilder {
    private GlideExecutor sourceExecutor;
    private GlideExecutor diskCacheExecutor;
    private GlideExecutor animationExecutor;

    Glide build(@NonNull Context context) {
        sourceExecutor = GlideExecutor.newSourceExecutor();
        diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
        animationExecutor = GlideExecutor.newAnimationExecutor();
    }
}
复制代码

总结

图片加载流程

总的来说,图片加载流程大致可分为 4 步:

  1. 准备工作
  2. 内存缓存
  3. 数据加载
  4. 解码显示

准备工作包括:

  1. 添加一个 Fragment 到对应的 Activity/Fragment 上面,监听生命周期。以控制请求的暂停执行、恢复执行,或清理资源
  2. 递归构建 Request。以协调主图、缩略图、出错图的加载请求。缩略图和主图同时加载,如果主图先加载完成,则取消缩略图的加载请求,否则先显示缩略图,等主图加载完成后再清除。出错图则是在主图加载失败后才开始加载并显示
  3. 获取目标尺寸。以 ImageView 为例,尺寸是在 ViewTreeObserver 的 onPreDraw 方法回调后获取的。
  4. 设置占位图。占位图是直接调用 setImageDrawable 方法设置的,因此不需要和主图协调

内存缓存包括两部分:

  1. 活跃的资源,即加载完成后已回调给 Target 并且未被释放的资源,可以简单理解为正在显示的图片,通过引用计数判断具体释放的时机
  2. 内存缓存,即不再显示,但仍处于内存中未被 LRU 算法移除的资源

数据加载包括两部分:

  1. 从磁盘缓存中加载
  2. 从目标地址中加载

磁盘缓存又分为两部分:

  1. 从缩放、变换后的文件缓存中查找
  2. 如果找不到,再到原始的图片缓存中查找

目标地址有很多种,比如本地文件路径、文件句柄、Asset、服务器链接等。

成功获取到数据之后:

  1. 首先需要将原始图像数据缓存到本地磁盘中
  2. 接着将数据解码为 Bitmap/Drawable 等资源类型,同时应用圆角等变换效果(如果不是从变换后的文件缓存中获取的话)
  3. 然后才能回调给 Target 并显示
  4. 最后还需要将缩放、变换后的图片数据写入到磁盘缓存,并清理资源

Target 显示图片时还会判断是否需要执行过渡动画,如果需要,则执行过渡动画后再显示资源。显示资源时,会判断该资源类型是否为 gif 图,如果是,则开始执行动画,否则直接显示即可。

缓存、资源重用机制

Glide 的缓存机制主要分为三部分:

  1. 资源重用(数组对象、Bitmap 对象、活跃资源)
  2. 内存缓存
  3. 磁盘缓存(原始图片数据、应用变换后的图片数据)

其中,活跃资源使用引用计数的方式来判断释放的时机,除此之外的其它缓存机制都是通过 LRU 算法来管理数据的。

活跃资源指已加载完成,并且未被释放的资源,可以简单地理解为正在显示的资源。

资源释放的时机主要有三个:

  1. 生命周期方法 onStop 回调
  2. 生命周期方法 onDestroy 回调
  3. onTrimMemory 回调,级别为 MODERATE

资源释放时,首先会判断是否可以使用内存缓存(可通过 RequestOptions 配置,默认可以),如果可以,则使用内存缓存资源,否则回收资源。回收时会根据资源类型执行不同的操作,如果是 Bitmap,则移入 Bitmap 对象池中,如果不是,要么清除资源,要么什么都不做。

内存/磁盘分配:

  1. 数组对象池,如果是低内存设备,则默认分配 2MB,否则分配 4MB
  2. Bitmap 对象池,API 26 之前分配四个屏幕分辨率(1920x1080 的手机大约是 32MB),API 26 之后统一使用 GPU 存储 Bitmap,因此只需要一个屏幕分辨率(1920x1080 的手机大约是 8MB)
  3. 内存缓存,默认使用 2 个屏幕分辨率(1920x1080 的手机大约是 16MB)
  4. 磁盘缓存默认最多缓存 250MB 数据

另外,Bitmap 对象池和内存缓存受到应用最大可用缓存的限制(在此基础之上乘以系数 0.4 或 0.33),如果不够,则按比例分配。

线程池主要分了三个:

  1. 用于获取数据,线程数为 CPU 个数,但不大于 4 个
  2. 用于执行读写缓存,线程数为 1
  3. 用于执行 gif 动画,在 CPU 个数大于等于 4 时,线程数为 2,否则为 1
文章分类
Android
文章标签