0

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?
user3504410
  • 173
  • 1
  • 13

2 Answers2

3

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

Gabe Sechan
  • 90,003
  • 9
  • 87
  • 127
  • I seem to be able to change the opacity of a view already added to the windowNanager from a background thread with no issue, although trying to add/remove the view from the windowManager does cause a crash. Is there any benefit of having a background thread dispatch both parts of the visibility change over using postDelayed? – user3504410 Jun 09 '23 at 18:00
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.

Pawel
  • 15,548
  • 3
  • 36
  • 36