AsyncTaskLoader and orienatation change [closed] - android

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 8 years ago.
Improve this question
I have an AsyncTaskLoader to do some work on the first startup. The loader is initialized in the OnCreate method of my Activity.
if(!startedLoader) {
getLoaderManager().initLoader(INITIALIZE_DB_LOADER_ID, null, this);
startedLoader = true;
}
startedLoader is a boolean that is kept in onSaveInstanceState and retrieved again in onCreate.
This avoids my loader to restart. But now it does not deliver the results through the callbacks because my listener, which is my activity itself, was destroyed.
Here's the code that starts my Loader:
#Override
public Loader<Boolean> onCreateLoader(int id, Bundle args) {
return new InitalizeDatabaseLoader(this);
}
How can I avoid my AsyncTaskLoader to restart on an orientation change but still deliver the results?

You have to extend the deliverResult() method and keep the result in memory until the activity reconnects to the loader.
You can find here a pretty good example: http://developer.android.com/reference/android/content/AsyncTaskLoader.html
The essential part is:
/**
* Called when there is new data to deliver to the client. The
* super class will take care of delivering it; the implementation
* here just adds a little more logic.
*/
#Override public void deliverResult(List<AppEntry> apps) {
if (isReset()) {
// An async query came in while the loader is stopped. We
// don't need the result.
if (apps != null) {
onReleaseResources(apps);
}
}
List<AppEntry> oldApps = mApps;
mApps = apps;
if (isStarted()) {
// If the Loader is currently started, we can immediately
// deliver its results.
super.deliverResult(apps);
}
// At this point we can release the resources associated with
// 'oldApps' if needed; now that the new result is delivered we
// know that it is no longer in use.
if (oldApps != null) {
onReleaseResources(oldApps);
}
}
Also, you will want to extend the onStartLoading() method, in order to deliver the cached result right away:
/**
* Handles a request to start the Loader.
*/
#Override protected void onStartLoading() {
if (mApps != null) {
// If we currently have a result available, deliver it
// immediately.
deliverResult(mApps);
}
// Start watching for changes in the app data.
if (mPackageObserver == null) {
mPackageObserver = new PackageIntentReceiver(this);
}
// Has something interesting in the configuration changed since we
// last built the app list?
boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());
if (takeContentChanged() || mApps == null || configChange) {
// If the data has changed since the last time it was loaded
// or is not currently available, start a load.
forceLoad();
}
}

Related

When and why deliverResult is called in AsyncTaskLoader?

I want to establish a good understanding of the AsyncTaskLoader lifecycle. I checked several resources, everything is clear but the usage of deliverResult. According to this picture from the internet (available here):
onStartLoading will be called, then if there is data already loaded, deliverResult is called, then it deliver the result to onLoadFinished. However, if there is no data foceLoad will be called, then loadInBackground, then deliverResult, then onLoadFinished.
I did the same way croworc answer suggests here: What does AsyncTaskLoader.deliverResult() actually do?
This is the code:
public class WeatherLoader extends AsyncTaskLoader<List<Weather>> {
List <Weather> receivedData;
/** Tag for log messages */
private String mUrl;
public WeatherLoader(Context context, String url) {
super(context);
mUrl = url;
}
#Override
protected void onStartLoading() {
if (receivedData == null){
Log.i ("loader ", "No data yet");
forceLoad();
} else {
deliverResult(receivedData);
Log.i ("loader ", "data is available no reload");
}
}
#Override
public void deliverResult(List<Weather> data) {
receivedData = data;
super.deliverResult(data);
Log.i ("loader ", "deliver result");
}
#Override
public List<Weather> loadInBackground() {
Log.i ("loader ", "load in background");
if (mUrl == null) {
return null;
}
// Perform the network request, parse the response, and extract a list of earthquakes.
List<Weather> weather = getweatherData(mUrl);
return weather;
}
}
But this is the sequence of the callbacks I'm getting when I initialize the loader or restart it:
onCreatLoader
No data yet
load in background
onLoaderFinish
deliver result
What really confuses me is that deliverResult is called after onLoaderFinished which also I think contradicts with this page of this book:
available here
The check for the availability of the data used in onStartLoading which calls deliverResult only gets called when the activity is stopped and restarted, like if I navigate to another activity then get back to it. Here is what gets printed in the logcat in this case:
deliver result
data is available no reload
Even onLoadFinished doesn't get called in this case. However, if I do the same behavior of navigating to another activity and getting back to the first one with having deliverResult with its original behavior (where I only call the super version of it), onStart gets called, then loadInBackground, then onLoadFinished, then DeliverResult. So, a new load happens
Can anyone please clarify why this behavior of callbacks is taking place? Does this mean that the image that shows the lifecycle is inaccurate?
Thanks.
Put the log calls before calling super and check the flow sequence again.
onLoadFinished is called during the call to super.deliverResult.
#Override
public void deliverResult(List<Weather> data) {
Log.i ("loader ", "deliver result");
receivedData = data;
super.deliverResult(data);
}

