SurfaceView在加载时闪烁黑屏

34

我有一个绘画应用程序,对于复杂的绘图(通过AsyncTask完成),需要约2-5秒才能加载画图。为了提供更好的用户体验,在此期间,我会闪烁来自应用目录中已存储的PNG版本的绘图作为ImageView,并在Activity构造函数中调用setContentView()显示加载ProgressBar

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
    android:id="@+id/flash"
    android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@color/note_bg_white"
        android:contentDescription="@string/content_desc_flash_img"
        android:focusable="false" />
<RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="6dp"
        android:paddingLeft="6dp"
    android:paddingRight="6dp"
        android:gravity="bottom|center_vertical">
        <ProgressBar 
            style="?android:attr/progressBarStyleHorizontal"
    android:id="@+id/toolbar_progress"
    android:layout_width="match_parent"
    android:layout_height="18dp"
    android:gravity="bottom|center_vertical" />
    </RelativeLayout>
</FrameLayout>

AsyncTask 完成后,我会再次使用新布局调用 setContentView()
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff">
    <com.my.package.DrawingCanvas
        android:id="@+id/canvas"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:focusable="true"
        android:background="#ffffff" />
</RelativeLayout>

当我使用简单的Canvas模型时,这个功能完美地工作,因为自定义的DrawingCanvas View会在初始的onDraw()上将绘画绘制到屏幕上,然后再显示给用户,但现在使用带有绘图循环的SurfaceView时,我看到了闪烁的布局,然后在加载绘画时,黑屏大约一秒钟,最后才出现新的DrawingCanvas。

我认为原因与绘图循环线程的启动时间有关,我已经在SurfaceView中保留了我的onDraw()覆盖,它被调用,但onDraw()中可用的画布似乎没有绘制到SurfaceView。我还尝试在上面的XML中设置纯色背景,希望至少显示白色背景,但即使从代码中设置它们,它们也似乎不起作用。

有什么建议或者对于我看到的黑屏的解释吗?

编辑:

好的,我确认onDraw()正在绘制到同一个画布上,所以我把我的绘制操作留在那里,希望在SurfaceView最初显示时,用户能像常规Canvas实现一样看到这些绘制,当绘制线程启动时,它会覆盖Canvas。

然而,如果我清除绘制线程操作,我确实可以看到onDraw()的结果,但是再次出现黑屏闪烁之后。如果我完全删除onDraw()覆盖,我仍然会看到黑色闪屏,然后看到来自XML的白色背景的布局。

因此,看起来无论如何,我总是会看到黑屏,除非也许不是切换布局,而是修改已经处于活动状态的“闪光”布局?

编辑2:

尝试使用ViewStub,以便在加载笔记后将SurfaceView膨胀到现有视图中,但是仍存在相同的问题。据我所知,在SurfaceView构造函数和surfaceCreated()调用执行之间有相当大的延迟(约200毫秒),但不确定黑屏是否发生在这里,或者为什么屏幕被绘制成黑色...

编辑3:

我现在的最后一次尝试包括使SurfaceView透明。这与保留现有布局并通过ViewStub简单地向该布局添加内容将导致一个可行的解决方案,但是仍然存在问题,当SurfaceView正在加载时,屏幕会闪烁黑色一瞬间,然后才会显示透明的SurfaceView。如果有其他想法,请发表。


在onCreate中进行一些繁重的操作可能会导致这样的结果。 - Ron
我在onCreate()函数中显示'flash'布局,没有任何延迟。一旦绘制加载完成并且我启动SurfaceView,就会出现延迟/黑屏,这只涉及启动线程。 - Unpossible
你的DrawingCanvas类的源代码可以看到吗? - Reuben Scratton
很遗憾,由于法律限制,我无法发布除小片段以外的任何内容。 - Unpossible
8个回答

151
我想我找到了黑屏的原因。在我的情况下,我在一个片段内使用SurfaceView,并在某些操作后将此片段动态添加到活动中。当我将片段添加到活动时,屏幕会闪烁黑色。我查看了SurfaceView源代码的grepcode,发现以下内容:当SurfaceView首次出现在窗口中时,它通过调用私有的IWindowSession.relayout(..)方法请求更改窗口参数。此方法“给”您一个新的帧、窗口和窗口表面。我认为屏幕就在那一刻闪烁。

解决方案非常简单:如果您的窗口已经具有适当的参数,则不会刷新整个窗口的内容,屏幕也不会闪烁。最简单的解决方法是向活动的第一个布局添加一个高度为0px的普通SurfaceView。这将在活动显示在屏幕上之前重新创建窗口,并且在设置第二个布局时,它将继续使用具有当前参数的窗口。 更新:看起来多年过去了,这种行为仍然存在。我建议使用TextureView代替SurfaceView。这实际上是同样的事情的新实现,它没有这种副作用,也没有在移动时出现黑色背景的问题(例如在ScrollView、ViewPager、RecyclerView等中)。


