September 2, 2014

Flattening The Stack

merge2In Dan Morrill’s Future-Proofing Your Apps post yesterday on the Android Developers blog, he wrote:

Due to changes in the View rendering infrastructure, unreasonably deep (more than 10 or so) or broad (more than 30 total) View hierarchies in layouts are now likely to cause crashes. This was always a risk for excessively complex layouts, but you can think of Android 1.5 as being better than 1.1 at exposing this problem. Most developers won’t need to worry about this, but if your app has very complicated layouts, you’ll need to put it on a diet. You can simplify your layouts using the more advanced layout classes like FrameLayout and TableLayout.

If, as you upgrade your app to Android 1.5, you find yourself getting StackOverflowExceptions, that suggests that perhaps your view hierarchy is a bit too deep. Even if your UI is not that complicated in your eyes, you can get to double-digit depth with surprising ease. For example, in 1.1r1, the Dialtacts activity (dialer, contacts, call log, favorites) runs at least a 12-deep view hierarchy.

The issue stems from the fact that Android widgets are intelligent objects, and laying out a UI involves calling a bunch of methods on those objects. In the case of ViewGroup subclasses, such as most layouts, these in turn call a bunch of methods on their child views. Eventually, you run out of stack space, and Android runs with minimal stack on the main UI threads.

If you are looking for ways to trim the fat, here are some suggestions:

1. Use RelativeLayout over nested LinearLayouts. Personally, I love the simple box model that LinearLayout offers, akin to the ones used in Flex and XUL. However, given Android’s implementation, nested LinearLayouts are more expensive, particularly in terms of stack depth. A well-crafted RelativeLayout can give you the same look with less footprint, as Romain Guy explained in February 2009.

2. Use <merge>. Sometimes, you wind up putting another layer in your view hierarchy just to satisfy XML’s need for a single root element in its files. Android offers the <merge> element to use as a placeholder in this case, so you can perhaps skip an extraneous LinearLayout or FrameLayout. Romain Guy covered this topic in March 2009.

3. Eliminate or replace tabs. Tabs add about three levels to your view hierarchy just for being there: one for the LinearLayout to place your TabHost and TabWidget, one for the TabHost/TabWidget itself, and one for the tabs (TextView and ImageView). That may be more than your application can handle, if you have complex tab contents. Consider splitting your tabs out into separate activities, or using some other means to allow people to move between “screens” of content (e.g., ViewSwitcher).

4. Simplify the UI. If you look at many of the built-in applications, you will find that their GUIs are relatively sparse: few buttons or fields except on dedicated data entry forms. Avoiding overly-deep view hierarchies is one of the design criteria that perhaps led to those applications’ UI layouts. While it may be neat to offer a bunch of buttons or whatnot to go along with your list of things to manipulate, the layout tree necessary to put all that stuff on-screen may push you past your stack budget, particularly if you are also using tabs. Consider moving some buttons to option or context menus, or pushing data entry into dialogs or separate activities, to reduce visual clutter and the associated view-hierarchy depth at the same time.