AsyncLoaderTask stops loading randomly (loadInBackground() stops getting called)

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.

onRetainNonConfigurationInstance with Multiple Threads?

I have about resuming multiple threads within the same Activity: I've three different threads in my app, that I call AThread, BThread and CThread.
If my app is closed and reopened, I need to reopen all preview Threads. How could I do that? I thought on returning a list of threads. Is that a good option? Something like:
#Override
public Object onRetainNonConfigurationInstance() {
return new ArrayList<Thread>(AThread, BThread, CThread);
}
And than, at the function onCreate, call a "for each" that verifies all Threads. Something like
#Override
public void onCreate(Bundle savedInstanceState) {
(...)
super.onCreate(savedInstanceState);
ArrayList<Thread> allThreads = (ArrayList<Thread>) getLastNonConfigurationInstance();
AThread = allThreads.get(0);
BThread = allThreads.get(1);
CThread = allThreads.get(2);
if (AThread != null && AThread.isAlive()) {
// TODO SOMETHING
}
if (BThread != null && BThread.isAlive()) {
// TODO SOMETHING
}
if (CThread != null && CThread.isAlive()) {
// TODO SOMETHING
}
}
Is that correct? Any better Idea?
Cheers =)
Use a Fragment to host your Threads (or more preferably an AsyncTask) and call setRetainInstance(true) in the Fragment's onCreate method. The Fragment will be retained across configuration changes and the Fragment (and its Threads) won't be destroyed along with the Activity. I believe this is preferred over using onRetainNonConfigurationInstance.

How to check if Async Task is already running

I have an app that needs to do an intensive database operation on start up. The app holds a local copy of the contacts on the phone and synchronizes with the android contact database on startup.
If a user starts the app, an Async Task is started that does the database synch in the background. If the user closes the app, the operation continues running which is fine. However if the user opens the app again, the Async Task is started and an error is produced.
Is there anyway of checking if the Task is already running from a different instance of the app?
Use getStatus() to get the status of your AsyncTask. If status is AsyncTask.Status.RUNNING then your task is running.
EDIT: you should reconsider your implementation and hold the AsyncTask probably in a Service or IntentService to fetch your data from the web.
I've managed to handle this problem with some sort of Singleton pattern.
Hope it helps.
// fill the places database from a JSON object
public class myAsyncTask extends AsyncTask<Void,Integer,Integer> {
Activity mContext = null;
static AsyncTask<Void,Integer,Integer> myAsyncTaskInstance = null;
// Private Constructor: can't be called from outside this class
private myAsyncTask(Activity iContext) {
mContext = iContext;
}
public static AsyncTask<Void, Integer, Integer> getInstance(Activity iContext) {
// if the current async task is already running, return null: no new async task
// shall be created if an instance is already running
if (myAsyncTaskInstance != null && myAsyncTaskInstance.getStatus() == Status.RUNNING) {
// it can be running but cancelled, in that case, return a new instance
if (myAsyncTaskInstance.isCancelled()) {
myAsyncTaskInstance = new myAsyncTask(iContext);
} else {
// display a toast to say "try later"
Toast.makeText(iContext, "A task is already running, try later", Toast.LENGTH_SHORT).show();
return null;
}
}
//if the current async task is pending, it can be executed return this instance
if (myAsyncTaskInstance != null && myAsyncTaskInstance.getStatus() == Status.PENDING) {
return myAsyncTaskInstance;
}
//if the current async task is finished, it can't be executed another time, so return a new instance
if (myAsyncTaskInstance != null && myAsyncTaskInstance.getStatus() == Status.FINISHED) {
myAsyncTaskInstance = new myAsyncTask(iContext);
}
// if the current async task is null, create a new instance
if (myAsyncTaskInstance == null) {
myAsyncTaskInstance = new myAsyncTask(iContext);
}
// return the current instance
return myAsyncTaskInstance;
}
#Override
protected Integer doInBackground(Void... iUnUsed) {
// ...
}
}
I think you should check the concept of Application in Android.
http://developer.android.com/reference/android/app/Application.html
In fact there is no such thing as
different instance of the app
. The Application is always the same for all your Activities/Services.
That means that you'd left the Activity and opened it again, 2 cases are possible:
The system already killed your application. In this case AsyncTask is dead already and it's safe to start a new one
The Application was still alive, so AsyncTask possibly still running.
In 2nd case I will recommend to use some static variables, pointing to this AsyncTask or it's state. If your app was still alive when 2nd time opened - all static references will be still valid, so you can successfully operate.
PS: By the way, in current approach be aware that your application can be terminated by the system at any time. So AsyncTask can be interrupted in any moment. It it's not ok for you - please check IntentServices - components, specially designed for background-operation purpose. http://developer.android.com/reference/android/app/IntentService.html
Good luck!

