Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LoaderCallbacks.onLoadFinished not called if orientation change happens during AsyncTaskLoader run

Tags:

android

Using android-support-v4.jar and FragmentActivity (no fragments at this point)

I have an AsyncTaskLoader which I start loading and then change the orientation while the background thread is still running. In my logs I see the responses come through to the background requests. The responses complete and I expect onLoadFinished() to be called, but it never is.

As a means of troubleshooting, in the Manifest, if I set android:configChanges="orientation" onLoadFinished() gets called as expected.

My Activity implements the loader callbacks. In the source for LoaderManager.initLoader() I see that if the loader already exists, the new callback is set to the LoaderInfo inner object class but I don't see where Loader.registerListener() is called again. registerListener only seems to be called when LoaderManagerImpl.createAndInstallLoader() is called.

I suspect that since the activity is destroyed and recreated on orientation change and since it is the listener for callbacks, the new activity is not registered to be notified.

Can anyone confirm my understanding and what the solution so that onLoadFinished is called after orientation change?

like image 818
Daddyboy Avatar asked Aug 23 '11 19:08

Daddyboy


2 Answers

Nikolay identified the issue - Thank you.

I was calling initLoader fron onResume(). The Android documentation states:

"You typically initialize a Loader within the activity's onCreate() method, or within the fragment's onActivityCreated() method."

Read "typically" as a bit more emphatic than I did when it comes to dealing with configuration change life cycle.

I moved my initLoader call to onCreate() and that solved my problem.

I think the reason is that in FragmentActivity.onCreate() a collection of LoaderManagers is pulled from LastNonConfigurationInstance and in FragmentActivity.onStart() there is some start up work regarding Loaders and LoaderManagers. Things are already in process by the time onResume() is called. When the Loader needs instantiated for the first time, calling initLoader from outside onCreate() still works.

like image 152
Daddyboy Avatar answered Oct 24 '22 02:10

Daddyboy


It's actually not the call to initLoader() in onCreate() that's fixing it. It's the call to getLoaderManager(). In summary, what happens is that when an activity is restarted, it already knows about the loaders. It tries to restart them when your activity hits onStart(), but then it hits this code in FragmentHostCallback.doLoaderStart()*:

void doLoaderStart() {
    if (mLoadersStarted) {
        return;
    }
    mLoadersStarted = true;

    if (mLoaderManager != null) {
        mLoaderManager.doStart();
    } else if (!mCheckedForLoaderManager) {
        mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false);
        // WTF: Why aren't we calling doStart() here?!
    }
    mCheckedForLoaderManager = true;
}

Since getLoaderManager() wasn't called yet, mLoaderManager is null. It therefore skips the first condition and the call to mLoaderManager.doStart().

You can test this by simply putting a call to getLoaderManager() in onCreate(). You don't need to call init / restart loaders there.

This really seems like a bug to me.

* This is the code path even if you aren't using fragments, so don't get confused by that.

like image 23
Jeffrey Blattman Avatar answered Oct 24 '22 04:10

Jeffrey Blattman