11

I have a main Activity A that uses a CursorLoader to query a DB. This I create in the activity onCreate() method:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    getSupportLoaderManager().initLoader(LOADER_MEASUREMENTS, null, A.this);
}

Activity A also implements the 3 callbacks for the CursorLoader:

public Loader<Cursor> onCreateLoader(int loaderId, Bundle args)
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor)
public void onLoaderReset(Loader<Cursor> loader)

When I rotate the device, I see the correct lifecycle methods run:

A.onPause()
A.onStop()
A.onDestroy()          
A.onCreate()      <-- re-connect to existing loader, onCreateLoader() not called
A.onLoadFinished()
A.onStart()
A.onResume()

Then I open a sub-Activity B and rotate my device. When I finish B and return to Activity A I see the following run:

B.onPause()
       A.onLoaderReset()      <- why does this run?
       A.onDestroy()          
       A.onCreate()
       A.onCreateLoader()     <- now runs as loader is null
       A.onStart()
       ...

Why is my loader reset because I had Activity B open and did a device rotate? Just to add that Activity B has nothing to do with the DB or the CursorLoader.

MickeyR
  • 1,858
  • 2
  • 13
  • 24
  • 2
    [This question](http://stackoverflow.com/questions/15897547/loader-unable-to-retain-itself-during-certain-configuration-change?lq=1) and the linked questions there are related. Maybe you can find something there that might help. – George Mulligan May 11 '16 at 23:33
  • Thanks George - that question discusses exactly the same problem I'm having. Seems like it might be due to Support Loader Manager. – MickeyR May 15 '16 at 21:30
  • 2
    This [link](https://code.google.com/p/android/issues/detail?id=183783) states that this is/will be fixed in v24. – MickeyR May 15 '16 at 21:38
  • Nice find. Hopefully it is fixed permanently this time and in a way that fixes all scenarios listed in the issue. – George Mulligan May 15 '16 at 21:45

3 Answers3

3

I checked the LoaderManager source code you'll find this method:

/**
     * Stops and removes the loader with the given ID.  If this loader
     * had previously reported data to the client through
     * {@link LoaderCallbacks#onLoadFinished(Loader, Object)}, a call
     * will be made to {@link LoaderCallbacks#onLoaderReset(Loader)}.
     */
    public abstract void destroyLoader(int id);

It appears that you loader gets destroyed when rotating the screen (due to configuration change). The LoaderManager internally calls the destroyLoader method which in turn calls the onLoaderReset callback method.

Mina Wissa
  • 10,923
  • 13
  • 90
  • 158
2

It seems like this has something to do with the LoaderManager and not retaining activity state.

The LoaderManager is managed by android.app.FragmentHostCallback and void doLoaderStop(boolean retain) in this class seems to make the difference. Depending on the argument it will either retain() or stop() its loaders.

When changing the configuration in activity A (rotating your screen) the activity gets destroyed and immediately recreated. In ActivityThread#handleRelaunchActivity() the mChangingConfigurations value of the activity gets set to true. This is important, because when stopping your activity on configuration change, this gets called in Activity:

final void performStop() {
    mDoReportFullyDrawn = false;
    mFragments.doLoaderStop(mChangingConfigurations /*retain*/);
    // ...
}

You can try descending deeper into what happens—your android installation should have the sources and grep is awesome to do this—but I'll just summarize the rest.

Disclaimer: I did not thoroughly verify the following, but this is what I understand is happening.

As can be seen above, when the activity is seen fit for relaunch it will retain the loaders instead of stopping them. When you rotate activity A, the loaders get retained. When you rotate activity B, activity B gets recreated immediately, while activity A just gets destroyed. Contrary to before, the loaders will be stopped.

Stopped loaders can just be destroyed and recreated as seen fit by the LoaderManager, and this is what's happening to yours.

If you want to take a good look yourself, check out:

  • app/LoaderManager or v4/app/LoaderManager
  • app/FragmentHostCallback
  • Activity and ActivityThread
David Medenjak
  • 33,993
  • 14
  • 106
  • 134
1

You can simply set a breakpoint inside A.onLoaderReset() and run the app in debug mode to see the stack trace for troubleshooting using something like:

class A extends Activity implements LoaderManager.LoaderCallbacks
{
    @Override
    public void onLoaderReset(Loader loader)
    {
        try
        {
            // Constructs a new Exception that includes the current stack trace.
            throw new Exception();
        }
        catch (Exception e)
        {
            Log.d("TAG", Log.getStackTraceString(e));
        }
    }
}
Chebyr
  • 2,171
  • 1
  • 20
  • 30