RSS

Search Engine

Wednesday, September 8, 2010

Handling Lengthy Operations in Google's Android

Mobile devices are very often resource-limited. That means they usually do not have the CPU power, vast memory, or storage space commonly seen in modern computers. However, sometimes complicated operations are still required to perform many features currently used on these devices. Android engineers lay out some important rules in the design philosophy; you will focus on the part about how to make sure your software is responsive enough without interfering with the main thread and inadvertently causing the system to pop up an ANR (Application Not Responding) dialog. Furthermore, a message handling mechanism is introduced to post back the result and update the view in the main thread—the device screen users operate on most of the time.

What Google APIs Are Used?

Because you are going to implement complicated operations in child threads, the usual concurrent class used in the Java threading package java.lang.Thread is used. The calling sequence and life cycle of a thread is identical to the traditional Java concept. For convenience reasons, a progress dialog is utilized to keep track of the progress. One thing to note is that Android's progress range is between 0 and 10000. It is from the android.app.ProgressDialog package.

Classes inside the android.os.Message package are used to define messages to be put in the MessageQueue for scheduled execution of a Runnable object. A Message must be processed by a Handler in the MessageQueue from the android.os.Handler package. A Handler is bound to the thread/message queue that creates it. Messages will be processed in the order they come out of the message queue. There are two main uses for a Handler: to schedule messages and runnables to be executed and to enqueue an action to be performed on a different thread.

ANR—Application Not Responding

The screen shot illustrated in Figure 1 displays the typical ANR dialog automatically popped up by the Android platform. When and why does it happen? In Android, Activity Manager and Window Manager system services monitor the application responsiveness and issue the ANR dialog if the main view detects no response to an input event over five seconds or an intent receiver has not finished executing over ten seconds. This is the main reason behind this article because this inadvertent and unpleasant ANR dialog not only symbolizes poor software design, but also rubs users the wrong way.

Figure 1: Application Not Responding (ANR) dialog

Android's Suggestions About Writing Efficient and Responsive Code

Before you start working on your solution in an example, there are some notes from Android experts you should pay attention to. To stress the importance of designing an Android application, the Android web site highlights some recommendations I think are very beneficial to all developers. After all, learning how to do things the right way from the very beginning cuts down significant development and maintenance time and effort. Besides, it does bring along the advantages of enhancing users' experience. For more details about design philosophy, please see http://code.google.com/android/toolbox/philosophy.html.

There are three key characteristics for an excellent user experience: fast, responsive, and seamless.

  • Fast: Code needs to be efficient enough. There are some key points mentioned:
    • Do not allocate memory if you can avoid it.
    • Do not do work with what you do not need to.
    • Avoid creating objects when possible.
    • Use native methods.
    • Prefer static over virtual methods.
    • Access variables directly within a class not through info getters.
    • Accessing object fields is much slower than accessing local variables.
    • Declare constants final.
    • Use for-each syntax with caution
    • Avoid enums
    • Declare fields and methods accessed by inner classes to have package scope.
    • Avoid floating point.
    • Check approximate run times for basic actions.
  • Responsive: Applications that tend to be slow, hang, freeze, or take too long to process input for noticeable periods are considered unresponsive. They usually blocks on I/O operations. Potentially long running operations should be done in child threads. Your main thread should provide a Handler for child threads to post back to upon completion. Your application should start a Service if a potentially long running action needs to be taken in response to an Intent broadcast.

  • Seamless: Sometimes a background process (for example, a Service or IntentReceiver) that pops up a UI may seem harmless but it could be annoying for users and in some cases result in data loss. The Android standard is to use a Notification for such events and leave users in control. Here are the tips for writing a seamless application:

    • Save the work in progress.
    • Create a ContentProvider to expose your data.
    • Do not interrupt users when they are talking.
    • Avoid huge Activity.
    • Use a theme for user interface.
    • Make resolutions flexible.
    • Assume that the network is slow.
    • Keep different keyboard layouts in mind.
    • Save battery power.

Performing Lengthy Operations in Child Threads

You implement the child thread in the setRandomPointsInChildThread function. Basically, a new thread will be created and executed. Within the child thread, a function to emulate the lengthy operations is provided in setRandomPoints, which does nothing but generate a series of random points and delay the process by pausing 50 milliseconds between points. After all the points have been prepared, it will send a message within a unique ID, GUIUPDATEIDENTIFIER, defined by you. A deterministic progress dialog is initiated. The screen shot is illustrated in Figure 2.