16
听起来可能有些疯狂,但是在您的活动布局中(根布局的活动中),添加一个大小为0px * 0px的SurfaceView实际上可以消除碎片中SurfaceView黑屏闪烁的问题!我已经尝试过并且完美解决了! - Erik Živković
嗯...解决方案有点不太干净,但是很有效 :) - Bartek Lipinski
我正在实现一个SurfaceView,用于在片段中的MediaPlayer内部,该片段是片段ViewPager的一部分,并且我尝试修复此问题已经几个小时了。我以为是MediaPlayer加载的问题。但事实上是SurfaceView...真糟糕。这很有效!太好了! - JacksOnF1re
数小时的优化尝试...我甚至没有意识到是由于VideoView引起的。我以为我的布局太复杂了,膨胀需要足够的时间来产生可见的闪烁。你的修复方法奏效了,谢谢! - vir us
13
有一种更简洁的方法,只需在宿主活动(host activity)的onCreate()回调函数中调用getWindow().setFormat(PixelFormat.TRANSLUCENT);,然后再调用setContentView()即可,无需繁琐操作。 - CrazyOrr
显示剩余8条评论

5
我不建议使用两个单独的布局来实现此功能。建议您将根元素设置为RelativeLayout,然后将SurfaceView和ImageView作为同级子元素。无论线程如何运行,ImageView应该完全不透明地绘制在SurfaceView的上方,因此您不会看到闪烁。
一旦工作线程加载了绘图并且SurfaceView可以自我绘制,就可以从视图层次结构中删除进度条和ImageView。

我曾尝试过这个方法,但没有成功,如果找不到闪存的根本原因,可能会很快重新尝试。 - Unpossible
应该可以工作。如果您发布com.my.package.DrawingCanvas的代码可能会有帮助? - Reuben Scratton
这并没有解释问题的原因,但似乎作为一种解决方法是有效的,所以我授予了赏金。因为hackbod从未扩展可能是根本原因,而我尝试过的与那行思路相关的所有事情似乎都失败了。 - Unpossible
1
如果您提供一个邮寄地址,我将创建一个演示项目并通过电子邮件发送给您。 - Reuben Scratton
已发送带有演示项目的电子邮件。 - Reuben Scratton
有人能给个例子吗? - Egos Zhang

2

我遇到了同样的问题,但感谢您提供的答案。

有一种不那么混乱的方法,只需在宿主活动的onCreate()回调中调用setContentView()之前添加getWindow().setFormat(PixelFormat.TRANSLUCENT)即可。


2

您需要确保在 onSurfaceChanged() 返回之前,已经完全绘制并发布了 SurfaceView 的 Surface 内容。


我该如何确保Surfaceview被完全绘制和发布?什么可能会导致在完全发布和绘制之前调用onSurfaceChanged()?我确认View在我的绘图线程启动后调用了onSurfaceChanged(),但在绘图循环能够完成其第一个操作(在lockCanvas()调用时触发)之前。 - Unpossible
Hackbod是Android团队的高级开发人员,她可能是正确的。我从来没有想过在onSurfaceChanged()中进行绘图,但是现在她建议这样做,似乎有点显然了。她的意思是在onSurfaceChanged()中调用holder.lockCanvas(),然后绘制一些东西,最后调用holder.unlockCanvasAndPost()。如果您的视图树中还有一个占位符imageview,那么只需用完全透明度填充surfaceview的画布即可实现所需的效果(不会遮挡它)。 - Reuben Scratton
将绘图代码添加到 onSurfaceChanged() 中不幸没有起作用 - 仍然看到黑色闪烁。 - Unpossible
这是解决我的应用程序启动闪烁问题的方法。虽然我在本地程序中使用Vulkan,但我遇到了类似的问题。我发现在 onNativeWindowRedrawNeeded() 返回之前重新绘制窗口是必要的。甚至在原生活动回调参考文档中也有记录。在Java / Kotlin方面,它将是 surfaceRedrawNeeded() 方法。 - John Meschke

2

我猜测你在SurfaceView中绘制了一些复杂的代码,因为据我所知,SurfaceView只有在绘制完成后才会显示完整的视图。我建议您首先进入SurfaceView的onDraw()方法,然后设置画布的背景,接着强制调用invalidate来避免黑屏问题。添加一个条件以确保这个强制刷新只被调用一次。


1
如果您使用带有片段的NavigationDrawer,则Evos解决方案将无法正常工作!
尝试改用带有活动的NavigationDrawer,这将帮助您100%。
如何使用活动实现NavDrawer:链接
另一个有用的链接。(如果SurfaceView将绘制在SlidingMenu之上)

这与导航抽屉甚至没有关系,更多的是关于动态添加带有表面的片段。 - Evos

1
CrazyOrr的解决方案对我有用,但它深深地隐藏在Evos的顶级答案的评论中,所以在这里再次提供:

有一种不那么麻烦的方法,只需在主活动的onCreate()回调中调用setContentView()之前放置getWindow().setFormat(PixelFormat.TRANSLUCENT);。- CrazyOrr May 5 '16 at 7:29

只需简单地放置

getWindow().setFormat(PixelFormat.TRANSLUCENT); 

在活动的onCreate()中对我有效。

0

“在onDraw()中可用的画布似乎不能绘制到SurfaceView。” - 您确定没有创建第二个(或第三个...)Surface View实例吗?它们中的一些可能会变黑并显示一秒钟。我在这里看不到代码,所以我不能确定,但如果它是在我的应用程序中,我首先会检查此错误。


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