Thursday, May 21, 2015

Managing Activity in Task Stacks

Android support multi-tasking view Tasks. And the most important element of a Task is a Back Stack of Activity. See this Android guide for an introduction.

The difficulty of mastering Android Tasks, so as to manipulate them and to control how a new Activity is launched into them, lies in the plethora of options and flags that affect how a new Activity is launched and how it interacts with existing tasks. The key to success is a good understanding of those flags. A good grasp of these concept and mechanism opens up a whole new world of possibilities that can make your app more usable and intuitive, rather than always launching an Activity in the standard way. For me, I found these following articles very useful:

The first article is newer and also more comprehensive in terms of corner cases and pitfalls.

Also, dumping the current Task stacks is very handy for debugging. To do so, use the following command:
adb shell dumpsys activity activities

Tuesday, May 5, 2015

Order of Touch Event Dispatch in Android

Until very recently, I couldn’t say that I well understand Android’s UI event handling. Chances are that one won’t need to understand the very details to meet daily needs.

However, I eventually encountered a case of “one ScrollView inside another ScrollView” which forces me to figure out the arcane relationship between ViewGroup.onInterceptTouchEvent() and ViewGroup.requestDisallowInterceptTouchEvent(). Fortunately, this is a question many other has already asked. Thanks to technical blog post such as this one (and sincerely the Android documentation about this is not bad at all), I don’t need to learn it from trials and falls.

There is one nuisance though, that has confused me for a while, the answer of which isn’t in a readily available post from StackOverflow. That is what is the order in which Android dispatch touch events, when there are overlapping views. Overlapping is the key word here and this is the topic of today’s post.

This Simple Case

Throughout our discussion, let’s ignore the onInterceptTouchEvent() calls and focus only on onTouchEvent() calls, i.e. focusing on the event bubbling phase in web term.

When there is no overlapping views, touch dispatching is straightforward. Suppose we have a view hierarchy as follows:
then the order that onTouchEvent() is called is: C => B => A. That is, the target View (i.e. the innermost View that the touch lands on) received the call to onTouchEvent() first, followed by its parent, so on and so forth.

The Overlapping Case

Now consider the simplest case of View overlapping. Suppose there are two leaf Views C and D which has an overlapping region and that the touch event lands on that region (indicated by red circle) :
It’s not hard to guess that the order of onTouchEvent() call is: (C, D) => B => A. I am using the notion (C, D) to indicate that the order between C and D could vary. For all framework ViewGroup, such as FrameLayout, the order is determined by the child order. For instance, if C is the first child View of B and D is the second child View of B, then D’s onTouchEvent() is called first. This matches the drawing behavior that child View with lower index is drawn later, thus on top of, child View with higher index. Therefore, the child View that is drawn on top is also the one that handle the touch event first. One can redefine the drawing order via ViewGroup.getChildDrawingOrder(), which will affect the touch event dispatching order as well (if dispatchTouchEvent() is not overridden).

The General Case

To generalize, rather than giving result first, I’ll resort to another example, as shown below:
The View hierarchy is provide below for clarity:
In the View hierarchy above, child View to the left has lower index.

The touch location, which is indicated by the red circle again, is the overlapping region of all the child Views C, D, F and G. To verify the order of onTouchEvent() calls, I made a sample app with the above layout and attached a OnTouchListener to each of the Views that output some log. The app looks like the following:
Screenshot_2015-05-06-00-27-41.png

Here is the log I got from touching the region indicated by the red circle:
V/OVERLAPPING_VIEW_TEST﹕ ----View G.onTouchEvent()
V/OVERLAPPING_VIEW_TEST﹕ ----View F.onTouchEvent()
V/OVERLAPPING_VIEW_TEST﹕ --ViewGroup E.onTouchEvent()
V/OVERLAPPING_VIEW_TEST﹕ ----View D.onTouchEvent()
V/OVERLAPPING_VIEW_TEST﹕ ----View C.onTouchEvent()
V/OVERLAPPING_VIEW_TEST﹕ --ViewGroup B.onTouchEvent()
V/OVERLAPPING_VIEW_TEST﹕ ViewGroup A.onTouchEvent()

Here is the result of touching the region indicated by the green circle:
V/OVERLAPPING_VIEW_TEST﹕ ----View D.onTouchEvent()
V/OVERLAPPING_VIEW_TEST﹕ ----View C.onTouchEvent()
V/OVERLAPPING_VIEW_TEST﹕ --ViewGroup B.onTouchEvent()
V/OVERLAPPING_VIEW_TEST﹕ ViewGroup A.onTouchEvent()

And here is the result for the blue circle:
V/OVERLAPPING_VIEW_TEST﹕ ----View F.onTouchEvent()
V/OVERLAPPING_VIEW_TEST﹕ --ViewGroup E.onTouchEvent()
V/OVERLAPPING_VIEW_TEST﹕ ----View C.onTouchEvent()
V/OVERLAPPING_VIEW_TEST﹕ --ViewGroup B.onTouchEvent()
V/OVERLAPPING_VIEW_TEST﹕ ViewGroup A.onTouchEvent()

To summarize, here are the steps to determine the order of onTouchEvent() calls:
  1. Draw the View hierarchy tree.
  2. Remove from the tree any node that the touch location is not on.
  3. Do a post-order traversal where child View with higher index takes precedence.

Of course, any participate can return true from onTouchEvent() to stop the dispatching.

Sunday, May 3, 2015

Debugging Android UI Performance via MessageQueue

Last week at work, I investigated a performance regression in our Android app (a document typesetting application). Apparently, users has experience slowness when inputing text in our app.

My first attempt to find the cause of the performance regression was using method tracing from Dalvik Debug Monitor Server (DDMS). In particular, I started tracing when the onKeyDown() method is called, and ended tracing when the call finishes. However, the total time consumption and the time distribution of the trace result looked almost identical to the result of a version of our app before the regression.

It occurred to me that the computation that slows down the key handling must be "posted" in MessageQueue and run in separate message loops. But what do I mean by that?

An Android app by default runs in a single main thread called UI thread. The UI thread is managed by a MessageQueue and a Looper. The message queue is the data structure to contain various tasks (called message) the app needs to execute. An example of a message could be redrawing the UI, handling key event, etc. The Looper, on the other hand, is simply a infinite loop that dispatch each message from the MessageQueue to a suitable Handler. The simplest form of a message is a Runnable and one can post such a Runnable to the MessageQueue to be executed later, by calling Handler.post() or Handler.postDelayed().

Even though the key handling method is not slowing our app down, it was very likely that some Runnables are posted during key handling. Since those posted Runnables will executes immediately after the key handling, they will occupy the UI thread and prevent it from handling the next key event and freezes the UI. This is when the users experience lag.

In order to find out the culprit Runnables, I used the Looper.dump() method. This method will dump all the queued message currently in the MessageQueue. By comparing the dump before and after onKeyDown() is called, it's easy to tell what are the new messages (Runnables) posted by onKeyDown(), as in the following snippet:

From the dump, I finally found out the regression cause and solved the problem. As a side note, one can also use Looper.setMessageLogging() method to log when a message is dispatched and finished. This was less useful in my case since Android system post many messages to the UI thread and it's hard to tell which ones are related to key handling.

Useful Resources about Android's MessageQueue and Threading.