void setRandomPoints(boolean modeThreaded) {
int delay_in_msecs = 50;
int npoints = 120;
int r = 0, g = 0, b = 0;
Random rand = new Random();

if (!modeThreaded)
mypd = ProgressDialog.show(TutorialOnANR.this,
"Lengthy Calculations",
"Please wait...",
false);

mypoint = null;
mypoint = new MyPoint[npoints];
for (int i = 0; i < npoints; i++) {
mypoint[i] = new MyPoint();
mypoint[i].x = rand.nextFloat() * 320;
mypoint[i].y = rand.nextFloat() * 240;
r = (int)(rand.nextFloat() * 155) + 100;
g = (int)(rand.nextFloat() * 155) + 100;
b = (int)(rand.nextFloat() * 155) + 100;
mypoint[i].c = 0xff000000 | (r << 16) | (g << 8) | b;
mypoint[i].sz = (int)(rand.nextFloat() * 10) + 3

// Update the progress dialog
mypd.setProgress(10000 * i / npoints);

// Emulate the lengthy operations with an intentional
// delay here
try {
Thread.sleep(delay_in_msecs);
} catch (InterruptedException e) {
}

}

// Dismiss the progress dialog
mypd.dismiss();
}

// Do lengthy operations in a child thread
private void setRandomPointsInChildThread() {

// Start a progress dialog here
mypd = ProgressDialog.show(TutorialOnANR.this,
"Lengthy Calculations",
"Please wait...",
false);

Thread t = new Thread() {

public void run() {

setRandomPoints(true);

// Send a message to the handler
Message message = new Message();
message.what = TutorialOnANR.GUIUPDATEIDENTIFIER;
TutorialOnANR.this.myGUIUpdateHandler.
sendMessage(message);
}
};

t.start();
}

Figure 2: Deterministic progress dialog

Using Message Handlers to Post Back Results

First, each message should be associated with an ID that can be randomly assigned by you, but you need to guarantee its uniqueness among all the messages. In the example, you use GUIUPDATEIDENTIFIER as your message ID. Because the child threads cannot modify the view in the main thread directly, we need to update the main view myview with myview.invalidate() when the message handler receives the message ID from a child thread.

The example result is illustrated in Figure 3.

// Set up a random unique ID for message handler
protected static final int GUIUPDATEIDENTIFIER = 12345;

// Set up the message handler
Handler myGUIUpdateHandler = new Handler() {

// @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case TutorialOnANR.GUIUPDATEIDENTIFIER:
myview.invalidate();
break;
default:
break;
}

super.handleMessage(msg);
}
};

Figure 3: Final updated result

A Working Example

In addition to what I described earlier about the creations of Thread, Message, and Handler, you construct the content view from MyView by dynamically instantiating it in Activity's OnCreate instead of configuring the resources through an XML file.

You also set a key event handler to wait for the user's input by hitting the middle button of the direction pad. That completes the example.

// Set up the main view and random points
MyView myview = null;
MyPoint[] mypoint = null;

@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);

myview = new MyView(this);

setContentView(this.myview);
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {

// Uncomment the following 2 lines to test the case of
// no child thread and no message handling
// setRandomPoints(false);
// myview.invalidate();

// Set points with a child thread and message handling
setRandomPointsInChildThread();

return (true);
}

return super.onKeyDown(keyCode, event);
}

// Define the class for main view
public class MyView extends View {
private Paint mPaint = new Paint();

public MyView(Context context) {
super(context);

mPaint.setAntiAlias(true);
mPaint.setARGB(255, 255, 255, 255);
mPaint.setStrokeWidth(1);
}

@Override protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);

if (mypoint != null) {
for (int i = 0; i < mypoint.length; i++) {
mPaint.setColor(mypoint[i].c);
canvas.drawCircle(mypoint[i].x, mypoint[i].y,
mypoint[i].sz, mPaint);
}
}
}

}

// Define the class for our random point
class MyPoint {
float x;
float y;
int c;
float sz;
}

Conclusion

The article mainly deals with two issues:

  • When operations take a long time to finish, Android's system automatically pops up a dialog called ANR (Application Not Responding). You provide a sample solution to put them into child threads so that the main thread can continue without any interruption for user interaction.

  • If the lengthy operations need to update the view in the main thread, child threads cannot do that directly. A message handling scheme is employed for this purpose to post back the result from child threads to communicate with the main thread for the view update.

You also pass along the recommendations from Android designers to build software applications that will enhance user experiences. With these in mind, hopefully when you come up with your own creations next time, not only development time and efforts are better accounted for, but users will also like the products better. It is suggested you find more details about design philosophy at http://code.google.com/android/toolbox/philosophy.html.

References

  1. Download and save the entire software project
  2. Android - An Open Handset Alliance Project at http://code.google.com/android/
  3. Android Development Community at http://www.anddev.org
  4. Androidlet at http://www.androidlet.com

0 comments:

Post a Comment