Monday, March 30, 2009

Developer News

For no particular reason other than to celebrate this particular Monday, I wanted to update developers on two Android-related news items.

If you're a developer who will be in the San Francisco Bay Area at the end of May, I hope you'll join us at the 2009 Google I/O developer conference. You might have already seen the sessions we had listed for Android, but today I'm quite pleased to let you know that we've added a few more Android-related sessions. You can find the full list plus abstracts on the Google I/O site, but here are the titles:

  • Turbo-Charge Your UI: How to Make Your Android UI Fast and Efficient
  • Pixel-Perfect Code: How to Marry Interaction and Visual Design the Android Way
  • Supporting Multiple Devices with One Binary
  • Debugging Arts of the Ninja Masters
  • Coding for Life—Battery Life, That Is
  • Writing Real-Time Games for Android
  • Android Lightning Talks

These sessions don't even include the "fireside chat" with the Core Technical Team that we have planned. We're working on still more sessions too; keep an ear to the ground on this blog and the Google I/O site for the latest info. I'm pretty excited about how the Android sessions for Google I/O are coming together. I think it's going to be a great event, and I hope to meet many of you there.

The other topic I want to mention is that our partners at HTC have uploaded a new system image for Android Dev Phone 1 owners. This new image is mostly the same as the one we mentioned earlier this month, but adds voice dialing. Note that not all features will work correctly in all countries, such as voice dialing and Google Voice Search which currently only work well for US English. Additionally, there are some features that we aren't able to make available at all in some countries. For instance, this build can't currently include Google Latitude due to privacy standards in some regions. We'll always keep the ADP1 builds as full-featured as we can, but it's important to remember that these devices are primarily intended for development, and won't necessarily have all the features included on mainstream builds.

I hope this news is useful to you. As always, happy coding!

Android Layout Tricks #3: Optimize with stubs

Sharing and reusing layouts is very easy with Android thanks to the <include /> tag, sometimes even too easy and you might end up with user interfaces that contain a large number of views, some of which are rarely used. Thankfully, Android offers a very special widget called ViewStub, which brings you all the benefits of the <include /> without polluting your user interface with rarely used views.

A ViewStub is a dumb and lightweight view. It has no dimension, it does not draw anything and does not participate in the layout in any way. This means a ViewStub is very cheap to inflate and very cheap to keep in a view hierarchy. A ViewStub can be best described as a lazy include. The layout referenced by a ViewStub is inflated and added to the user interface only when you decide so.

The following screenshot comes from the Shelves application. The main purpose of the activity shown in the screenshot is to present the user with a browsable list of books:

The same activity is also used when the user adds or imports new books. During such an operation, Shelves shows extra bits of user interface. The screenshot below shows the progress bar and cancel button that appear at the bottom of the screen during an import:

Because importing books is not a common operation, at least when compared to browsing the list of books, the import panel is originally represented by a ViewStub:

When the user initiates the import process, the ViewStub is inflated and replaced by the content of the layout file it references:

To use a ViewStub all you need is to specify an android:id attribute, to later inflate the stub, and an android:layout attribute, to reference what layout file to include and inflate. A stub lets you use a third attribute, android:inflatedId, which can be used to override the id of the root of the included file. Finally, the layout parameters specified on the stub will be applied to the roof of the included layout. Here is an example:

<ViewStub
android:id="@+id/stub_import"
android:inflatedId="@+id/panel_import"

android:layout="@layout/progress_overlay"

android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />

When you are ready to inflate the stub, simply invoke the inflate() method. You can also simply change the visibility of the stub to VISIBLE or INVISIBLE and the stub will inflate. Note however that the inflate() method has the benefit of returning the root View of the inflate layout:

