Android GLSurfaceView.Renderer 类 onDrawFrame 方法的刷新机制

本文深入探讨了在Android中利用GLSurfaceView实现动画的方法,重点解析了onDrawFrame函数的作用及其实现动画的关键作用。文章还介绍了如何通过setRenderMode方法控制onDrawFrame的调用频率,以及如何使用Thread.Sleep调整动画帧率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这玩意只要不是设置渲染模式是when dirty,就会一直调用这个函数,我之前一直以为是采集到数据才会调用

http://blog.waterlin.org/articles/the-principle-of-glsurfaceview-renderer-ondrawframe.html

 

在 Android 中,要实现动画效果,要么通过循环调用 canvas.draw 系统方法,要么通过调用 Opengl ES 中的 GLSurfaceView.Renderer,通过实现方法 onDrawFrame 来进行绘制。

canvas 画图确实要简单得多,但要实现更好的性能最好还是调用 OpenGL 方法,因为它的性能相对来说要好一些。

实现动画的关键就是实现 GLSurfaceView.Renderer 接口内的方法,最重要的一个就是 onDrawFrame,这个方法的原理是 Android 系统会开起一个线程,在此线程内不断的调用 onDrawFrame,以实现动画效果。因此只要在 onDrawFrame 方法里实现你自己需要重复绘制的内容即可。

可以通过 GLSurfaceView 的 setRenderMode 这个方法来设置是通过其它地方的消息来刷新 onDrawFrame,抑或让系统自动刷新 onDrawFrame 这个函数。

在我的测试结果下,如果让系统自动调用 onDrawFrame 函数,会精确地 20 毫秒刷新一次。

另外,如果需要限制 onDrawFrame 的速度,可以在 onDrawFrame 函数里使用 Thread.Sleep 来延长一下线程的执行时间,不过这个时间需要精确计算一下才能达到控制一秒刷新若干帧的要求,需要考虑到当前线程周期里已经消耗的时间等因素

