LoaderCallbacks.onLoadFinished not called if orientation change happens during AsyncTaskLoader run - 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?

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.

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.

Related

Android - Threads and callback to activity

I have an activity and a fragment.
This fragment starts a thread which lasts 3 seconds and then calls a callback method on the activity.
if configuration is changed (i.e orientation changed), my actiivty gets recreated with new references, but the thread still has the old reference, causing a mess.
Any idea to solve this? thanks
how about use service instead thread?
reference here :
https://github.com/stephanenicolas/robospice/wiki/Starter-guide
if you want communicate a fragment or activity between thread. use interface like listener. for example, if activity recreate or something change, notify to thread, change listener to new activity.
You can use an AsyncTask instead of thread. Call task.cancel() in your onDestroy() method of the activity.
Hence your activity wont get getting any callback if the onDestroy() method has been already called.
If you insist on using thread then may be you can add a check like this in your callback method
if(getActivity() == null || getActivity().isFinishing())
return;

AsyncTaskLoader won't call onLoadFinished() after a rotation

On a button click I want to create a new SQLite database from two XML files. Using LoaderManager and AsyncTaskLoader seems to be appropriate for this.
The Fragment.onCreate() calls:
setRetainInstance(true);
When the button is clicked, my Fragment sets a flag that it is creating the database, changes the layout to show a ProgressBar and cancel button, and calls:
getLoaderManager().initLoader(0, extra, this);
The operation takes 12 seconds on my PC/Emulator. If I rotate the screen, the loader continues to call the Fragment's onLoaderProgressUpdate(int) (an interface callback method) and completes the creation of the database. But it never calls the onLoadFinished() method, where I can reset the layout, enable buttons and the action bar menu that require the database to be there.
I tried doing this in Fragment.onCreateView():
if (mCreatDBInProgress) {
mBTN_CreateDB.setEnabled(false);
mDBLayout.setVisibility(View.GONE);
mProgressBarLayout.setVisibility(View.VISIBLE);
getLoaderManager().restartLoader(0, null, this);
}
But it calls my onCreateLoader() method and starts the whole thing from scratch. This is a problem, since I have not called the method that deletes the database and goes through the process of determining what XML files to use for creating the database. The XML filenames are what I pass to that method in the Bundle arg.
I don't get why the ProgressBar callback method continues to work, but the AsyncTaskLoader can't call the onLoadFinished() method. LogCat shows that it did the deliverResult().
I tried to make the Bundle a member variable, so I could re-use it on the restartLoader() call. It start another asyncTaskLoader and my ProgressBar goes back and forth as both threads try to update it. Eventually, it gets an SQLConstraintException trying to add a row to the database that was already added by the first thread.
Well, suck me sideways! I came up with one more idea, and it worked!
Instead of calling restartoader() after the rotation, just call:
getLoaderManager().initLoader(0, mExtra, this);
For (whatever) reason, that causes the AsynTaskLoader to just continue with the first thread (instead of starting another) and relink to my Fragment for the onLoadFinished() call.
So:
initLoader() - reconnects the loader to your Fragment IF you call it with the same arguments (int, Bundle, this).
I had the same problem, when after rotating screen onLoadFinished not called.
I've seen a lot of answers but nothing really fix the issue.
The only thing that helps is to simply add call to getLoaderManager() in onCreate method (it is called again after activity is recreated because of rotation).
It's really fixed the issue for me:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LoaderManager loaderMngr = getLoaderManager();
Log.d(LOG_TAG, "" + loaderMngr);
And a little note about why I didn't just placed initLoader or restartLoader here: because Loader in my case is triggered after another event happens, so this was the only way to solve this problem for me.
Originally idea got from here.

fragments, when am i "active"?