((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
// or
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

It is very important to remember that after the stub is inflated, the stub is removed from the view hierarchy. As such, it is unnecessary to keep a long-lived reference, for instance in an class instance field, to a ViewStub.

A ViewStub is a great compromise between ease of programming and efficiency. Instead of inflating views manually and adding them at runtime to your view hierarchy, simply use a ViewStub. It's cheap and easy. The only drawback of ViewStub is that it currently does not support the <merge /> tag.

Happy coding!

Monday, March 9, 2009

Software Update Available for the Android Developer Phone

Back in December, the Android Dev Phone 1 (ADP1) went on sale, giving developers access to unlocked hardware for their work. A few weeks ago, consumers with retail devices received an over the air update with the 1.1 release of Android. I know that many developers will be pleased to hear that today, our colleagues at HTC have released a 1.1 version of Android for the Android Dev Phone which you can install on your device. If you have questions about the process of updating your device, you can ask the mailing list we've set up for such questions.

This new system image is fully compatible with Android 1.1. To see a list of everything that's new, you can review the notes from the 1.1_r1 SDK. This update also includes support for searching by voice, and priced apps in the Android Market.

Some developers have asked about the support for copy-protected apps on developer devices, and indeed there is a limitation you should be aware of. Many developers are concerned about the unauthorized redistribution of their applications, so they make use of the copy-protection feature (known as "forward locking") which prevents applications from being copied off devices. However, developer phones like the ADP1 allow for unrestricted access to the device's contents, making it impossible to enforce copy protection. As a result, the Market application on such devices is not able to access copy protected apps, whether they are free or paid. If you choose to add copy protection when you upload your application to the Android Market, then you won't be able to test it on the ADP1's Android Market client. Your application will always be accessible to users who have standard configurations though, and if your application (whether it is free or paid) is not copy-protected it will appear on all devices, including developer configurations.

If you own an Android Developer Phone, I definitely suggest you take advantage of this update. There's lots of good stuff in there, and the new software is backward compatible with Android 1.0, too. The original 1.0 system image is also now available, you need to downgrade for any reason. Happy coding!

Thursday, March 5, 2009

Window Backgrounds & UI Speed

Some Android applications require to squeeze every bit of performance out of the UI toolkit and there are many ways to do so. In this article, you will discover how to speed up the drawing and the perceived startup time of your activities. Both these techniques rely on a single feature, the window's background drawable.

The term window background is a bit misleading however. When you setup your user interface by calling setContentView() on an Activity, Android adds your views to the Activity's window. The window however does not contain only your views, but a few others created for you. The most important one is, in the current implementation used on the T-Mobile G1, the DecorView, highlighted in the view hierarchy below:

A typical Android view hierarchy

The DecorView is the view that actually holds the window's background drawable. Calling getWindow().setBackgroundDrawable() from your Activity changes the background of the window by changing the DecorView's background drawable. As mentioned before, this setup is very specific to the current implementation of Android and can change in a future version or even on another device.

If you are using the standard Android themes, a default background drawable is set on your activities. The standard theme currently used on the T-Mobile G1 uses for instance a ColorDrawable. For most applications, this background drawable works just fine and can be left alone. It can however impacts your application's drawing performance. Let's take the example of an application that always draws a full screen opaque picture:

An opaque user interface doesn't need a window background

You can see on this screenshot that the window's background is invisible, entirely covered by an ImageView. This application is setup to redraw as fast as it can and draws at about 44 frames per second, or 22 milliseconds per frame (note: the number of frames per second used in this article were obtained on a T-Mobile G1 with my finger on the screen so as to reduce the drawing speed which would otherwise be capped at 60 fps.) An easy way to make such an application draw faster is to remove the background drawable. Since the user interface is entirely opaque, drawing the background is simply wasteful. Removing the background improves the performance quite nicely:

Remove the background for faster drawing

In this new version of the application, the drawing speed went up to 51 frames per second, or 19 milliseconds per frame. The difference of 3 milliseconds per is easily explained by the speed of the memory bus on the T-Mobile G1: it is exactly the time it takes to move the equivalent of a screenful of pixels on the bus. The difference could be even greater if the default background was using a more expensive drawable.

Removing the window's background can be achieved very easily by using a custom theme. To do so, first create a file called res/values/theme.xml containing the following:

<resources>
<style name="Theme.NoBackground" parent="android:Theme">
<item name="android:windowBackground">@null</item>
</style>
</resources>

You then need to apply the theme to your activity by adding the attribute android:theme="@style/Theme.NoBackground" to your <activity /> or <application /> tag. This trick comes in very handy for any app that uses a MapView, a WebView or any other full screen opaque view.

Opaque views and Android: this optimization is currently necessary because the Android UI toolkit is not smart enough to prevent the drawing of views hidden by opaque children. The main reason why this optimization was not implemented is simply because there are usually very few opaque views in Android applications. This is however something that I definitely plan on implementing as soon as possible and I can only apologize for not having been able to do this earlier.

Using a theme to change the window's background is also a fantastic way to improve the perceived startup performance of some of your activities. This particular trick can only be applied to activities that use a custom background, like a texture or a logo. The Shelves application is a good example:

Textured backgrounds are good candidates for window's background

If this application simply set the wooden background in the XML layout or in onCreate() the user would see the application startup with the default theme and its dark background. The wooden texture would only appear after the inflation of the content view and the first layout/drawing pass. This causes a jarring effect and gives the user the impression that the application takes time to load (which can actually be the case.) Instead, the application defines the wooden background in a theme, picked up by the system as soon as the application starts. The user never sees the default theme and gets the impression that the application is up and running right away. To limit the memory and disk usage, the background is a tiled texture defined in res/drawable/background_shelf.xml:

<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/shelf_panel"
android:tileMode="repeat" />

This drawable is simply referenced by the theme:

<resources>
<style name="Theme.Shelves" parent="android:Theme">
<item name="android:windowBackground">@drawable/background_shelf</item>
<item name="android:windowNoTitle">true</item>
</style>
</resources>

The same exact trick is used in the Google Maps application that ships with the T-Mobile G1. When the application is launched, the user immediately sees the loading tiles of MapView. This is only a trick, the theme is simply using a tiled background that looks exactly like the loading tiles of MapView.

Sometimes the best tricks are also the simplest so the next time you create an activity with an opaque UI or a custom background, remember to change the window's background.

Download the source code of the first example.

Download the source code of Shelves.

Tuesday, March 3, 2009

Android Layout Tricks #3: Optimize by merging

In the previous installment of Android Layout Tricks, I showed you how to use the <include /> tag in XML layout to reuse and share your layout code. I also mentioned the <merge /> and it's now time to learn how to use it.

The <merge /> was created for the purpose of optimizing Android layouts by reducing the number of levels in view trees. It's easier to understand the problem this tag solves by looking at an example. The following XML layout declares a layout that shows an image with its title on top of it. The structure is fairly simple; a FrameLayout is used to stack a TextView on top of an ImageView:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"

android:scaleType="center"
android:src="@drawable/golden_gate" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dip"
android:layout_gravity="center_horizontal|bottom"

android:padding="12dip"

android:background="#AA000000"
android:textColor="#ffffffff"

android:text="Golden Gate" />

</FrameLayout>

This layout renders nicely as we expected and nothing seems wrong with this layout:



A FrameLayout is used to overlay a title on top of an image


Things get more interesting when you inspect the result with HierarchyViewer. If you look closely at the resulting tree you will notice that the FrameLayout defined in our XML file (highlighted in blue below) is the sole child of another FrameLayout:

A layout with only one child of same dimensions can be removed

Since our FrameLayout has the same dimension as its parent, by the virtue of using the fill_parent constraints, and does not define any background, extra padding or a gravity, it is totally useless. We only made the UI more complex for no good reason. But how could we get rid of this FrameLayout? After all, XML documents require a root tag and tags in XML layouts always represent view instances.

That's where the <merge /> tag comes in handy. When the LayoutInflater encounters this tag, it skips it and adds the <merge /> children to the <merge /> parent. Confused? Let's rewrite our previous XML layout by replacing the FrameLayout with <merge />:

<merge xmlns:android="http://schemas.android.com/apk/res/android">

<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"

android:scaleType="center"
android:src="@drawable/golden_gate" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dip"
android:layout_gravity="center_horizontal|bottom"

android:padding="12dip"

android:background="#AA000000"
android:textColor="#ffffffff"

android:text="Golden Gate" />

</merge>

With this new version, both the TextView and the ImageView will be added directly to the top-level FrameLayout. The result will be visually the same but the view hierarchy is simpler:

Optimized view hierarchy using the merge tag

Obviously, using <merge /> works in this case because the parent of an activity's content view is always a FrameLayout. You could not apply this trick if your layout was using a LinearLayout as its root tag for instance. The <merge /> can be useful in other situations though. For instance, it works perfectly when combined with the <include /> tag. You can also use <merge /> when you create a custom composite view. Let's see how we can use this tag to create a new view called OkCancelBar which simply shows two buttons with customizable labels. You can also download the complete source code of this example. Here is the XML used to display this custom view on top of an image:

<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:okCancelBar="http://schemas.android.com/apk/res/com.example.android.merge">

<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"

android:scaleType="center"
android:src="@drawable/golden_gate" />

<com.example.android.merge.OkCancelBar
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"

android:paddingTop="8dip"
android:gravity="center_horizontal"

android:background="#AA000000"

okCancelBar:okLabel="Save"
okCancelBar:cancelLabel="Don't save" />

</merge>

This new layout produces the following result on a device:

Creating a custom view with the merge tag

The source code of OkCancelBar is very simple because the two buttons are defined in an external XML file, loaded using a LayoutInflate. As you can see in the following snippet, the XML layout R.layout.okcancelbar is inflated with the OkCancelBar as the parent:

public class OkCancelBar extends LinearLayout {
public OkCancelBar(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(HORIZONTAL);
setGravity(Gravity.CENTER);
setWeightSum(1.0f);

LayoutInflater.from(context).inflate(R.layout.okcancelbar, this, true);

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.OkCancelBar, 0, 0);

String text = array.getString(R.styleable.OkCancelBar_okLabel);
if (text == null) text = "Ok";
((Button) findViewById(R.id.okcancelbar_ok)).setText(text);

text = array.getString(R.styleable.OkCancelBar_cancelLabel);
if (text == null) text = "Cancel";
((Button) findViewById(R.id.okcancelbar_cancel)).setText(text);

array.recycle();
}
}

The two buttons are defined in the following XML layout. As you can see, we use the <merge /> tag to add the two buttons directly to the OkCancelBar. Each button is included from the same external XML layout file to make them easier to maintain; we simply override their id:

<merge xmlns:android="http://schemas.android.com/apk/res/android">
<include
layout="@layout/okcancelbar_button"
android:id="@+id/okcancelbar_ok" />

<include
layout="@layout/okcancelbar_button"
android:id="@+id/okcancelbar_cancel" />
</merge>

We have created a flexible and easy to maintain custom view that generates an efficient view hierarchy:

The resulting hierarchy is simple and efficient

The <merge /> tag is extremely useful and can do wonders in your code. However, it suffers from a couple of limitation:

  • <merge /> can only be used as the root tag of an XML layout

  • When inflating a layout starting with a <merge />, you must specify a parent ViewGroup and you must set attachToRoot to true (see the documentation of the inflate() method)

In the next installment of Android Layout Tricks you will learn about ViewStub, a powerful variation of <include /> that can help you further optimize your layouts without sacrificing features.

Download the complete source code of this example.