Wednesday, March 20, 2013

Is Your AsyncTask Running?

A short note about AsyncTask: Prior to 4.0 (and maybe prior to 3.0, but I haven't tested), AsyncTask.getStatus() might lie to you. If the AsyncTask is canceled, it won't set the status correctly; instead, it will remain RUNNING far after AsyncTask.onCancelled() finishes.

My initial thought was to use AsyncTask.isCancelled(), but you can run into some concurrency issues there if you're trying to gauge whether the AsyncTask is done from another thread.  A cancelled AsyncTask doesn't necessarily end the moment you cancel it; in fact, if you're not checking isCancelled() regularly in doInBackground() then you can end up having the AsyncTask run for a while after you cancel.

My solution is to set a boolean at the end of onCancelled() that will indicate to the system that you got to the end of execution.  Here's an example of writing an AsyncTask where you can properly know when it's been finished:

private class MyAsyncTask extends AsyncTask {
  private boolean mFinishedCancel = false;

  protected Void doInBackground(Void... params) {
    return null; // You'd normally do something here
  }

  protected void onCancelled() {
    mFinishedCancel = true;
  }

  public boolean isFinished() {
    return getStatus() == Status.FINISHED || mFinishedCancel;
  }
}

Tuesday, March 12, 2013

Easier View State Saving

If you write your own custom Views you may eventually want to leverage onSaveInstanceState() and onRestoreInstanceState() to store some data relating to the View.  Unlike Activity/Fragment, which use Bundle to pass around the instance state, Views use Parcelables for their instance state.

Parcelable is fast, efficient, and kind of a pain in the ass to setup.  It's doubly painful for custom Views as you have to ensure that you're properly saving the superclass' state as well in your own Parcelable.  The boilerplate code is excessive.

It turns out there's a much easier way which leverages these two points:

  1. Bundles implement Parcelable.  This means you can use a Bundle instead of a custom Parcelable as your instance state.
  2. Bundles can store Parcelables.  This means you can preserve the superclass' state in your Bundle.

Here's a simple implementation of the concept:
public class CustomView extends View {
  public CustomView(Context context) {
    super(context);
  }

  @Override
  protected Parcelable onSaveInstanceState() {
    Bundle bundle = new Bundle();
    bundle.putParcelable("superState", super.onSaveInstanceState());
    // Put whatever you want into the Bundle here
    return bundle;
  }

  @Override
  protected void onRestoreInstanceState(Parcelable state) {
    Bundle bundle = (Bundle) state;
    super.onRestoreInstanceState(bundle.getParcelable("superState"));
    // Restore what you put into the bundle here
  }
}

At this point you can store any primitives in a Bundle as you normally would in an Activity/Fragment.  Plus, if you use this trick many times you could even wrap it in a subclass of View (and simply replace the default instance state with a Bundle version of it).