Monday, December 31, 2007

Activity invocation performance

Intent-based programming is a great thing and the Android tutorials are encouraging programmers to take advantage of it. Behind the scenes, however, a complex choreography occurs to implement it so I was curious, what the limits of the Android intent system may be. I extended the example program in the Playing with Intents blog entry so that I can measure the performance of the intent invocation.

The principle of the measurement is simple. Instead of invoking the target activity only once, it will be invoked repeatedly to average out measurement error. I ran the measurements on the emulator whose performance compared to the target mobile device is unknown therefore I compared the activity invocation performance to plain Java method invocations, also running on the emulator. There is a fourth button therefore to launch the plain Java invocation measurement.

Let's see now, how the Playing with Intents example program was extended so that it can make this measurement. The modifications are not all that trivial and as I am not (yet :-)) an experienced Android programmer, I ran into common mistakes too. The measurement program can be found here. Similarly to the Playing with Intents version, there are actually two applications that need to be compiled and deployed separately, see the Playing with Intents blog entry for details.

I innocently started by organizing a loop around the intent invocation in the Button's onClick method. This did not work at all and the reason is the relatively complex lifecycle of the Android applications. The following log demonstrates, how complex an activity invocation really is. You can generate this log by setting the debug variable in IntentMeasSend's aexp.intentmeassend.IntentSender class to true. Then you can follow the events in the log by launching the fantastic adb logcat tool. Note the usage of android.util.Log class that provides logging functions.

Let's see the log now.

D/INTENTMEASSEND( 547): onStart
D/INTENTMEASSEND( 547): onResume
I/ActivityManager( 461): Displayed activity {aexp.intentmeassend/aexp.intentmeassend.IntentSender}: 685 ms
I/ActivityManager( 461): Done launching {aexp.intentmeassend/aexp.intentmeassend.IntentSender}: 694 ms
D/ActivityManager( 461): Stopping: HistoryRecord{401be290 {com.google.android.home/com.google.android.home.AllApps}}
D/INTENTMEASSEND( 547): Before starting subactivity
I/ActivityManager( 461): Starting activity: Intent { comp={aexp.intentmeassend/aexp.intentmeassend.IntentReceiver} extras=android.os.Bundle@4018e0e0 }
D/INTENTMEASSEND( 547): After starting subactivity
D/INTENTMEASSEND( 547): onFreeze
D/INTENTMEASSEND( 547): onPause
D/ActivityThread( 547): Performing launch of ActivityRecord{40047d38 {aexp.intentmeassend/aexp.intentmeassend.IntentReceiver}}
D/INTENTRECEIVER(INT)( 547): Activity invoked
D/INTENTRECEIVER(INT)( 547): Activity done
D/ActivityManager( 461): Resuming: HistoryRecord{40110618 {aexp.intentmeassend/aexp.intentmeassend.IntentSender}}
D/INTENTMEASSEND( 547): ACTIVITY_INVOKE event
D/INTENTMEASSEND( 547): onResume
I/ActivityManager( 461): Removing activity: Intent { comp={aexp.intentmeassend/aexp.intentmeassend.IntentReceiver} extras=android.os.Bundle@4018e0e0 }

Some of the log entries are marked as bold by me to emphasize key events. Activity invocation is an asynchronous process. It involves pausing the invoking activity, invoking the target activity, delivering the onActivityResult event to the invoking activity then resuming the invoking activity. This complex dance of events simply does not work if the target activity is invoked in a loop repeatedly in an event handler (onClick) because there are no further event deliveries before onClick finishes. It was necessary to reorganize the program so that the next activity invocation occurs only in the onActivityResult handler.

Now the results. I emphasize one more time that the actual numbers are not very much important, all the more so their proportions to the plain Java invocation running on the same emulator.

Explicit intent addressing, internal activity: about 23 msec/activity invocation
Explicit intent addressing, external activity: about 30 msec/activity invocation
Implicit intent addressing, external activity: about 30 msec/activity invocation
Direct Java call: about 3 microsec/method call

(Note that the measurement program does 100 activity invocations per measurement vs. 100000 Java method invocations so the measured times have to be divided by these numbers).