Android: CalledFromWrongThreadException thrown when broadcast intent is handled

Here is the basic life cycle of my application. It targets SDK version 8 by now, since I am still running Android 2.3.3 on my device.
The application starts, onResume() is called
The method show() is called to display cached data.
A background service gets started which downloads and stores data. It uses AsyncTask instances to accomplish its work.
One of the tasks stores downloaded data in a SQLite database.
A broadcast intent is sent in onPostExecute() when the storing task has finished.
The MapActivity receives the intent and handles it.
The method show() is called to display cached and new data.
Within the method show() the map view gets invalidated after the overlay has been added. This works fine when show() has been called from the MapActivity itself. It raises an exception, however, when the asynchonous task is the source of the method call (indirectly).
As far as I understand, I am at the UI thread when I trigger show() in both cases. Is this true?
public class CustomMapActivity extends MapChangeActivity {
private boolean showIsActive = false;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(IntentActions.FINISHED_STORING)) {
onFinishedStoring(intent);
}
}
};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
registerReceiver(mReceiver, new IntentFilter(IntentActions.FINISHED_STORING));
}
#Override
protected void onResume() {
super.onResume();
show();
}
#Override
protected void onMapZoomPan() {
loadData();
show();
}
#Override
protected void onMapPan() {
loadData();
show();
}
#Override
protected void onMapZoom() {
loadData();
show();
}
private void onFinishedStoring(Intent intent) {
Bundle extras = intent.getExtras();
if (extras != null) {
boolean success = extras.getBoolean(BundleKeys.STORING_STATE);
if (success) {
show();
}
}
private void loadData() {
// Downloads data in a AsyncTask
// Stores data in AsyncTask
}
private void show() {
if (showIsActive) {
return;
}
showIsActive = true;
Uri uri = UriHelper.getUri();
if (uri == null) {
showIsActive = false;
return;
}
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
List<Overlay> mapOverlays = mapView.getOverlays();
CustomItemizedOverlay overlay = ItemizedOverlayFactory.getCustomizedOverlay(this, cursor);
if (overlay != null) {
mapOverlays.clear();
mapOverlays.add(overlay);
}
}
cursor.close();
mapView.invalidate(); // throws CalledFromWrongThreadException
showIsActive = false;
}
}
Here is the stack trace ...
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRoot.checkThread(ViewRoot.java:3020)
at android.view.ViewRoot.invalidateChild(ViewRoot.java:647)
at android.view.ViewRoot.invalidateChildInParent(ViewRoot.java:673)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:2511)
at android.view.View.invalidate(View.java:5332)
at info.metadude.trees.activities.CustomMapActivity.showTrees(CustomMapActivity.java:278)
at info.metadude.trees.activities.CustomMapActivity.onMapPan(CustomMapActivity.java:126)
at info.metadude.trees.activities.MapChangeActivity$MapViewChangeListener.onChange(MapChangeActivity.java:50)
at com.bricolsoftconsulting.mapchange.MyMapView$1.run(MyMapView.java:131)
at java.util.Timer$TimerImpl.run(Timer.java:284)
Note: I use the MapChange project in order to receive notifications on map events.
EDIT:
From what I now read in the documentation about AsyncTask (scroll down a bit), I am not sure if I use it the correct way. As previously mentioned I start AsyncTask instances from within a Service class. In contrary, the documentation states ...
AsyncTask allows you to perform asynchronous work on your user interface. It performs the blocking operations in a worker thread and then publishes the results on the UI thread, without requiring you to handle threads and/or handlers yourself.
... which sounds as if AsyncTask should only be used within an Activity not within a Service?!
The reason for your crash is because of the way that the MapChange library you are using is implemented. Under the hood, this library uses Timer and TimerTask implementations to delay firing the change event and reduce the number of calls your application gets to onMapChanged(). However, you can see from the docs on Timer that it runs its tasks in created threads:
Each timer has one thread on which tasks are executed sequentially. When this thread is busy running a task, runnable tasks may be subject to delays.
Since the MapChange library does nothing to ensure that callbacks are posted to your application on the main thread (a serious bug IMO, especially on Android), you have to protect the code you call as a result of this listener. You can see this in the example MyMapActivity bundled with the library, everything from that callback gets funneled through a Handler which posts the calls back to the main thread for you.
In your application, the code inside onMapPan() and subsequently showTrees() is being called on a background thread so it is not safe to manipulate the UI there. Using either a Handler or runOnUiThread() from your Activity will guarantee your code is called in the right place.
With regards to your second questions about AsyncTask, there is nothing stopping you from using it inside of any application component, not just Activity. Even though it's a "background" component, by default a Service is still running on the main thread as well, so AsyncTask is still necessary to offload long-term processing to another thread temporarily.
If it's getting called on the wrong thread, then it's likely not on the UI thread. Have you tried this:
runOnUiThread(new Runnable() {
public void run() {
mapView.invalidate();
}});

Categories

Resources