SurfaceView中lockCanvas锁定耗时过长的原因及解决办法
问题分析与解决方案
首先,你遇到的lockCanvas耗时过长的核心原因,是单线程连续调用lockCanvas+unlockCanvasAndPost的方式完全违背了SurfaceView的设计逻辑,具体拆解如下:
为什么lockCanvas会慢?
- SurfaceView的缓冲区数量有限(系统通常分配2-3个缓冲帧)。当你在单线程里循环调用
draw()时,每次lockCanvas都需要等待前一次unlockCanvasAndPost后的缓冲帧被系统渲染完成并释放。如果前一帧还没被渲染线程处理完,当前的lockCanvas就会阻塞等待——这就是你看到每次lock耗时1400万纳秒的根本原因,600次累积下来总耗时自然会达到13秒。 - 你的调用方式把SurfaceView的异步渲染优势完全浪费,硬生生变成了同步阻塞的串行操作。
解决办法
1. 改用独立渲染线程(最核心修复)
SurfaceView的正确用法是开启独立的渲染线程,在线程内做循环渲染,而非在单线程里连续调用draw()。示例代码如下:
private class RenderThread extends Thread { private volatile boolean running = true; @Override public void run() { while (running) { if (!surface_created) { Log.d("GAME_SURFACE", "SURFACE NOT CREATED YET."); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } continue; } long now = System.nanoTime(); Canvas canvas = holder.lockCanvas(); Log.d("GAME_SURFACE", "Time to lock:" + (System.nanoTime()-now)); if (canvas != null) { // 在这里完成绘制逻辑 canvas.drawBitmap(this.bitmap, null, this.dest, null); holder.unlockCanvasAndPost(canvas); } // 控制帧率,给系统留出缓冲帧处理时间,避免过度占用CPU try { Thread.sleep(16); // 对应约60fps的帧率 } catch (InterruptedException e) { e.printStackTrace(); } } } public void stopRender() { running = false; } }
这种方式会在每次绘制后适当休眠,让系统有时间处理缓冲帧,lockCanvas的阻塞时间会大幅降低。
2. 批量绘制,减少lock/unlock次数
如果你的需求是绘制600个不同的位图,绝对不要每次lock画一个,而是在一次lockCanvas获取画布后,把所有600个bitmap都画在同一个canvas上,再调用unlockCanvasAndPost。这样lock/unlock的次数从600次降到1次,开销直接减少几个数量级。
3. 优化SurfaceHolder配置
- 初始化时设置合适的像素格式:如果不需要透明度,用
PixelFormat.RGB_565比RGBA_8888少占用一半缓冲区内存,渲染速度更快:
holder.setFormat(PixelFormat.RGB_565);
- 设置固定的Surface尺寸,避免系统频繁调整缓冲区大小:
holder.setFixedSize(width, height);
4. 优化Bitmap本身
- 确保bitmap尺寸和Surface尺寸匹配,避免绘制时的缩放开销(
canvas.drawBitmap的dest参数如果和bitmap尺寸差异大,会触发硬件缩放,增加耗时)。 - 提前加载并复用bitmap,避免绘制时重复解码(你的代码里用
this.bitmap应该已经做到,但如果是多个bitmap,最好提前全部加载到内存)。
内容的提问来源于stack exchange,提问作者Andrew Kor
相关产品推荐
相关产品推荐

