Most apps draw once per vsync. Therefore, apps can only respond to 1 input event per frame. If multiple input events come in during the period of 1 vsync, it would be wasteful to deliver them all at once to the app. For this reason, input events are batched to only deliver 1 event per frame to the app.
The batching process works in the following manner:
InputDispatcher
sends an event to the appLooper
is notified about the available event.handleEvent
callback is executed. Events are read from fd.InputConsumer::hasPendingBatch
returns true. No event is sent to the app at this point.Choreographer
to consume it a short time before the next frameLet's discuss the specifics of some of these steps.
handleEvent
callbackThe app is notified about the available event via the Looper
callback handleEvent
. When the app‘s input socket becomes readable (e.g., it has unread events), the looper will execute handleEvent
. At this point, the app is expected to read in the events that have come in to the socket. The function handleEvent
will continue to trigger as long as there are unread events in the socket. Thus, the app could choose to read events 1 at a time, or all at once. If there are no more events in the app’s socket, handleEvent will no longer execute.
Even though it is perfectly valid for the app to read events 1 at a time, it is more efficient to read them all at once. Therefore, whenever the events are available, the app will try to completely drain the socket.
To consume the events inside handleEvent
, the app calls InputConsumer::consume(.., consumeBatches=false, frameTime=-1, ..)
. That is, when handleEvent
runs, there is no information about the upcoming frameTime, and we dont want to consume the batches because there may be other events that come in before the ‘consume batched input’ runnable runs.
If a batched event comes in at this point (typically, any MOVE event that has source = TOUCHSCREEN), the consume
function above would actually return a NULL
event with status WOULD_BLOCK
. When this happens, the caller (NativeInputEventReceiver
) is responsible for checking whether InputConsumer::hasPendingBatch
is set to true. If so, the caller is responsible for scheduling a runnable to consume these batched events.
In the previous section, we learned that the app can read events inside the handleEvent
callback. The other time when the app reads events is when the ‘consume batched input’ runnable is executed. This runnable is scheduled via the Choreographer by requesting a CALLBACK_INPUT
event.
Before the batched events are consumed, the socket is drained once again. This is an optimization.
To consume the events inside ‘consume batched input’ runnable, the app calls InputConsumer::consume(.., consumeBatches=true, frameTime=<valid frame time>, ..)
. At this point, the consume
function will return all batched events up to the frameTime
point. There may be batched events remaining.
Some of the behaviours above should be highlighted, because they may be unexpected.
Even if events have been read by InputConsumer
, consume
will return NULL
event with status WOULD_BLOCK
if those events caused a new batch to be started.
Events are read from the fd outside of the regular handleEvent
case, during batched consumption.
The function handleEvent
will always execute as long as there are unread events in the fd
The consume
function is called in 1 of 2 possible ways:
consumeBatches=false, frameTime=-1
consumeBatches=true, frameTime=<valid time>
I.e., it is never called with consumeBatches=true, frameTime=-1
.