i've been constantly frustrated by this and i can't find a good answer, so hoping someone here can offer guidance.
i have a fragment that uses AsyncTask quite extensively. i'm constantly plagued by bugs where the fragment calls getActivity(), which returns null. i assume these are happening because some method in the fragment are invoked before the activity is attached, or after it is detached.
what's the correct way to handle this in my code? i don't want to have this idiom littered all over the place,
Activity activity = getActivity();
if (activity != null) { // do something }
looking at the docs for Fragment, i can come up with many possible hooks to solve this: isDetached(), onActivityCreated(), onAttach(), isResumed(), and so on. what is the right combination?
EDIT:
A few people have suggested canceling tasks when paused, but this implies that the standard idiom,
new AsyncTask<...>.execute();
cannot be used. It implies that every exec'd AsyncTask needs to be tracked to completion, or canceled. I have simply never seen that in example code from Google or elsewhere. Something like,
private final Set<AsyncTask<?>> tasks = new HashSet<>;
...
AsyncTask<?> t = new AsyncTask<...>() {
...
public void onPostExecute(...) {
tasks.remove(this);
...
}
}
tasks.add(t);
t.execute();
...
#Override
public void onPause() {
for (AsyncTask<?> t: tasks) {
t.cancel();
}
tasks.clear();
}
Try to cancel your AsyncTasks in the onPause or onStop methods. That will prevent the onPostExecute from being called when the Fragment is not active anymore (getActivity() returns null).
Or you could check if the Fragment is attached by calling this.isAdded() in your Fragment.
I could not find a good solution. In summary, either use a Loader, or check that getActivity() does not return null before using it. I looked into using Loaders, but the pattern makes a lot of assumptions about the structure of the app and the nature of data retrieval that didn't work for me.
In terms of coordinating lifecycles, I keep onActivityCreated as a mental benchmark - it marks the point at which the underlying activity has finished its own onCreate. Prior to that I do not believe there is an activity to getActivity() from.
That get activity is returning null sounds like either you're calling getActivity() too early (i.e. before it is created) or too late (i.e. when it stopped interacting with the fragment). Stopping your tasks in onPause() would prevent getActivity from returning null since it would cut off the task once the fragment stopped interacting with the underlying activity becuase the activity itself was paused. I think waiting for onStop() may be too late since, if the task were to still be running when the underlying activity paused it may still reutrn null.

OnLoadFinished() called twice

I'm trying to figure out if I'm doing something wrong with respect to Loaders. I'm using the support library, and I have a Fragment which in onCreate() calls initLoader() setting itself as the LoaderCallbacks, however on a rotation it is receiving the result twice in onLoadFinished(), once as a result of calling init (and it already having the data), and once as a result of FragmentActivity looping through all Loaders in onStart() and delivering the result since it already has the data.
If I only call init once (on first launch of the Fragment), it doesn't set itself as the callback for the Loader so it doesn't receive a call to onLoadFinished at all. It seems as though onLoadFinished should only be called once since some expensive things may be done in onLoadFinished() (such as clearing list adapters, etc.), so I'm just trying to figure out if this is a bug or if I am just calling init at the wrong time or something else.
Anyone have any insight to this issue?
I had a similar problem and the cause was that I had initLoader and restartLoader in my code. Depending on user's action, my query could change, so I needed to restart my loader.
The solution was to use only restartLoader, even in onResume callback method replace initLoader with restartLoader.
This is a rather old question, but for future readers I have an alternative solution. Basically what I ended up doing was restart the loader if it existed.
public void onActivityCreated(Bundle savedInstanceState) {
...
if(getLoaderManager().getLoader(Constants.LOADER_ID) == null) {
getLoaderManager().initLoader(Constants.LOADER_ID, null, this);
} else {
getLoaderManager().restartLoader(Constants.LOADER_ID, null, this);
}
...
}
This solved my issue with that on screen rotate the loader was triggered twice. One thing too note is that this is only needed for me on Android < 6 that I tested. Android 6 seem to not have this issue at all.
I am experiencing same problem my self, with no good solution.
It seems as bug in Android framework, here is similar thread in which proposed solution is to place initLoader() in onResume() - I have tried it and it works, on onLoadFinished() gets called only once:
Android: LoaderCallbacks.OnLoadFinished called twice
See my post at Android: LoaderCallbacks.OnLoadFinished called twice
I had a similar problem when restarting Fragments in a ViewPager. My solution is to remove the Loader once I'm finished with it (at the end of onLoadFinished) by calling
getLoaderManager().destroyLoader(LOADER_ID);
Hope it helps!
It is looks like framework Loader wrong implementation/bug.
1. look at what I got from Log.v(LOG_TAG, ...) messages from every important method/callback after screen rotation:
...: .initLoader() 100
...: onStartLoading()
...: onLoadFinished()
...: updateUi(); articles size=10
...: loadInBackground()
...: getInputStream() - HttpRequest
...: onLoadFinished()
...: updateUi(); articles size=10
2. As you can see everything after 'updateUi()' is no needed there.

Avoid Service callback when Activity gets closed and re-opened

I have a LocalService that exposes a Binder with some APIs. I create a Service Listener, just like this:
if (dataServiceListener == null) {
dataServiceListener = new DataServiceListener();
mainActivity.getApplicationContext().bindService
(new Intent(mainActivity, LocalService.class),
dataServiceListener.svcConn, mainActivity.BIND_AUTO_CREATE);
}
After I call the method that the Binder in dataServiceListener exposes, I get the response in the dataServiceListener onResult() method. Up to this point, no kind of issues, everything is working.
Some sort of problem occurs when I close the Activity that is waiting for the Service Listener callback and immediately reopen it. Even though I re-instantiate the dataServiceListener in onCreate(), I get two callbacks instead of one, the old one from the destroyed Activity and the latter (right) one; this way the results mix up on the UI.
Is there a way to tell the Service or the Service Listener that when the activity finishes, the callbacks must be avoided. Or maybe even destroy the ServiceListener objects.
I think this is the issue that Mark L. Murphy (Commonsware) described in "The Busy Coder's Guide to Android Development":
The biggest catch is to make sure that the activity retracts the listeners when it is done.
How can I do this? Is there a way to get rid of the useless listeners when the activity finishes?
Thank you!
I had the same issue. I was working in a remote sevice using AIDL. I got this problem when i am trying do unregister my listeners using the remove method from ArrayList Collection inside a foreach loop, because I was not using asBinder in the comparision. Searching fora solution, I find out the RemoteCallbackList class in Android API. This class does exactly what i needed, and what i think you should do, on a easy way, taken all reponsabilites for the hard work that involves this task.
From the Android API:
To use this class, simply create a single instance along with your service, and call its register(E) and unregister(E) methods as client register and unregister with your service. To call back on to the registered clients, use beginBroadcast(), getBroadcastItem(int), and finishBroadcast().
Broadcast sample:
int i = callbacks.beginBroadcast();
while (i > 0) {
i--;
try {
callbacks.getBroadcastItem(i).somethingHappened();
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
// the dead object for us.
}
}
callbacks.finishBroadcast();
The code you show is for binding to a service. You do not show where you are registering a listener with that service. You apparently are, based upon your question and your reference to an onResult() method. Given the nature of your problem, I am going to guess that what you're doing is:
Binding to the service in onCreate()
In onServiceConnected(), you are calling some sort of setListener() method on the Binder
In that case, if we ignore configuration changes, the proper way to unwind matters would be to, in onDestroy(), call some removeListener() method on the Binder, then call unbindService().
Configuration changes, particularly in a pre-fragment world, make this complicated. It's the reason why this sample project (and the accompanying material in the book) is so icky. Binding is twitchy -- if you unbind from the old activity, and nothing else is keeping the service around, the service will shut down before the new activity gets a chance to bind. Binding is also state -- you cannot simply fail to unbind, lest you leak stuff.
So, the recipe becomes:
Bind to the service in onCreate() using the Application Context
In onServiceConnected(), call sort of setListener() method on the Binder
In onRetainNonConfigurationInstance(), make note of the fact that you're undergoing a configuration change, and return some Object that has your Binder, your Listener, and all the rest of your state
In onCreate(), use getLastNonConfigurationInstance() -- if it is null, proceed as normal, but if it is not null, hold onto that Binder and Listener and don't re-bind and re-register the listener
In onDestroy(), if the flag from Step #3 above is false (i.e., we are not undergoing a configuration change), call some removeListener() method on the Binder, then call unbindService().
Using fragments with setRetainInstance(true) can probably simplify this some, though I have not worked through a sample for that yet.
I had this issue too. You need to release all the resources,listeners,threads from the service when it finishes.
Your activity has to register/unregister itself as the listener. You need to use the proper lifecycle callback methods, not onBackPressed(). Register onStart(), unregister onStop(). One way to do it is to make the listener a static member of your service, and provide static register/unregister methods. Then call those from your activity as appropriate.
I finally solved the issue (and no, I haven't been working on it for so long :D).
The callback to the listener was made before the Fragment's onDestroy was called. So the boolean "dontupdate" value was never set to false. Overriding onBackPressed in the main activity solved the problem, as I invoked a destroy() method for each fragment that takes care of setting the boolean value to false.

Categories

Resources