Loader restarted on Activity start confusion

666 Views Asked by At

I have a loader. I want it to only start when the underlying data model changes. Which I understood as the point of a loader. From the Android docs:

Loaders, in particular CursorLoader, are expected to retain their data after being stopped. This allows applications to keep their data across the activity or fragment's onStop() and onStart() methods, so that when users return to an application, they don't have to wait for the data to reload.

Great. However, whenever my activity resumes, my loader onStartLoading() is called. Debugging into the platform code, the implementation of Activity.onStart() ends up restarting all of the loaders. Specifically the call stack is,

Activity.onStart() -->
FragmentController..doLoaderStart() -->
FragmentHostCallback.doLoaderStart() -->
LoaderManagerImpl.doStart() --> 
LoadermanagerImpl.LoaderInfo.start() -->
Loader.startLoader() -->
<my loader>.onStartLoading()

My loader is costly so I don't want it to reload when my activity is restarted, and this seems to go against the quote above which specifically states that loaders are supposed to retain their data across stops / start cycles.

Does this make sense?

1

There are 1 best solutions below

0
Jeffrey Blattman On

The problem was basically a misunderstanding of loaders. When the framework calls a Loader's onStartLoading(), it's more like a lifecycle method. It's the framework telling your loader that the activity has started. Contrary to my original thinking, it doesn't mean that the loader must reload it's data.

A naive implementation of a loader (like mine) just reloads all of it's data on onStartLoading(). A smarter implementation only loads if the underlying data has changed. If we look at CursorLoader.onStartLoading(),

@Override
protected void onStartLoading() {
    if (mCursor != null) {
        deliverResult(mCursor);
    }
    if (takeContentChanged() || mCursor == null) {
        forceLoad();
    }
}

It first sends the cached result immediately if it has it. It then calls forceLoad() which actually loads the data, but it only does this if the data has changed.

The other detail is how it tracks when content has changed. It boils down to Loader's implementation of onContentChanged():

public void onContentChanged() {
    if (mStarted) {
        forceLoad();
    } else {
        // This loader has been stopped, so we don't want to load
        // new data right now...  but keep track of it changing to
        // refresh later if we start again.
        mContentChanged = true;
    }
}

This method says: when the content has changed as we're started, load the data. Otherwise, just keep a flag letting us know the content has changed. It's basically delaying the actual load the of the data until the loader is started.

takeContentChanged() basically checks the mContentChanged instance field:

public boolean takeContentChanged() {
    boolean res = mContentChanged;
    mContentChanged = false;
    mProcessingChange |= res;
    return res;
}

As long as your loader implementation calls onContentChanged() when the content has changed, the implementation of Loader handles the rest.