4

I have a class that extends AsyncTaskLoader and which frequently receives updates. Now when the app initially starts everything works fine, the UI (a SherlockListFragment (so I am using the compatability library) that implements LoaderManager.LoaderCallbacks<List<Item>>) updates accordingly as updates are received. At some random point however the UI simply stops updating. So far I haven't noticed any type of pattern to ascertain when it will stop updating; it can happen when very few updates are occurring or when many updates are occurring.

Below is my custom AsyncTaskLoader (I simply edited class and variable names to be highly generic so as to hopefully make the code slightly simpler to understand):

public class CustomLoader extends AsyncTaskLoader<List<Item>> {
private static final String LOG_TAG = "CustomLoader";
private List<Item> items;

public CustomLoader(Context context) {
    super(context);
}

@Override
public List<Item> loadInBackground() {
    return ItemModel.getItemList();
}


@Override
public void deliverResult(List<Item> data) {
    if (isReset()) {
        if (data != null) {
            Log.w(LOG_TAG, "Warning! An async query came in while the Loader was reset!");
            releaseResources(data);
            return;
        }
    }

    // Hold a reference to the old data so it doesn't get garbage collected.
    // We must protect it until the new data has been delivered.
    List<Item> oldItems = items;
    items = data;

    if (isStarted()) {
        Log.i(LOG_TAG, "Delivering results to the LoaderManager.");
        // If the Loader is in a started state, have the superclass deliver the
        // results to the client.
        super.deliverResult(data);
    }

    // Invalidate the old data as we don't need it any more.
    if (oldItems != null && oldItems != data) {
        Log.i(LOG_TAG, "Releasing any old data associated with this Loader.");
        releaseResources(oldItems);
    }
}

@Override
protected void onStartLoading() {
    Log.i(LOG_TAG, "onStartLoading() called!");

    if (items != null) {
        // Deliver any previously loaded data immediately.
        Log.i(LOG_TAG, "Delivering previously loaded data to the client");
        deliverResult(items);
    }

            //Initialises the loader within the model 
    ItemModel.registerLoader(this);

    if (takeContentChanged()) {
        forceLoad();
    }
    else if (items == null) {
        // If the current data is null... then we should make it non-null! :)
        forceLoad();
    }
}

@Override
protected void onStopLoading() {
    Log.i(LOG_TAG, "onStopLoading() called!");

    // The Loader has been put in a stopped state, so we should attempt to
    // cancel the current load (if there is one).
    cancelLoad();
}

@Override
protected void onReset() {
    Log.i(LOG_TAG, "onReset() called!");
    super.onReset();

    // Ensure the loader is stopped.
    onStopLoading();
    // At this point we can release the resources associated.
    if (items != null) {
        releaseResources(items);
        items = null;
    }
    // The Loader is being reset, so we should stop monitoring for changes.
            // We do this by making the loader instance null
    ItemModel.deregisterLoader();
}


@Override
public void onCanceled(List<Item> data) {
    Log.i(LOG_TAG, "onCanceled() called!");

    /**
     * So... we were having problems with the loader sometimes simply not refreshing. It was found
     * that when receiving two updates in quick succession, the loader would call onCanceled() after 
     * the second update (in order to try to stop the previous load). Whenever onCanceled() was called,
     * the loader would stop refreshing. 
     * 
     * And the reason for this?? The support library version of Loader does not support onCanceled() !!!
     * Thanks to this answer on stack overflow for bringing up the issue - http://stackoverflow.com/a/15449553
     * By examining the API for the support library and the API 11 versions of Loader, it is clear that
     * we shouldn't be receiving onCanceled() calls here, but we still do!
     * 
     * Also important to note is that even on Android 3.0 and up, the framework will still use the 
     * support library methods for Loader.
     * 
     * So we simply swallow this onCanceled() call and don't call the super method. This seems to fix 
     * the issue - it may also work if we simply remove onCanceled() completely, but not 100% sure. 
     */

        // Attempt to cancel the current asynchronous load.
    //super.onCanceled(data);

    // The load has been canceled, so we should release the associated resources
            //Uncommenting this line of code does not resolve my issue
    //releaseResources(data);
}

@Override
public void forceLoad() {
    Log.i(LOG_TAG, "forceLoad() called!");
    super.forceLoad();
}

private void releaseResources(List<Item> data) {
    // All resources associated with the Loader should be released here.
    if (data != null) {
        data.clear();
        data = null;
    }
}
}

Now, while the UI is still updating properly the Logs show the following sequence of events:

03-03 17:23:33.859: I/CustomLoader(20663): forceLoad() called!
03-03 17:23:33.859: I/CustomLoader(20663): Load in background called...
03-03 17:23:33.864: I/CustomLoader(20663): Delivering results to the LoaderManager.
03-03 17:23:33.864: D/CustomFragment(20663): onLoadFinished() for loader_id 0
03-03 17:23:33.869: I/CustomLoader(20663): Releasing any old data associated with this Loader.

whenever the data is updated.

At the point that the UI stops updating it seems as though forceLoad() keeps on getting called every time the data changes it doesn't seem to actually accomplish anything (i.e. loadInBackground() doesn't get called). I have done a lot of research, looking at other implementations of AsyncTaskLoader and the overall logic of my implementation is similar to everything I've found so I'm at a bit of a loss here.

Kent Hawkings
  • 2,793
  • 2
  • 25
  • 30

0 Answers0