Thread.sleep在主线程上会导致视图更改被跳过吗?

huangapple go评论75阅读模式
英文:

Thread.sleep on main thread causing view changes to be skipped?

问题

I'm trying to add a red flash to my application when some event occurs.
我正在尝试在某个事件发生时为我的应用程序添加红色闪烁效果。

Using the windowManager, i've created and added a simple view that just fills the canvas with red, and initializes the opacity to 0%
使用windowManager,我创建并添加了一个简单的视图,只是用红色填充画布,并将不透明度初始化为0%

To implement the "flash", I plan to set the opacity to 100%, sleep for 25ms, then set the opacity back to 0%
为了实现“闪烁”效果,我计划将不透明度设置为100%,休眠25毫秒,然后将不透明度恢复为0%

I know that I can get this to work properly by doing something like
我知道可以通过类似以下方式使其正常工作

view.alpha = 255f
handler.postDelayed(
  Runnable {
    view.alpha = 0f
  }, 25)

However, I am confused about this behavior when I try to add a delay using Thread.sleep() directly on the main thread
但是,当我尝试在主线程上直接使用Thread.sleep()添加延迟时,我对这种行为感到困惑

view.alpha = 255f
Thread.sleep(25)
view.alpha = 0f

This basically makes it so that the first statement view.alpha = 255f is never executed.
这基本上导致第一条语句view.alpha = 255f从未执行。

However, if i were to put this code in a background thread, then the behavior works as I expect
但是,如果我将此代码放在后台线程中,行为就会按我预期的方式工作

        Thread(
                Runnable {
                  view.alpha = 255f
                  Thread.sleep(50)
                  visualizer.alpha = 0f
                })
            .start()

My questions
我的问题

  1. Why do view changes before sleeping main thread not get executed (or at least are not visible)?
    为什么在主线程休眠之前的视图更改不会被执行(或者至少不可见)?
  2. Why am I able to change the opacity of a view on a background thread? Is this not considered a view change and needs to be done from the UI thread?
    为什么我能够在后台线程中更改视图的不透明度?这不被视为视图更改,需要从UI线程执行吗?
英文:

I'm trying to add a red flash to my application when some event occurs.
Using the windowManager, i've created and added a simple view that just fills the canvas with red, and initializes the opacity to 0%

To implement the "flash", I plan to set the opacity to 100%, sleep for 25ms, then set the opacity back to 0%

I know that I can get this to work properly by doing something like

view.alpha = 255f
handler.postDelayed(
  Runnable {
    view.alpha = 0f
  }, 25)

However, I am confused about this behavior when I try to add a delay using Thread.sleep() directly on the main thread

view.alpha = 255f
Thread.sleep(25)
view.alpha = 0f

This basically makes it so that the first statement view.alpha = 255f is never executed.

However, if i were to put this code in a background thread, then the behavior works as I expect

        Thread(
                Runnable {
                  view.alpha = 255f
                  Thread.sleep(50)
                  visualizer.alpha = 0f
                })
            .start()

My questions

  1. Why do view changes before sleeping main thread not get executed (or at least are not visible)?
  2. Why am I able to change the opacity of a view on a background thread? Is this not considered a view change and needs to be done from the UI thread?

答案1

得分: 3

以下是翻译好的部分:

这是Android绘图系统的工作原理:

当您更改一个视图时,它会向主线程发送一个无效消息。当该消息被处理时,会调用视图的onDraw方法。onDraw方法创建一组绘制命令,然后由另一个线程处理这些命令并将它们绘制到屏幕上。

如果主线程休眠,它将永远不会返回到事件循环顶部的处理程序。如果它不这样做,它就永远不会处理无效消息。因此,它就不会进行绘制。

为什么要这样工作呢?嗯,假设您想要更改视图的背景颜色、文本和文本颜色。如果您对视图进行的更改立即绘制,那将是3次昂贵的绘制操作。通过发送INVALIDATE消息,它允许将所有3次更改合并为1次绘制操作,提高了效率。这也是地球上几乎所有操作系统的工作原理。

所以基本上,永远不要暂停主线程。如果这样做,您将无法看到通过主线程循环传递的绘制和其他命令的结果,直到它重新启动。

另外,您提到的postDelayed示例并不在另一个线程上运行。默认情况下,Handler在创建它们的线程上运行,这很可能是主线程,除非您特意避免它。如果尝试在另一个线程上运行Handler并从该线程更改UI,您的应用将崩溃。Handler通过将消息发布到消息队列来工作。主线程的消息队列是由操作系统创建的,它与发送绘制的INVALIDATE消息使用的消息队列相同。

英文:

Here's how the android drawing system works:

When you change a view, it posts an invalidate message to the main thread. When that message is processed, the views onDraw is called. That onDraw creates a set of draw commands, which are then processed by another thread which draws them to the screen.

If the main thread sleeps, it will never return to the handler at the top of the event loop. If it doesn't do that, it never processes the invalidate message. So it never draws.

Why does it work like this? Well, let's say you wanted to change the background color of a view, the text, and the text color. If your change to the view drew immediately, that would be 3 expensive draws. By sending an INVALIDATE message it allows all 3 changes to be combined into 1 draw, boosting efficiency. This is also how just about every OS on the planet works.

So basically- don't pause the main thread, ever. If you do, you won't see the results of draws and other commands that go through the main thread looper until it restarts.

Also your example of postDelayed doesn't happen on another thread. Handlers by default run on the thread which creates them, which is likely the main thread unless you made a real effort to not have it be. If you try to run a handler on another thread and change the UI from that thread you will crash. Handlers work by posting a message to a message queue. The message queue for the main thread is created by the OS, and it's the same one used to send the invalidate messages for drawing

答案2

得分: 2

因为主线程使用了一个 LOOPER,在其中绘制和布局是其中之一的步骤。

当您更改视图属性时,它们要么触发 invalidate(请求重新绘制),要么触发 request layout(更新其大小和位置)。

这意味着对多个视图的更改基本上会被批处理并在下一个布局或绘制步骤中一起执行,以便 UI 线程不会因多次执行这些昂贵的操作而不堪重负。它还保持了许多与 UI 生命周期相关的回调和事件按照正确的顺序进行。

所以要记住:

1:通过调用 view.alpha = 0,然后是 view.alpha = 255,零变化从未执行,因为主 looper 从未在绘制步骤中看到它。在主线程上添加 Thread.sleep 实际上是一种违规,因为您会冻结您的应用程序 - looper 无法继续前进,因此不会执行任何绘制。

2:除了不是所有视图属性/方法都强制要求在主线程上调用之外,我没什么特别要说的。我会说主要影响视图层次结构的那些属性/方法会强制要求在主线程上调用。

英文:

Because main thread is using a LOOPER where drawing and layout are one of the steps.

When you alter view properties they will either trigger an invalidate (to request a redraw) or request layout (to update its size and positioning).

That means changes to multiple views at "once" essentially get batched and executed together during next layout or drawing step so UI thread does not get overwhelmed by performing those costly operations multiple times. It also keeps a lot of ui-lifecycle related callbacks and events in their proper order.

So with that in mind:

1: by calling view.alpha = 0 followed by view.alpha = 255 the zero change is never executed because main looper never sees it in the drawing step. You adding Thread.sleep on main thread is actually a violation because you freeze your app - looper cannot move forward and as such no drawing is executed.

2: there isn't much to say aside from the fact that not all view properties/methods are enforcing main thread calls. I'd say mostly those that affect view hierarchy do.

huangapple
  • 本文由 发表于 2023年6月9日 06:38:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/76436137.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定