You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 02:35:19