<think>好的,用户现在问的是关于Android中使用OpenGL ES加载超高清图片时如何回收Bitmap、使用LRUCache缓存分块、后台线程解码以及同步到渲染线程的问题。我需要详细回答这四个方面,确保每个步骤都清晰,并且给出代码示例。 首先,用户之前的问题是关于加载超高清图片的整体方法,现在深入到了具体实现细节。这可能意味着用户已经尝试过实现,但遇到了问题,比如内存泄漏或性能问题。需要针对每个小点进行详细说明,避免他们卡在某个环节。 关于及时回收Bitmap,用户可能遇到的是内存泄漏或者OOM的问题。需要强调在加载分块后立即回收的重要性,以及正确使用recycle()方法。同时,可能需要提醒不要在UI线程中处理大图,避免ANR。 使用LRUCache缓存分块,用户可能不太清楚如何结合分块和缓存机制。需要解释LRUCache的基本用法,如何根据分块的坐标生成键,以及如何管理缓存的大小。可能还需要提到软引用或弱引用的使用,但LRUCache本身已经管理了内存,所以重点在正确的实现。 后台线程解码图片是关键,因为解码大图耗时,不能在UI线程进行。需要说明如何使用AsyncTask或者更现代的线程管理方式,比如HandlerThread或ExecutorService。同时,解码后的Bitmap如何传递到渲染线程,避免并发问题。 通过GLSurfaceView.Renderer回调同步到渲染线程,用户可能对OpenGL ES的多线程机制不太熟悉。需要解释GLSurfaceView的渲染循环,以及如何利用queueEvent()将任务从后台线程安全地提交到渲染线程。要强调OpenGL上下文只能在渲染线程使用,所以纹理上传必须在渲染线程进行。 另外,需要注意用户可能对某些概念不太清楚,比如为什么要在特定线程处理纹理,或者如何避免内存泄漏。需要以简洁明了的方式解释这些点,并提供代码示例来帮助理解。可能还需要提到一些常见错误,比如在错误的线程绑定纹理,或者未正确同步导致的崩溃问题。 最后,总结部分要强调这些步骤如何共同解决内存和性能问题,确保用户能够综合应用这些技术。可能还需要提醒测试不同设备的兼容性,比如最大纹理尺寸和压缩格式的支持情况。</think>### 一、何时回收不再使用的Bitmap? #### 1. **回收时机** - **立即回收**:当分块图片完成纹理上传到GPU后,应立即回收对应的`Bitmap`对象。 - **场景触发**: - 分块移出屏幕可视范围时 - 应用切换到后台时 - 收到系统`onTrimMemory()`内存警告时 #### 2. **回收方法** ```java // 上传纹理到GPU后立即回收 Bitmap tileBitmap = decoder.decodeRegion(rect, options); GLUtils.texImage2D(GL_TEXTURE_2D, 0, tileBitmap, 0); tileBitmap.recycle(); // 手动释放Native内存 // 在Activity/Fragment生命周期中释放 @Override protected void onDestroy() { if (bitmap != null && !bitmap.isRecycled()) { bitmap.recycle(); bitmap = null; } super.onDestroy(); } ``` --- ### 二、使用LRUCache缓存常用分块 #### 1. **实现原理** 通过`LruCache`维护最近使用的分块,避免频繁解码同一区域。 #### 2. **代码实现** ```java // 定义缓存 public class TileCache { private LruCache<String, Bitmap> cache; public TileCache(int maxSizeMB) { int maxSize = maxSizeMB * 1024 * 1024; // 转换为字节 cache = new LruCache<String, Bitmap>(maxSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); // 计算Bitmap占用的字节数 } }; } // 添加分块到缓存(键格式:x_y) public void putTile(int x, int y, Bitmap bitmap) { String key = x + "_" + y; cache.put(key, bitmap); } // 从缓存获取分块 public Bitmap getTile(int x, int y) { String key = x + "_" + y; return cache.get(key); } } // 使用示例 TileCache tileCache = new TileCache(50); // 缓存容量50MB Bitmap cachedBitmap = tileCache.getTile(x, y); if (cachedBitmap == null) { cachedBitmap = decodeTile(x, y); tileCache.putTile(x, y, cachedBitmap); } ``` --- ### 三、后台线程解码图片 #### 1. **使用HandlerThread异步解码** ```java // 创建解码线程 HandlerThread decodeThread = new HandlerThread("TileDecoder"); decodeThread.start(); Handler decodeHandler = new Handler(decodeThread.getLooper()); // 提交解码任务 decodeHandler.post(new Runnable() { @Override public void run() { Bitmap tile = decoder.decodeRegion(rect, options); // 通过GLSurfaceView同步到渲染线程 glSurfaceView.queueEvent(() -> { uploadTextureToGPU(tile); // 在渲染线程上传纹理 tile.recycle(); }); } }); ``` #### 2. **使用AsyncTask(适用于简单场景)** ```java private class DecodeTask extends AsyncTask<Rect, Void, Bitmap> { @Override protected Bitmap doInBackground(Rect... rects) { return decoder.decodeRegion(rects[0], options); } @Override protected void onPostExecute(Bitmap bitmap) { uploadTextureToGPU(bitmap); bitmap.recycle(); } } // 启动任务 new DecodeTask().execute(new Rect(x, y, x+size, y+size)); ``` --- ### 四、通过GLSurfaceView.Renderer同步到渲染线程 #### 1. **OpenGL ES线程模型** - **渲染线程**:`GLSurfaceView`内部维护独立的OpenGL渲染线程 - **数据同步**:必须通过`queueEvent()`将纹理上传操作提交到渲染线程 #### 2. **实现步骤** ```java public class MyRenderer implements GLSurfaceView.Renderer { private Queue<Runnable> textureUploadQueue = new ConcurrentLinkedQueue<>(); // 在渲染线程中执行队列任务 @Override public void onDrawFrame(GL10 gl) { while (!textureUploadQueue.isEmpty()) { Runnable task = textureUploadQueue.poll(); if (task != null) task.run(); } // 正常渲染逻辑... } // 添加纹理上传任务(可从任意线程调用) public void scheduleTextureUpload(Runnable task) { textureUploadQueue.add(task); } } // 在Activity中关联Renderer glSurfaceView.setRenderer(new MyRenderer()); // 在后台线程提交任务 decodeHandler.post(() -> { Bitmap tile = decodeTile(x, y); glSurfaceView.queueEvent(() -> { // 此代码块在渲染线程执行 int textureId = uploadTextureToGPU(tile); tile.recycle(); updateTileTexture(x, y, textureId); }); }); ``` --- ### 五、完整工作流程示例 1. **用户滑动屏幕** → 触发可见区域计算 → 生成需要加载的分块坐标列表 2. **缓存检查** → 从`LRUCache`查找是否已有缓存分块 → 命中缓存:直接使用现有纹理 → 未命中:启动后台线程解码 3. **后台解码** → 使用`BitmapRegionDecoder`解码指定区域 → 解码完成后通过`queueEvent()`提交到渲染线程 4. **渲染线程处理** → 上传纹理到GPU → 更新分块纹理ID到渲染数据 → 回收`Bitmap`并缓存到`LRUCache` --- ### 六、注意事项 1. **线程安全** - 所有OpenGL操作(如`glGenTextures`)必须在渲染线程执行 - 使用`ConcurrentHashMap`或`synchronized`保护共享数据 2. **内存监控** ```java // 检查当前内存使用 ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); ((ActivityManager)getSystemService(ACTIVITY_SERVICE)).getMemoryInfo(memoryInfo); if (memoryInfo.lowMemory) { // 主动释放非核心缓存 } ``` 3. **分块尺寸优化** ```java // 根据设备最大纹理尺寸动态调整分块大小 int[] maxTextureSize = new int[1]; glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxTextureSize, 0); tileSize = Math.min(maxTextureSize[0], 2048); // 取较小值 ``` 通过上述方法,可实现超高清图片的高效加载与渲染,同时避免内存溢出和性能卡顿问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值