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.

Saturday, January 31, 2015

LayoutParams of Inflated View

One day at work, I encountered a "bug" that costed me 3 hours. It turns out that the issue had was due to my misunderstanding of some details regarding inflating view hierarchy programmatically. The problem I has was that the layout parameter for the out-most view in the inflated hierarchy doesn't not obey what I specified in the xml file. In this post, I am going to record what the issue was and how I solved it.

Here is a sample activity that I will use to reproduce the issue. There are two layout files, activity.xml is used as the content of the activity, content.xml is the view inflated in the onCreate() method and added to the acitivity's view hierarchy programmatically.




The resulting app looks like the following:


If you didn't spot the mistake in the above code like me at the first place, then you'll be surprised that the FrameLayout with id my_content and red background does not respect the 200px * 200px dimension specified in the layout file.

It turns out that the LayoutParams of the out most view in the inflated view hierarchy is ignored. And this is for good reason! Since different subclasses of ViewGroup has different attributes in their corresponding LayoutParams, there is no guarantee that the layout parameters specified for the top level view in the layout file makes sense to the parent view, which is unknown, to which it will be added. One might argue that layout_width and layout_height shall be understood by any ViewGroup, but Android just decided to make no special case here and ignore layout_width and layout_height together with other parameters.

If the layout parameters of the out most view is ignore, then what will be used when it is added to a parent view? There has to be some layout parameters in place, right? It turns out that ViewGroup owns the following method:
protected LayoutParams generateDefaultLayoutParams()
Which shall be implemented to provide the default layout parameter for a child view. In the case of FrameLayout, which our inflated view hierarchy is added to, this simply set both layout_width and layout_height to MATCH_PARENT, thus the result we see in the above screenshot.

Then how to set the layout parameter other than the default one? There are three ways that I found:

  1. Set layout parameter explicitly before adding the view hierarchy to its parent. This is not ideal since it's undesirable to hard-code dimensions rather than specify them in xml file.
  2. Use the version of addView that asks for layout parameters.
  3. This is the most desirable way. When inflating the view, specify the parent view. The parent view is used to ensure that the layout parameter is applicable to it. For the case of layout_width or layout_height, you don't even need to use the real parent view that the inflated view hierarchy will be added to. In fact, any instance of ViewGroup will do, and one can set the third parameter to false so that the inflated view is not added during the call. This technique is especially useful since sometimes you don't have access to the parent view. One example is when specifying the content view for a PopupWindow. The PopupWindow code adds the content view to its parent, which you don't control.