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 在加载大量图片时仍能保持流畅的性能和低内存占用。

相关推荐
百***78752 小时前
gpt-image-1.5极速接入指南:3步上手+图像核心能力解析+避坑手册
android·java·gpt
撩得Android一次心动3 小时前
Android 四大组件——Service(服务)【基础篇2】
android·java·服务·四大组件·android 四大组件
是垚不是土3 小时前
MySQL8.0数据库GTID主从同步方案
android·网络·数据库·安全·adb
cnxy1883 小时前
MySQL地理空间数据完整使用指南
android·数据库·mysql
Digitally3 小时前
4种方法在电脑上查看安卓短信
android·电脑
_李小白3 小时前
【Android FrameWork】第四十天:SamplingProfilerService
android
走在路上的菜鸟3 小时前
Android学Dart学习笔记第二十四节 类-可调用对象Class()()
android·笔记·学习·flutter
2501_915921433 小时前
Flutter App 到底该怎么测试?如何在 iOS 上进行测试
android·flutter·ios·小程序·uni-app·cocoa·iphone
常利兵3 小时前
Kotlin Flow 从入门到实战:异步数据流处理的终极解决方案
android·kotlin