The measurement does demonstrate that invoking an activity is a relatively costly procedure. There is no problem if the activity invocation is related to some user action but one may run into performance problems if the activity invocation facility is used too frequently.

Thursday, December 27, 2007

Playing with Intents

Intent-based programming is one of the most distinctive features of the Android platform. The entire application space effectively consists of components (called Activities in Android parlance) and messages among components (called Intents). The Android application model is therefore a simple service-oriented architecture which is indeed an interesting approach.

Intents can used in many ways to invoke another Activity. The tutorial is not very explicit on the two main kinds of Intents so the I had to discover them myself in the documentation of the Intent class.
  • Explicit intent targets a particular class that has been declared intent receiver in the AndroidManifest.xml
  • Implicit intent targets an intent receiver with particular characteristics (like a particular action)
Intent-based programming is interesting that's why I decided to play around with it. I had very unpleasant experiences with the Blogger engine rendering preformatted text like program code (in addition, the engine swallowed inexplicably part of an XML sample document that I tried to insert into the post) so I decided to upload the project bundles onto a download page so that I can copy into the post only the relevant code fragments. Note that sdk-folder and android-tools properties in the build.xml files need to be updated so that they reflect the location of your Android SDK installation directories.

I tried out three setups in the simple test program (that actually consists of two Android applications).

  • Explicit intent addressing with the invoked activity internal to the application (exp/int).
  • Explicit intent addressing with the invoked activity external to the application (IntentSender application invokes IntentReceiver application) (exp/ext).
  • Implicit intent addressing with external activity invocation (imp/ext). The invoked activity is again in the IntentReceiver application.
Download the project bundle from here, compile and install both IntentSender and IntentReceiver applications (they are in the intentsender and intentreceiver directories, respectively. Both applications need to be compiled with ant and need to be installed on the emulator as described in an earlier post). You can launch IntentSender and try out the activity invocations by pressing the appropriate buttons.

The implementation of this simple application did have its adventures. :-) The tutorial uses a simple form of explicit intent creation.

Intent i = new Intent(this, NoteEdit.class);

This was not usable for me because in the exp/ext case the target activity class was not located within the application. The solution seemed to be simple and relied on the Intent class' setClassName( pkgName, className ) method. Who could have thought that the right form of parametrization requires full path in className too (after having received the package part in the pkgName parameter)? This took me something like an hour wasted and was resolved by finding out, how the tutorial version of explicit invocation works.

The critical piece of code is the following:

Intent i = new Intent();
i.setClassName( "aexp.intentreceiver", "aexp.intentreceiver.IntentReceiver" );
i.putExtra( "i1", new Integer( 4 ) );
i.putExtra( "i2", new Integer( 5 ) );
startSubActivity(i, ACTIVITY_INVOKE);


The target activity needs to be declared in the AndroidManifest.xml. IntentReceiver class in IntentReceiver application happens to be the app's main intent receiver therefore no additional declaration is necessary. The internal IntentReceiver in IntentSender needs to be declared in IntentSender's AndroidManifest.xml.

[activity class=".IntentReceiver"]

(of course, this is XML with <> characters, I just can't get it through the #&@@! blog engine).

After getting through the explicit addressing's arcane package name/class name convention, I went after the implicit addressing which seemed similarly simple. In this case, there is an extra level of indirection between the invoker and invoked activity. The intent does not carry the target class, it carries a set of information that is used by the system to identify the target activity. In my example, I used only the intent action that I set to an action string I made up myself (aexp.intentsender.add).

Invoking code is simple:

Intent i = new Intent();
i.setAction( "aexp.intentsender.add" );
i.putExtra( "i1", new Integer( 5 ) );
i.putExtra( "i2", new Integer( 6 ) );
startSubActivity(i, ACTIVITY_INVOKE);


The intent receiver is identified according to the intent filter declaration in IntentReceiver's AndroidManifest.xml.

[intent-filter]
[action android:value="aexp.intentsender.add"/]
[category android:value="android.intent.category.DEFAULT"/]
[/intent-filter]



There is one point here to note that costed me again some half an hour wasted. :-) Although I don't use any categories, Intent's constructor creates one category by default, the Intent.DEFAULT_CATEGORY whose string format is android.intent.category.DEFAULT. I did not include the category originally into the AndroidManifest.xml and that's why the intent resolution was not succesful. This problem was rectified using the fantastic adb logcat command.

