安卓画布多次绘制导致闪烁问题

3

关于此主题的关键词:

  1. CustomSurfaceView: 为三个不同级别创建了三个自定义surfaceview。
  2. Canvas: lock/unlockAndPost方法 (我没有使用自定义位图)
  3. 多线程(每个surface都是单独的一个线程)
  4. 形状 (在画布上的形状)
  5. 客户端/服务器 (架构)
  6. 闪烁问题 (这就是我来到这里的原因)

我们正在开发一个客户端/服务器应用程序,我正在处理客户端方面的工作。我从服务器接收包含有关路径的一般数据(坐标、颜色、宽度[...]) 的消息,例如圆形、矩形、线条和其他形状。Web 应用程序允许用户通过 HTML5 Canvas 将这些数据发送到接收该消息并解析它的 android 设备,以便能够重新绘制所有形状。根据我的经验,在这些方面,我学到的最好的方法是把所有在画布上绘制的东西保存到缓冲区、数组、列表或类似的东西中,然后在需要时重用它 (例如,你可以使用旧路径来展示、隐藏、移动或简单地更改画布中的某些东西)。在我的看法中,android 应用程序遵循了 android 开发和面向对象编程范例的最佳实践,所以我不会假设与错误相关的架构问题。在这种情况下,我将消息保存在 web 客户端侧。当用户在 HTML5 Canvas 上绘制时,包含形状信息的消息能够完美地传递到 android 画布中,但问题出现在这里:

[举个例子] 考虑你绘制了 10 个对象(10 条消息),并且想要仅删除其中一个对象的情况下,唯一的方法是清除所有画布,并重新绘制之前的不包括已删除的形状的图形(因此通过循环消息缓冲区重新发送 9 条消息)。这种方法对于 Web 应用程序来说运作得很好,但会导致 android 客户端出现闪烁问题。因此,在进行了太多的实验后,我找到了一个解决方法,使用 Thread.sleep(100)(哇!100 毫秒太长),以便慢慢解析消息,并让 surfaceview 线程正确地读取数据(通过单例模式访问数据)并将其写入画布的双缓冲区。嗯,虽然效率很低、有些丑陋,但它确实能运作!实际上,我并不喜欢这种“可怕”的解决方法,所以请帮我找到一条出路。

这是从形状容器获取数据并在数据存在的情况下绘制画布的代码片段。每个容器的数据都来自服务器消息。

@Override
public void run() {
    Canvas canvas = null;
    while (running) {
        //this is the surface's canvas
        try {
            canvas = shapesSurfaceHolder.lockCanvas();
            synchronized (shapesSurfaceHolder) {
                if (shapesSurfaceHolder.getSurface().isValid()) {
                    if(!Parser.cmdClear){

                        //draw all the data present
                        canvas.drawPath(PencilData.getInstance().getPencilPath(),
                                PencilData.getInstance().getPaint());
                        canvas.drawPath(RectData.getInstance().getRectPath(),
                                RectData.getInstance().getPaint());
                        canvas.drawPath(CircleData.getInstance().getCirclePath(),
                                CircleData.getInstance().getPaint());
                        canvas.drawPath(LineData.getInstance().getLinePath(),
                                LineData.getInstance().getPaint());
                        canvas.drawText(TextData.getInstance().getText(),
                                TextData.getInstance().getX(),
                                TextData.getInstance().getY(),
                                TextData.getInstance().getPaint());

                    } else {
                        //remove all canvas content and clear data.
                        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                        for (int i = 0; i < AbstractFactory.SHAPE_NUM; i++) {
                            abstracFactory.getShape(i).clearData();
                        }
                    }
                }
            }
        } finally {
            if (canvas != null) {
                shapesSurfaceHolder.unlockCanvasAndPost(canvas);
            }
        }
    }
}//end_run()

我可以总结一下,我的问题似乎是绘制得太快了。
注:
类似概念:Android线程控制多个纹理视图会导致奇怪的闪烁 启用硬件加速。
minSdkVersion 17
测试设备为:
- 三星平板 SM-T113 - 谷歌 Nexus 5
1个回答

2

TextureView的问题是由于TextureView特有的一个bug导致的。你正在使用SurfaceView,所以这里不适用。

在SurfaceView的Surface上绘制时,每次必须更新脏矩形内的每个像素(即传递给lockCanvas()的可选参数)。如果不提供脏矩形,则表示必须更新整个屏幕。这是因为Surface是双或三缓冲的,并且在调用unlockCanvasAndPost()时进行交换。如果锁定/清除/解锁,则下一次锁定/绘制/解锁时,您将无法绘制到先前清除的缓冲区中。

如果要进行增量渲染,则应将Canvas指向离屏位图并在那里进行所有渲染。然后只需在锁定和解锁之间复制整个位图。另一种方法是存储绘图命令,从最初的清除开始,并在锁定/解锁之间播放它们。

如果同时在屏幕上显示三个自定义SurfaceView,则会引起一定程度的担忧。如果它们都处于不同的Z深度(默认值、媒体覆盖层、顶部),则它们将正常工作,但如果您可以将所有内容放在一个上面,则系统通常更有效率。


嗨,fadden,感谢您的回复。我将尝试使用脏矩形,因为您建议的替代方案不起作用,实际上这就是我正在做的...存储所有命令,然后在锁定/解锁之间清除/重绘所有内容。关于“三个自定义surfaceview”...我创建了三个不同的surfaceview,因为每个surface都有不同的目的。“形状”,“远程指针”和“屏幕截图”,我认为这些功能不能共存。我会告诉您成功的情况...我希望如此。再次感谢。 - Andrea D'Ubaldo
嗨@fadden,经过多次尝试使用脏矩形,我可以说我需要更新整个画布,所以surfaceHolder.lockCanvas()没有任何参数是可以的。 我也考虑了你的替代方案:“存储绘图命令,从初始清除开始,并在lock/unlock之间播放它们。”实现如下:canvas = surfaceHolder.lockCanvas(); for( int i = 0; i < commandList.size(); i++){ canvas.drawPath(pathList.get(i),paintList.get(i)); } surfaceHolder.unlockCanvasAndPost(canvas); 对吧? - Andrea D'Ubaldo
在锁定画布后,您可能希望清除整个画布,但是是的,这是可行的方法。当物体重叠时,使用脏矩形会很棘手。在屏幕外绘制位图并将其复制到表面上,随着重复渲染的数量增加,可以变得更有效率。 - fadden
谢谢@fadden,它可以工作!在lockCanvas() unlockCanvasAndPost()之间绘制存储的数据是解决方案。我也很想了解离屏位图的替代方法,所以(如果你愿意)能否为我提供一些示例链接?再次感谢。 - Andrea D'Ubaldo
1
创建一个屏幕大小的位图,然后使用带有位图参数的构造函数创建画布。在该画布上进行所有渲染操作。如果您只想添加一件事情,您不必擦除和重新绘制--因为它是由位图而不是表面支持的,所以它只是将像素渲染到单个缓冲区中。当您想要更新显示时,锁定表面画布,使用单个drawBitmap()调用混合位图,并解锁并发布。 - fadden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接