SurfaceView drawText 闪烁

Posted

技术标签:

【中文标题】SurfaceView drawText 闪烁【英文标题】:SurfaceView drawText flickering 【发布时间】:2015-12-28 11:45:52 【问题描述】:

我正在尝试使用SurfaceView 制作一个简单的秒表。它显示百分之一秒,因此它会尝试尽可能频繁地更新以保持流畅的动画。它在运行 4.0.3 的平板电脑和运行 4.0.4 的手机上运行良好,但是当我在 4.4.4 上尝试时,似乎有些撕裂,我无法解释。

底部的图像显示了应该发生的事情,并且在大多数情况下它是有效的。但是,每隔几秒钟就会出现一次闪烁。当我设法在正确的时间暂停时,我看到 一些 的数字被移到了右边。他们只会向右移动(上图)。这只发生了几分之一秒,刚好足以引起明显的闪烁。

请注意,数字使用一次计算的值(在onMeasure() 中)定位在画布上,并存储在类字段中。它们在初始化后不会改变。我通过记录onDraw() 内部的每个像素值来测试这一点。

在上图中,转置的是分钟值,但有时是其他数字。它们似乎总是在一组中被调换;分钟、秒或厘秒。

进一步:这只影响drawText()我有这个秒表的更复杂版本,有很多路径、圆圈等。问题只发生在文本上。

我查看了关于 SO 和其他地方的几个问题和 cmets。这是我确定的:

我认为这不是缓冲区问题。行为似乎类似于 基于缓冲区的撕裂的描述,但在这种情况下,我不认为 问题可能是旧数据被发布到屏幕上,因为 数字从不在它们显示的位置绘制。

我在评论中读到它可能与 更新在 onDraw() 之间多次调用的值 方法调用。我已尝试限制更新,但问题 持续存在。

我还尝试绘制到临时位图并使用canvas.drawBitmap() onDraw() 函数,再次无济于事。

基本上,我想知道:

是什么导致了这个问题? 为什么它只在更高版本的 Android 中? 我该怎么办?

【问题讨论】:

听起来不像是“闪烁”,而是偶尔在错误的位置绘制文本。当您说您绘制临时位图“无济于事”时,这是否意味着您继续看到在错误位置呈现的分钟数字?它总是分钟数字吗?使用screenrecord 获取录音,然后逐帧浏览。当框架绘制不正确时,计数器是向前前进还是向后跳跃?覆盖onDraw() 在 SurfaceView 上绘图是个坏主意;重命名函数,去掉“@Override”,并确认问题仍然存在。 我确实尝试将绘图放入自定义方法中,这解决了问题,但导致帧率大幅下降,所以我重新使用 onDraw。我会按照你的建议尝试录制。 如果您使用的是onDraw(),那么当应用程序框架刷新 View UI 时,您将在 View 上绘图(就像自定义 View 一样)而不是 Surface。 View 与 Surface 重叠,因此您在两个重叠的图层上绘图。 Surface 的大小不必与 View 的大小相同,因此使用onMeasure() 是不合适的(参见SurfaceHolder#setFixedSize())。您可能应该改用自定义视图:developer.android.com/training/custom-views/index.html 有趣...我最初尝试使用自定义视图,但帧率从来没有高到足以使动画看起来流畅。 按速度递减顺序: (1) 使用 OpenGL 渲染到 SurfaceView Surface; (2) 使用 Canvas 渲染到硬件加速的自定义视图上; (3) 使用 Canvas 渲染到 SurfaceView Surface 上。并非所有 Canvas 操作都可以在所有版本的 Android 上在硬件中完成;见developer.android.com/guide/topics/graphics/hardware-accel.html。 【参考方案1】:

阅读@fadden 的 cmets 后,我能够更有效地进行搜索。

看来我的错误是在动画线程中尝试调用onDraw()。当我将那个电话改为postInvalidate() 时,它立即解决了问题。简而言之,动画循环的内部是这样的:

holder = view.getHolder();
canvas = holder.lockCanvas();
if (canvas != null) 
    synchronized (holder) 
        if (view != null) 
            view.DEBUG_THREADCALLS++;
            // NOT view.onDraw(), but:
            view.postInvalidate();
        
    

据我了解,对onDraw() 的调用会导致问题,因为它会导致应用同时绘制ViewSurface

我猜这个问题只出现在更现代的 Android 版本中的原因可能是因为更新的设备具有更高的刷新率,所以问题更明显。我注意到该解决方案还显着提高了我最旧设备上的帧速率,这可能是因为它无法同时绘制两个位置。


更新

根据下面非常有用的 cmets 的进一步建议,我完全删除了 SurfaceView,现在正在使用单独的动画线程在自定义视图上绘图。结果是即使在旧设备上也能获得令人难以置信的流畅动画效果,比 SurfaceView 好得多!因此,对于尝试使用 SurfaceView 进行动画处理的其他人,我说:除非您有充分的理由,否则不要这样做!

【讨论】:

由于您没有使用canvas,您可以删除除view.postInvalidate()之外的所有代码。 哦,哇。所以从字面上看,我需要做的就是创建一个单独的线程来发布无效?不费吹灰之力锁定画布?为什么这么多教程都像我以前那样使用 onDraw...? 很多SurfaceView教程都是完全错误的。 (这提醒了我,您现在可以将 SurfaceView 更改为 View,因为您根本不使用 Surface。) 这不会导致更生涩的动画吗?我认为我使用 SurfaceView 的最初原因是因为在 Android 规范中的某处提到使用 SurfaceView 更好地完成高帧率动画。无论如何,我会试一试,看看会发生什么。你是怎么知道这么多关于这些东西的?你能推荐一些好的资源吗?还是只是经验? 我曾在 Google 工作(点击我的名字查看我的个人资料)。我建议阅读 source.android.com/devices/graphics/architecture.html 以了解这些部分是如何组合在一起的。

以上是关于SurfaceView drawText 闪烁的主要内容,如果未能解决你的问题,请参考以下文章

SurfaceView 闪烁/撕裂

Android SurfaceView导致屏幕闪烁

SurfaceView 在加载时闪烁黑色

SurfaceView 与 TextureView 详解

SurfaceView 与 TextureView 详解

安卓——SurfaceView。在不清除屏幕的情况下更新。 (注:我是菜鸟)