Guess, what goes into the log if there is an action match whose category does not match?

W/IntentResolver( 461): resolveIntent failed: found match, but none with Intent
.DEFAULT_CATEGORY

Thanks, emulator developers, this log message was really helpful!

Sunday, December 23, 2007

XML and programmatic layout

The tutorial taught me how to use XML files (under the layout directory) to define screen layouts. I was curious about the relationship of the XML layout and the Java classes like android.widget.LinearLayout. To verify it, I tried to recreate programmatically the layout of the skeleton project.

Update: download the project bundle from here.

The XML layout file of the skeleton project declares that the application's main view is controlled by a LinearLayout that occupies the entire available screen estate (fill_parent for both the layout_width and layout_height). The LinearLayout is vertical (elements are placed vertically). The layout contains just one text element that fills the entire available width (layout_width=fill_parent) but occupies just the height needed for the content (layout_height=wrap_content).

This XML file is inflated into instances of Java objects. The following code is equivalent in functionality with the XML file (except for the text of the TextView instance).

public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
TextView t = new TextView( this );
t.setText( "Hello, world, programmatic layout" );
LinearLayout.LayoutParams tlp =
new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT );
LinearLayout l = new LinearLayout( this );
l.setOrientation( LinearLayout.VERTICAL );
t.setPreferredWidth( LayoutParams.FILL_PARENT );
t.setPreferredHeight( LayoutParams.FILL_PARENT );
l.addView( t, tlp );
setContentView( l );
}

While playing with this example, I ran into an application error.



This error was a great opportunity to try out the adb logcat command which dumps the emulator's log. By browsing the blog, I was able to find the offending exception (excerpts):

E/AndroidRuntime( 634): at android.view.ViewGroup.addView(ViewGroup.java
:792)
E/AndroidRuntime( 634): at android.view.ViewGroup.addView(ViewGroup.java
:780)
E/AndroidRuntime( 634): at aexp.h2.H2.onCreate(H2.java:25)
E/AndroidRuntime( 634): at android.app.Instrumentation.callActivityOnCre
ate(Instrumentation.java:786)

My first steps with command-line development tools

As, I guess, most of the prospective Android developers, I started my Android experience by going through the Tutorial. This tutorial assumes Eclipse-based development environment. Many people love Eclipse but I am kind of old-fashioned and I prefer command line development tools. Somehow I feel myself more in control of what is happening during development. :-)

The documentation coming with Android is pretty exhaustive on command-line tools but I had to figure out myself how to use them because step-by-step tutorial similar to the Eclipse-based one is not available.

First, I created a root directory for the project then I created the project skeleton with the activityCreator script (I assume that the SDK was installed correctly so the SDK tool binaries are on the path).

C:\USERS\paller\android\helloworld>activityCreator aexp.helloworld.HelloWorld
This created the project skeleton into the current directory. The project skeleton is actually a functional HelloWorld application that can be compiled by invoking ant in the project root directory and may be executed on the emulator. Adb is the tool that talks to the emulator. In order for adb to function, the emulator needs to be running. Adb can be requested to query the available emulators.

C:\USERS\paller\android\helloworld\bin>adb devices
List of devices attached


If the emulator was started with the emulator command, the same adb command detects it.

C:\USERS\paller\android\helloworld\bin>adb devices
List of devices attached
1 emulator-tcp-5555 device 0


The ant script creates the installable package with apk extension in the bin subdirectory of the project root directory.

C:\USERS\paller\android\helloworld\bin>adb install HelloWorld.apk
151 KB/s (0 bytes in 2421.000s)


Now let's see the emulator. Our application can be found in the Applications folder that is the leftmost icon on the rolling bar.


Entering this folder, the application can be found and executed.