Android OpenGLES纹理内存高效管理:求优于内存预警回收的更佳方案
Android OpenGL ES 纹理缓存优化方案(应对内存不足场景)
核心需求
在Android OpenGL ES中管理纹理缓存:内存充足时保留暂时未使用的纹理(后续可复用),内存不足时自动清理未使用纹理(可重新加载)。
现有方案痛点
- 响应系统内存警告:效果尚可,但依赖系统触发时机,清理不够主动及时。
- SoftReference:Android 9及以上版本垃圾回收器行为激进,软引用会被立即回收,完全无法实现内存敏感型缓存的预期效果。
- LruCache:需手动指定最大容量,难以适配不同设备的内存差异;且默认逻辑可能误删正在使用的纹理,导致渲染异常。
推荐优化方案
1. 两级缓存 + 主动内存监控
将纹理分为活跃缓存和非活跃缓存,结合内存状态动态调整:
- 活跃缓存:用强引用保存当前正在使用的纹理(比如当前页面、正在渲染的场景依赖的纹理),确保不会被误清理。
- 非活跃缓存:用弱引用或自定义LRU集合管理暂时未使用的纹理,同时定期监控系统内存:
- 当可用内存高于阈值(例如设备总内存的20%),保留非活跃缓存中的纹理。
- 当可用内存低于阈值,主动清理非活跃缓存的所有纹理,释放OpenGL资源。
实现细节:
- 自定义
TextureCache类,维护两个集合:activeTextures(强引用HashMap,key为纹理标识,value为Texture包装类)、inactiveTextures(LinkedHashMap实现LRU顺序的非活跃纹理集合)。 - 提供
markActive(String textureKey)和markInactive(String textureKey)方法,用于切换纹理的活跃状态。 - 通过
ActivityManager.getMemoryInfo()定期获取内存状态,触发清理逻辑。
2. 带活跃保护的LruCache改进版
针对LruCache的缺点做定制化改造:
- 继承
LruCache,重写entryRemoved()方法,仅当纹理处于非活跃状态时,才调用glDeleteTextures释放OpenGL资源。 - 维护一个
Set<String>记录当前活跃的纹理Key,在清理时跳过活跃纹理,避免误删。 - 动态计算缓存最大容量:根据设备总内存比例设置,例如总内存>4GB时设为512MB,2-4GB设为256MB,<2GB设为128MB,同时支持运行时根据内存压力调整。
示例代码片段:
class ProtectedLruCache extends LruCache<String, Texture> { private final Set<String> activeKeys = new HashSet<>(); public ProtectedLruCache(int maxSize) { super(maxSize); } public void markActive(String key) { activeKeys.add(key); } public void markInactive(String key) { activeKeys.remove(key); } @Override protected void entryRemoved(boolean evicted, String key, Texture oldValue, Texture newValue) { super.entryRemoved(evicted, key, oldValue, newValue); if (!activeKeys.contains(key)) { // 仅清理非活跃纹理的OpenGL资源 GLES20.glDeleteTextures(1, new int[]{oldValue.getId()}, 0); } } } // 动态计算缓存上限 ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); am.getMemoryInfo(mi); long maxMemory = mi.totalMem / 8; // 取总内存的1/8作为缓存上限 ProtectedLruCache textureCache = new ProtectedLruCache((int) maxMemory);
3. 基于ComponentCallbacks2的分级清理
实现ComponentCallbacks2接口,根据系统内存级别执行不同强度的清理:
- 监听
onTrimMemory()回调,针对不同内存等级(如TRIM_MEMORY_RUNNING_LOW、TRIM_MEMORY_UI_HIDDEN)执行差异化清理:- 轻度内存不足:清理非活跃缓存中最久未使用的纹理。
- 重度内存不足:清空所有非活跃缓存。
实现细节:
public class TextureCache implements ComponentCallbacks2 { private final LinkedHashMap<String, Texture> inactiveCache = new LinkedHashMap<>(16, 0.75f, true); // LRU顺序 private final HashMap<String, Texture> activeCache = new HashMap<>(); @Override public void onTrimMemory(int level) { if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) { // 清理非活跃缓存中一半最久未使用的纹理 int removeCount = inactiveCache.size() / 2; Iterator<Map.Entry<String, Texture>> iterator = inactiveCache.entrySet().iterator(); while (iterator.hasNext() && removeCount > 0) { Texture texture = iterator.next().getValue(); GLES20.glDeleteTextures(1, new int[]{texture.getId()}, 0); iterator.remove(); removeCount--; } } else if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { // 清空所有非活跃缓存 for (Texture texture : inactiveCache.values()) { GLES20.glDeleteTextures(1, new int[]{texture.getId()}, 0); } inactiveCache.clear(); } } // 纹理状态切换、加载等方法... }
关键注意事项
- 线程安全:OpenGL操作必须在GL线程执行,清理纹理时要确保切换到GL线程调用
glDeleteTextures。 - LRU策略:记录纹理的访问顺序,清理时优先移除最久未使用的纹理,提升缓存命中率。
- 避免finalize():不要在
finalize()方法中处理OpenGL资源,该方法执行时机不可控,易导致资源泄漏或延迟释放。
内容的提问来源于stack exchange,提问作者Zach Dean
相关产品推荐
相关产品推荐

