Glide BitmapPool 实现原理笔记

Glide 的 BitmapPool 实现原理分析

1. 概述

BitmapPool 是 Glide 内存优化的核心组件之一,主要目的是复用 Bitmap 内存,减少 GC 和内存分配开销,提升应用性能。

2. 主要作用

  • 减少内存分配:复用已分配的 Bitmap 内存
  • 降低 GC 频率:避免频繁创建和回收 Bitmap
  • 提升性能:减少系统级的内存分配调用
  • 防止 OOM:通过合理的复用策略管理内存

3. 核心实现类:LruBitmapPool

3.1 主要数据结构

java 复制代码
public class LruBitmapPool implements BitmapPool {
    private final LruPoolStrategy strategy;  // 存储策略
    private final Set<Bitmap> allowList;     // 白名单(某些Bitmap不放入池)
    private final long initialMaxSize;       // 初始最大容量
    private long maxSize;                    // 当前最大容量
    private long currentSize;                // 当前已使用大小
    private int hits, misses;               // 命中统计
    private int puts;                       // 放入统计
    private int evictions;                  // 驱逐统计
}

3.2 存储策略(LruPoolStrategy)

根据 Android 版本使用不同策略:

java 复制代码
// Android 4.4+ 使用 SizeConfigStrategy
static LruPoolStrategy getDefaultStrategy() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        return new SizeConfigStrategy();
    } else {
        return new AttributeStrategy();
    }
}

SizeConfigStrategy(Android 4.4+):

  • 按 Bitmap 配置(ARGB_8888, RGB_565等)和大小分组
  • 使用 GroupedLinkedMap 存储相同配置的 Bitmap
  • 支持复用更大尺寸的 Bitmap(需重新配置)

AttributeStrategy(Android 4.4以下):

  • 按 width、height、config 精确匹配
  • 使用简单 HashMap 存储

4. 关键方法实现

4.1 get() - 获取 Bitmap

java 复制代码
@Override
public Bitmap get(int width, int height, Bitmap.Config config) {
    Bitmap result = getDirtyOrNull(width, height, config);
    if (result != null) {
        // 清零Bitmap数据
        result.eraseColor(Color.TRANSPARENT);
    } else {
        // 池中没有合适的,创建新的
        result = createBitmap(width, height, config);
    }
    return result;
}

private Bitmap getDirtyOrNull(int width, int height, Bitmap.Config config) {
    // 1. 参数校验
    Preconditions.checkArgument(width > 0 && height > 0);
    
    // 2. 获取合适的Bitmap
    Bitmap result = strategy.get(width, height, config);
    if (result == null) {
        misses++;
    } else {
        hits++;
        currentSize -= strategy.getSize(result);
        // 从allowList移除
        allowList.remove(result);
        // 重新配置Bitmap
        normalize(result);
    }
    return result;
}

4.2 put() - 放入 Bitmap

java 复制代码
@Override
public void put(Bitmap bitmap) {
    // 1. 参数校验
    Preconditions.checkNotNull(bitmap);
    Preconditions.checkArgument(bitmap.isMutable());
    Preconditions.checkArgument(bitmap.getWidth() > 0 
        && bitmap.getHeight() > 0);
    
    // 2. 计算大小并尝试放入
    final int size = strategy.getSize(bitmap);
    strategy.put(bitmap);
    puts++;
    currentSize += size;
    
    // 3. 执行LRU清理
    evict();
}

private void evict() {
    trimToSize(maxSize);
}

private void trimToSize(long size) {
    while (currentSize > size) {
        // 移除最近最少使用的Bitmap
        final Bitmap removed = strategy.removeLast();
        if (removed == null) {
            break;
        }
        currentSize -= strategy.getSize(removed);
        removed.recycle();
        evictions++;
    }
}

5. 内存管理策略

5.1 大小计算

java 复制代码
private static int getBitmapByteSize(int width, int height, 
                                     Bitmap.Config config) {
    return width * height * getBytesPerPixel(config);
}

private static int getBytesPerPixel(Bitmap.Config config) {
    switch (config) {
        case ARGB_8888:
            return 4;
        case RGB_565:
        case ARGB_4444:
            return 2;
        case ALPHA_8:
            return 1;
        default:
            return 1;
    }
}

5.2 自适应大小调整

java 复制代码
public synchronized void setSizeMultiplier(float sizeMultiplier) {
    maxSize = Math.round(initialMaxSize * sizeMultiplier);
    evict();  // 调整后立即清理
}

6. 与 Glide 其他组件的协作

6.1 与 MemoryCache 的关系

java 复制代码
// 在Engine中协调使用
public class Engine implements EngineJobListener {
    private final MemoryCache memoryCache;  // 存储解码后的图片
    private final BitmapPool bitmapPool;    // 存储可复用的Bitmap内存
    
    // 当从MemoryCache移除时,放入BitmapPool
    private void onResourceReleased(Key key, Resource resource) {
        if (resource.isCacheable()) {
            memoryCache.put(key, resource);
        } else {
            bitmapPool.put(resource.get().getBitmap());
        }
    }
}

6.2 与 Downsampler 的协作

java 复制代码
// Downsampler使用BitmapPool复用内存
private static Bitmap decodeStream(InputStream is, BitmapPool pool, 
                                   DecodeFormat decodeFormat) {
    // 从pool获取Bitmap
    Bitmap result = pool.get(options.outWidth, options.outHeight, 
                            options.inPreferredConfig);
    // 解码到复用的Bitmap中
    return BitmapFactory.decodeStream(is, null, options);
}

7. 性能优化技巧

7.1 Bitmap 复用条件

  1. Mutable:必须是可变的 Bitmap
  2. Size >= Requested:尺寸必须大于等于请求尺寸
  3. Config Compatible:配置兼容(可向下兼容)
  4. Reusable:Android 4.4+ 支持复用任意配置的 Bitmap

7.2 配置建议

java 复制代码
// 在GlideModule中配置
public class MyGlideModule implements GlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        // 根据设备内存动态设置
        ActivityManager am = (ActivityManager) 
            context.getSystemService(Context.ACTIVITY_SERVICE);
        boolean isLargeHeap = (context.getApplicationInfo().flags 
            & ApplicationInfo.FLAG_LARGE_HEAP) != 0;
        
        int memoryCacheSize = isLargeHeap ? 256 : 128;
        int bitmapPoolSize = isLargeHeap ? 128 : 64;
        
        builder.setMemoryCache(
            new LruResourceCache(memoryCacheSize * 1024 * 1024));
        builder.setBitmapPool(
            new LruBitmapPool(bitmapPoolSize * 1024 * 1024));
    }
}

8. 注意事项

  1. 线程安全LruBitmapPool 使用 synchronized 保证线程安全
  2. 内存计算:精确计算 Bitmap 内存占用
  3. 配置兼容:注意不同 Android 版本的差异
  4. 白名单机制:某些特殊 Bitmap 不放入池中

9. 总结

Glide 的 BitmapPool 通过精妙的 LRU 策略和版本适配,实现了高效的 Bitmap 内存复用:

  • 减少 80%+ 的 Bitmap 分配:显著降低 GC 压力
  • 智能匹配策略:根据配置和尺寸灵活复用
  • 动态内存管理:根据应用状态调整池大小
  • 版本兼容:适配不同 Android 版本的特性

这种设计使得 Glide 在加载大量图片时仍能保持流畅的性能和低内存占用。

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui