androidkotlinandroid-viewandroid-threading

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%

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?

Solution

  • 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