Wednesday, December 30, 2009

Android Dialogs Created From Threads

There are a lot of issues with Dialogs, but I've run into one recently that deserves mention.

Suppose you've got a background task running with AsyncTask. At the end of this task (in onPostExecute()), you display a Dialog informing the user of success or failure. This seems to be pretty cut and dry, but there's a catch - sometimes you'll get this error:

android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@4479b390 is not valid; is your activity running?
at android.view.ViewRoot.setView(ViewRoot.java:468)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:177)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
at android.view.Window$LocalWindowManager.addView(Window.java:424)
at android.app.Dialog.show(Dialog.java:239)
at android.app.Activity.showDialog(Activity.java:2488)
...

The problem lies in the fact that the AsyncTask can continue running past when the Activity itself has been finished. If a user hits the "back" button on your Activity while the AsyncTask is running, you will end up calling showDialog() after the Activity itself is finished.

My initial fix was to try to augment onCreateDialog() to handle finished activities, but unfortunately, there's no way to cancel dialog creation once you've called showDialog(); you'll cause errors returning null from onCreateDialog(). Thus, the only solution is to play it safe before you call showDialog():

protected void onPostExecute(Object result) {
if (!isFinishing()) {
showDialog(MY_DIALOG_ID);
}
}

Sunday, December 27, 2009

Dynamically Retrieving Resources in Android

Normally, when retrieving resources (drawables, strings, what have you) in code you use the automatically generated R.java to do so. However, I recently wrote a reference application wherein each item in a ListView has a different icon next to it. The data for all this is stored in JSON as an asset, which meant that there was no way for me to link my data to R.java.

Still, I needed some way to get a hold of the Drawable by name, and so I first turned to Resources.getIdentifier(). This method does the job well of finding the resource id of anything you desire, in any package:

Resources r = getResources();
int drawableId = r.getIdentifier("drawableName", "drawable", "com.mypackage.myapp");

This is all well and good, but: getIdentifier() is a general tool and as a result, slow. Running through 10,000 iterations, it took about 3200 ms on my G1. Also, it requires that you pass around a Context (or a Resources) wherever you need to use getIdentifier() which is an annoyance and seems unnecessary given that you can access R.java just fine normally in code.

The better solution - as long as you are retrieving data from your application's own R.java - is to use reflection.

try {
Class res = R.drawable.class;
Field field = res.getField("drawableName");
int drawableId = field.getInt(null);
}
catch (Exception e) {
Log.e("MyTag", "Failure to get drawable id.", e);
}

In testing, the second method is about 5x faster than getIdentifier(). Not only that, but it means you don't have to pass a Resources object around to use it. Obviously this cannot be used if you're accessing resources outside of your application's permissions and you'll have to rely on getIdentifier() then.

One last note - simply linking to R.java is way faster than either of these two methods, so only fall back on these if you need to link to resource identifiers dynamically by name.

Wednesday, December 16, 2009

Android Add-On Applications

I recently developed an add-on package for an Android application and thought I'd share some findings. That is, there is a base application and an add-on application which unlocks extras features in the base app.

I chose to put all the functionality in the base application and simply have the add-on unlock it. It turns out that this is very easy to do due to Android's dependence on signing keys. First, I made an application that has no components - it has no launcher, code, or layouts. All it has is a launcher icon (as this is viewable from the market and applications settings). Next, I signed both the base application and the add-on with the same key. Then, you can use the PackageManager to verify if the other application is installed:

public static boolean isAddOnInstalled(Context context) {
   PackageManager pm = context.getPackageManager();
   return pm.checkSignatures(BASE_APPLICATION_PACKAGE, ADDON_APPLICATION_PACKAGE) == PackageManager.SIGNATURE_MATCH;
}

The neat part of this solution is that it relies on the PackageManager's signature checking to verify the add-on. If the add-on application doesn't exist, or it was signed by a different key, this will return false.

An alternative way to do add-ons would be to put actual functionality or data in the add-on package. Again, you can use signing keys to share data. Beyond this, you can use sharedUserId to put both the base application and the add-on application in the same user ID. This will allow the applications to access each others' data files directory. A warning, though: it appears that adding a sharedUserId after the initial install causes the old data directory to still be under the old user ID, thus making all of your old files inaccessible (nor can you start writing new files - it kind of sucks). Google isn't acknowledging this issue, unfortunately.

One might think the way to go would be to use a ContentProvider, but if you're just doing an add-on for your own application, do you need to share the data with others? Besides that, ContentProviders are a much more difficult solution for a simple problem when you're working with just your own data. This isn't to say you shouldn't use ContentProviders, just that you should use the right tool for the job.

Tuesday, December 8, 2009

Welcome

Starting a new blog because I keep coming across old blog posts other coders make that help me... I figure it's time for me to give back to the community.