« First impressions are important


Working around Androids quirks

The more I develop for the Android OS, the more strange quirks I find in the platform.

Overall, it is not a bad platform at all, but there are a handful of decisions made by Google that I just do not understand.

In this case, I found myself fighting the activity life cycle (Directly linked from Google, I think they can afford the bandwidth):

The first thing to note about this diagram is that onPause is completely misleading, and will not fire when your application loses focus. It only fires if a activity partially obscures your app.

However my issue was that my game is made up of several activities, and the user needed to be able to move through them seamlessly without feeling like they are separate applications. This means that the background music should continue to play, and not stop and restart when changing activities.

To solve this, the music was separated out of the activities, and into a global application class.

However, that caused a bigger problem. When the application loses focus (Perhaps the user hit "Home", perhaps a phone call came in...etc), the music did not stop.

"Easy!" I thought, "I'll just hook into the onStop handler of the activities and pause the music".

That "easy" job ended up taking me over 24 hours to figure out.

The problem is that onStop is not only called when your application loses focus, it is also called when you change activities from within your application, and there is no obvious way to tell the difference between an external application changing focus and an internal application changing focus.

However, after trying in vain for a few days to figure out how to tell the difference, I found out about the DroidFu library written by Matthias Käppler.

This library included a helper that would tell you if the activity was currently the top activity, but more importantly, it demonstrated how to access the running tasks using the ActivityManager (Something that I could not find in the Android docs).

While I didn't particularly like how he implemented the method (as a static helper method that did a lot of unneeded things), I wanted to credit him with showing me the proper API call that I needed.

I decided the proper approach was to add a new event into the Activity Life-cycle. This event is "onLostFocus", and will fire before onStop is invoked. This is implemented by creating a custom super class for all activities:

 

import android.app.Activity;

import android.app.ActivityManager;

import android.content.Context;

import android.os.Bundle;

 

/**

 * Base class for all Activities

 */

public class ActivityBase extends Activity {

 

    /**

     * Called when the activity is stopped

     * and a new package is running

     */

    public void onLostFocus()

    {       

    }

 

    @Override

    public void onStop()

    {

        if (wasFocusLost())

        {

            this.onLostFocus();

        }

        super.onStop();

    }

 

    /**

     * Checks if the activity is below another application    

     */

    public boolean wasFocusLost()

    {             

         ActivityManager am  = (ActivityManager)

              this.getSystemService(Context.ACTIVITY_SERVICE);

 

        return !(am.getRunningTasks(1)

                     .get(0)

                     .topActivity

                     .getPackageName()

                     .equals(this.getPackageName()));

    }     

}


Using this super class, all of my activities can hook into onLostFocus() just like they hook onCreate() or onStop().

Why Google did not include this out of the box is a complete mystery to me, as it is an important part of the activity life-cycle.

Hopefully this super class will save someone the pain and frustration of trying to solve this problem in the future.

Posted by Jonathan Holland on 12/6/2009.

Tags: Java   Android   Frustration

Comments:

Seems like the design was incorrect. It might have been a better idea to use one activity with multiple states.

Gravatar Posted by Breakable on 12/6/2009.

Breakable:

That goes against what Google suggests, because it breaks the semantics of the back-button. (Or you have to override the back button and emulate your activity stack.)

Gravatar Posted by Jonathan Holland on 12/6/2009.

Comments are closed on this post.