Memory leak - Android activities held by android.os.Message - java

I'm working on an application where 90% of the activities inherit from a common activity and all these activities leak, meaning that if I go from A->B and then B->A (finish() is called), B's onDestroy() gets called but it still leaks (checked with MAT).
The leaked activities are quite big (10MB~) so after going back and forth a few times the app crashes with OOM.
I've checked the heap dump and followed the path to GC roots for the leaking activities and they all look like this:
So I guess it's something in the common superclass that's leaking. I've already checked that all the BroadcastReceivers and listeners are unregistered when the activities get destroyed and there are no Handlers used nor anonymous inner classes which may be causing the leak (it seems to me, at least).
What could be the cause of the leak? Any help would be really appreciated.
EDIT
I've found that there are two pieces of code that when commented out the activities are not leaking anymore:
Some lines of code that instantiate a ProgressDialog.
A call to postDelayed with an anonymous Runnable.
In the first case, the dialog's dismiss() function is called before destruction, so I don't know why this could be the problem. In the second case, removeCallbacks is called for the Runnable in onPause, so theoretically it's properly cleaned, isnt' it?
Cheers.

The problem turn out to be the anonymous Runnable in postDelayed. This Runnable was called with the postDelayed() function of the content view of the base activity, so it was something like this:
#Override
protected void onResume() {
...
mCallback = new Runnable() { ... };
getContentView().postDelayed(mCallback, mDelay);
}
The surprising part was that this callback was removed in onPause():
#Override
protected void onPause() {
...
getContentView().removeCallbacks(mCallback);
mCallback = null;
}
Why this didn't prevent the leak is still a mistery to me. At first I tried using getContentView().getHandler().removeCallbacksAndMessages(null) and it fixed the leak, but then the app had things completely broken apart in seemingly unrelated places.
In the end what fixed the leak was creating a Handler instance and calling postDelayed() and removeCallbacks() on this handler.

Related

Unregistering BroadcastReceiver registered in Activity context results in IllegalArgumentException crash

The crash is pretty strange one.
In onStart, BroadcastReceiver that is stored in the Activity field is registered. In onStop, this BroadcastReceiver is unregistered. When BroadcastReceiver was successfully registered, I also set isRegistered field to true, and before I unregister the receiver, I check this field to see if we need
However, in Crashlytics I see that sometimes this fails, and the whole app crashes with IllegalArgumentException with Receiver not registered message, which originates from android.app.LoadedApk#forgetReceiverDispatcher. Which is pretty strange, considering that I check the flag, right?
After I looked into ContextImpl and LoadedApk classes which handle registration/unregistration and added some reflection-based diagnostics, it got even more mysterious. In short, on every crash like that I will extract the map of existing Context to BroadcastReceiver (see ContextImpl.java:1590 and LoadedApk.java:1361).
When it unregisters normally, without a crash I can see a map like this:
com.mypackage.myapp.MyAppContext instance -> list of instances that are registered app wide
com.mypackage.myapp.MyActivity1 instance -> list of instances that are registered for Activity1
com.mypackage.myapp.MyActivity2 instance -> list of instances that are registered for Activity2
...
However, in the event of the crash this map looks like this:
com.mypackage.myapp.MyAppContext instance -> list of instances that are registered app wide
i.e. there are no activities in which my BroadcastReceiver is registered, even though I never called unregisterReceiver!
It only happens in one specific activity, so my first guess was that this activity is leaked somehow, onDestroy lifecycle gets called, Activity entry get removed from receivers map, and then obviously the receiver won't be there when we try to unregister it. However, if that's the case, why is onStop being called after onDestroy then? And why then the receivers list is completely empty?
And if it's not the leak, then what can lead to such puzzling behaviour? I truly hope that somebody else has experienced such a strange occurrence, and may be able to help me, because right now I'm all out of ideas.
Edited
Lets just say I've felt the pain. For the sake of my own problem I just found a fix, but, to answer your question I really dived deeply into docs and SO older questions even wrote simple code and checked some lifeCycle tests myself(in which I though I had found the answer which I was wrong). I couldn't find the reason this incident happens, but to fix it I suggest wrap the register/unRegister in a try catch block:
private void registerBroadcastReceiver() {
try {
appUpdateReceiver = new AppUpdateReceiver();
registerReceiver(appUpdateReceiver, appUpdateIntentFilter);
} catch (IllegalArgumentException e) {
// already registered
}
}
private void unRegisterBroadCastReceiver() {
try {
unregisterReceiver(appUpdateReceiver);
} catch (IllegalArgumentException e) {
// already unregistered
}
}
Please provide some code so the community can look into this matter more deeply. And thanks for mentioning the LoadedApk class.
In onStart, BroadcastReceiver that is stored in the Activity field is
registered. In onStop, this BroadcastReceiver is unregistered.
A lot of things can happen in the onPause state. The app process might be killed and it might start again at onCreate.
Quoting from the docs
To stop receiving broadcasts, call
unregisterReceiver(android.content.BroadcastReceiver). Be sure to
unregister the receiver when you no longer need it or the context is
no longer valid.
Be mindful of where you register and unregister the receiver, for
example, if you register a receiver in onCreate(Bundle) using the
activity's context, you should unregister it in onDestroy() to prevent
leaking the receiver out of the activity context. If you register a
receiver in onResume(), you should unregister it in onPause() to
prevent registering it multiple times (If you don't want to receive
broadcasts when paused, and this can cut down on unnecessary system
overhead). Do not unregister in onSaveInstanceState(Bundle), because
this isn't called if the user moves back in the history stack.
So please register and unregister in onResume and onPause respectively.
Let me know if this solves it :)
Do you at any point read or write isRegistered to/from a different thread than the main application thread, for instance in a callback that is not executed by the main application thread? If so, due to possible memory consistency bugs, this may cause stale values to be read and other strange behaviour. There can be very weird behaviour if memory consistency is broken, see for instance http://jcip.net/ and http://jcip.net/listings/Holder.java . Also, do you register the BroadcastReceiver in the main application thread?
Have you tried checking the lifecycle in various ways to see if you could reproduce the bug yourself, such as turning the phone side-ways and causing lifecycle changes that way, or otherwise force lifecycle to change?
Do you set isRegistered to false after calling unregisterReceiver? I wonder whether this could be relevant, for instance if onStart is called, registration is successful, later onStop is called, un-registration is successful, later onStart is called but registration is not successful, and then onStop is called despite no BroadcastReceiver being registered due to isRegistered not having been reset. I don't know whether this is possible, but you describe "When BroadcastReceiver was successfully registered", so I wonder if there could be an issue reg. handling of unsuccessful registration. I also wonder whether this could happen if not all branches in your onStart registers the broadcast receiver.
I could guess that the following link might be relevant, but you already use a variable to check whether it has been registered or not, so I would guess not: https://developer.android.com/topic/libraries/architecture/lifecycle (though, I am uncertain whether I like or trust the wording in that text, the example it uses includes a callback).
Moreover, there's no guarantee that the component starts before the activity or fragment is stopped. This is especially true if we need to perform a long-running operation, such as some configuration check in onStart(). This can cause a race condition where the onStop() method finishes before the onStart(), keeping the component alive longer than it's needed.
I have done the implementation in this way:
Releasing the receiver in the onDestroy event
#Override
public void onDestroy() {
// ...releasing more objects
LocalBroadcastManager.getInstance(this).unregisterReceiver(testReceiver);
super.onDestroy();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ... more initializations
initializeBroadcastReceivers();
}
private void initializeBroadcastReceivers()
{
LocalBroadcastManager.getInstance(this)
.registerReceiver(testReceiver,
new IntentFilter("testReceiver"));
}
private BroadcastReceiver testReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// some code
}
};

Memory Leak in practical example

I am constantly struggling with identifying memory leaks. I guess I have several memory leaks in my project circular progress view.
One of my guesses is I have a memory leak in the internal class FadeRunnable.
But to be honest I don't know exactly how to find out if this is exactly the source of the problem. Well, when I do the usual scenario and switch the orientation I see an increase of the memory usage as shown below. And if I comment out the usage of the FadeRunnable class the steps are smaller (but still there, so I guess that's not the only leak)
Once I analyze the heap dump, I see something. But actually I don't know what the values mean. The things I do is
Change orientation many times
Open heap dump and sort by 'Retained Size'
Now when I click on "CircularProgressView' I see 8 rows in the right area. I guess this means there are 8 instances of the 'CircularProgressView' leaked and living somewhere as orphan in the memory.
Is this correct? If so, how can I find out in the dump information (I guess somewhere in the lower pane) where this object is saved/held.
I would love to have a step-by-step explanation how to find out if and which object is leaking some memory.
All of the code of the suspected view can be found in this class.
https://github.com/momentummodules/CircularProgressView/blob/master/circularprogressview/src/main/java/momentum/circularprogressview/CircularProgressView.java
But also feel free to check out the full project for deeper insight and if you want to play around with it.
Thanks in advance!
UPDATE
The code link from above shows the fixed code of the mem-leaking inner class. The following snippet shows the original mem-leaking code that should never be used like that
/**
* Mem-leaking code, for fixed code see repository link
* https://github.com/momentummodules/CircularProgressView/blob/master/circularprogressview/src/main/java/momentum/circularprogressview/CircularProgressView.java
*/
public class CircularProgressView extends View
{
...
private Thread fadeThread = null;
...
...
class FadeRunnable implements Runnable
{
#Override
public void run()
{
...
}
}
...
...
private void startFade(boolean fadeIn)
{
// check existing
if(this.fadeThread != null)
{
// check if fade is already running
switch(this.fadeThread.getState())
{
case TERMINATED:
case NEW:
this.fadeThread = null;
break;
case RUNNABLE:
case BLOCKED:
case TIMED_WAITING:
case WAITING:
return;
}
}
// create new
this.fadeThread = new Thread(new FadeRunnable(fadeIn, this.fadeTime));
this.fadeThread.start();
}
}
Yes, you do have a memory leak in FadeRunnable class.
Every instance of inner class contains implicit reference to its outer class, accessible through OuterClass.this operator. In your project, when you execute the FadeRunnable and then trigger reconfiguration by orientation change, the whole activity and your CircularProgressView contained within get recreated, but the FadeRunnable from previous is still alive (allocated) and, because of it holding implicit reference to its outer CircularProgressView class, the view continues to live also, that's why after several reconfigurations you have 8 instances of CircularProgressView allocated in memory, and that gets worse - every View keeps a reference to it's context, and this cannot be freed also, resulting in bad memory leaks.
Runnables, Handlers and similar objects that can out-live their enclosing activities, fragments, views etc. should be declared as standard classes or STATIC inner classes (a static inner class doesn't hold implicit reference to its outer class), and shouldn't keep references such as Context, View etc., instead you can keep a WeakReference<> so when your Activity is recreated through config change, the View can be destroyed and freed by garbage collector.
This is a very informative article on the subject, I strongly suggest reading it.
I guess you have the correct direction there. This FadeRunnable is certainly not cool. Even if you have other memory leaks, you defo should check this out.
In general what you should really be doing in a view is quite different, specially that views already have facilities to deal with timing and animation without the need for threads.
I'll suggest you what I believe is a simpler and cleaner approach to animate stuff on views.
Start by removing your runnable and thread completely.
then to start an animation you do:
ValueAnimator animation = ValueAnimator.ofFloat(0, 1);
animation.setDuration(500);
animation.addUpdateListener(animationUpdate);
animation.addListener(animationUpdate);
animation.start();
and then you need those listeners
// this gets called for every animation update,
// inside this call you update `CircularProgressView.this.fadeAlpha`
private final ValueAnimator.AnimatorUpdateListener animationUpdate = new ValueAnimator.AnimatorUpdateListener() {
#Override public void onAnimationUpdate(ValueAnimator animation) {
// this fraction varies between 0f and 1f
float fraction = animation.getAnimatedFraction();
// ... do your calculation
ViewCompat.postInvalidateOnAnimation(CircularProgressView.this);
}
};
// this is an optional one only if you really need
// in that you get notified when the animation starts and ends
private final Animator.AnimatorListener animationListener = new AnimatorListenerAdapter() {
#Override public void onAnimationStart(Animator animation) {
// anything u need goes here
ViewCompat.postInvalidateOnAnimation(CircularProgressView.this);
}
#Override public void onAnimationEnd(Animator animation) {
// anything u need goes here
ViewCompat.postInvalidateOnAnimation(CircularProgressView.this);
}
};
and that's about it.
On the topic of actual memory leak analysis I'll suggest you to from now on and forever use the leak canary library: https://github.com/square/leakcanary it's a great tool to help us (developers) track memory leaks.
edit:
Why are you having a memory leak on this animation?
It's quite simple:
on startFade(boolean); you create a new thread and a new runnable
the runnable have a reference to the view (because it's an non-static inner class)
the thread have a reference to the Runnable, so can run it.
the framework destroy the view, because it's not part of the UI anymore (rotation, back button)
the thread is still running, with the runnable still looping, with the View object still not destroyed because the Runnable references it.
the view Object have an instance of Context, and this context is the Activity.
So at the end of this sequence your activity will not be garbage collected by the GC, AKA: Memory Leak !

runOnUiThread and callbacks strange behavior

At runtime, I am using a callback pattern to have generic code run and use the callback when finished to execute code on the GUI. So in my handler I use the runOnUiThread function against the callback.
These are all async http calls. At runtime if I interrupt and use the back button and go to another fragment for example, the system will swap out the fragments and run both callbacks (the new fragment one on the correct callback, and the old callback that should have ran on the old fragment on the new current fragment). This is wrong, the new fragment gets both callbacks but when it was initialized it was assigned to the other fragment...
This makes no sense and you can only observe the behavior by switching fragments at runtime before an async call finishes. I don't see how it's possible, in the code I check if callback is null so it should have been garbage collected and how it runs on the new callback I don't know how it's possible....there's no additional assignment happening to change this. It's almost like it's just looking in memory for the function signature and running it even though when it does it's on the wrong object.
Does anyone have any clue what's going on? I've surpressed it with an ugly piece code, I just don't know how this is possible?
I understand the obvious candidates are if I assigned it again somewhere, but the answer is no. When the fragment is created it creates an object, assigns itself as the callback, and processing begins so when the fragment is destroyed it should be too. But the async task is still executing which is fine, why it does a callback on the new thread on the main gui I guess is because of runOnUiThread, so that function is somehow changing what the callback object points to in memory?
Its your code :-)
There is no magic happening 'behind the scenes' in Android and no references are changed.
Many have problems like yours, and its always because they assume that the Android system is somehow cleaning up when a fragment or activity instance is replaced or removed. It isn't, and therefore the callbacks are still executed, on a obsolete fragment or activity.
runOnUiThread takes a Runnable and runs it when the UI thread have spare time (after invoking lifecycle methods and finished rendering). Its not magic either :-)

What is the best equivalent of a main() function for an Android app?

I've searched everywhere a manner to have the equivalent of a main() function (yes function not method) inside a Android application but failed...
Typically what I would want to do is:
void main()
{
// do some really nice initialisations stuff here
// ... let the app does his life, I really don't care
// do some final stuff here before leaving
}
The nearest approach I've seen so far is to use a SplashScreen and override the OnCreate() method. The problem is that is not acceptable from my point of view.
Why? Because a SplashScreen is nothing than an Activity tagged as a LAUNCHER.
That makes it to appear in the apps list, thing I don't want when I develop an app widget.
Furthermore, where to place my code just before the app destroy? In the onDestroy() method?
No, once again, this is not reliable. Android can decide to delete my instance whereas the application is still running.
Well, in fact, I take for principle that every components of my app are running in the same process since I don't mention explicitely in the Manifest that I wan't a component to run in its own process.
In the case of an app widget, I've placed my init code on the first call of onUpdate() method. I think it's a good bet. Then this app widget (AppWidgetProvider more precisely) is in charge to launch any activity as its will.
The "DataBase" for all the app is defined in a separate Singleton like this:
public class MyDataBase {
public static MyDataBase getInstance() {
if (instance_ == null)
instance_ = new DataBase();
return instance_;
}
public void load();
public void save();
static MyDataBase instance_ = null;
public int myInt;
public String myString;
public Object myObject;
etc..
}
With this Singleton I'm sure at least, its lifecycle is the same as the entire app itself.
To back with that AppWidgetProvider, I have to trick a little. Indeed, Android can decide to delete its instance whereas some other activities are still on place and the process is still running. So for example, systematically loading my DataBase in the first call of the OnUpdate() is unnecessary and overkill. What I do is having a static boolean value that indicates if the DataBase have been loaded for the lifecycle of this process or not.
Thus, the AppWidgetProvider can be instanciated tons of time, as long as the Singleton DataBase persists (so the process), it will not reload the DataBase each time, got it?
(yes difficult to be clear...)
About the cleanup code of the app, I thought to override the finalize() method of my DataBase Singleton, but well, I'm really not sure it's a good idea since the moment of the call of this method is totally unpredictable. I suppose it would be called if you suddently power off your Android, but well I'm not sure of anything here, thus so far, I didn't found a solution for that part.
Any comment or something less tricky that what I currently do is welcome.
Thanks.
onResume() is the function that will be invariably reached before starting you app, so you could either put the 'main' code in the onCreate() method or the onResume().
onPause() is ALWAYS called before destroying the app, either by the user or the OS.
There is great explanation regarding the lifecycle in the Android documentation:
http://developer.android.com/training/basics/activity-lifecycle/starting.html
For the initialisation you can over-ride the onCreate method of the Application class:
Called when the application is starting, before any activity, service, or receiver objects (excluding content providers) have been created. Implementations should be as quick as possible (for example using lazy initialization of state) since the time spent in this function directly impacts the performance of starting the first activity, service, or receiver in a process. If you override this method, be sure to call super.onCreate()
Termination is harder to deal with. You'll probably have to monitor each component of your application separately. If you are targeting API level 14 or later you can use Application.registerActivityLifecycleCallbacks to help with this.
The nearest approach I've seen so far is to use a SplashScreen and
override the OnCreate() method. The problem is that is not acceptable
from my point of view. Why? Because a SplashScreen is nothing than an
Activity tagged as a LAUNCHER.
It's because Android is formed with several activities and those activities have life cycles. So every activities start from onCreate() then finishes at onDestroy().
http://developer.android.com/training/basics/activity-lifecycle/starting.html
That makes it to appear in the apps list, thing I don't want when I
develop an app widget. Furthermore, where to place my code just before
the app destroy? In the onDestroy() method? No, once again, this is
not reliable. Android can decide to delete my instance whereas the
application is still running.
In a scenario that user presses home button to exit from your app, your current activity is more likely to invoke onPause() method (only when the activity has no other processes to finish). However, when user force closes (terminates) your whole application by ending process. Then you don't have to worry about invoking any methods or what so ever because Android itself will close anything related to your application automatically.
To back with that AppWidgetProvider, I have to trick a little. Indeed,
Android can decide to delete its instance whereas some other
activities are still on place and the process is still running. So for
example, systematically loading my DataBase in the first call of the
OnUpdate() is unnecessary and overkill. What I do is having a static
boolean value that indicates if the DataBase have been loaded for the
lifecycle of this process or not. Thus, the AppWidgetProvider can be
instanciated tons of time, as long as the Singleton DataBase persists
(so the process), it will not reload the DataBase each time, got it?
(yes difficult to be clear...)
The example you have posted for singleton database connection is not bad I think, but there are better ways to get the job done cleanly and more effectively. For example hibernate framework connection pooling

How can you handle dismissing a DialogFragment (compatibility lib) upon completion of an AsyncTask

There are numerous posts about how to handle a configuration change during an AsyncTask, but none I have found give a clear solution regarding apps that are in background (onPause()) when an AsyncTask finishes and tries to dismiss a DialogFragment (compatibility library).
Here is the problem, if I have an AsyncTask running that should dismiss a DialogFragment in onPostExecute(), I get an IllegalStateException if the app is in the background when it tries to dismiss the DialogFragment.
private static class SomeTask extends AsyncTask<Void, Void, Boolean> {
public SomeTask(SomeActivity tActivity)
{
mActivity = tActivity;
}
private SomeActivity mActivity;
/** Set the view during/after config change */
public void setView(Activity tActivity) {
mActivity tActivity;
}
#Override
protected Boolean doInBackground(Void... tParams) {
try {
//simulate some time consuming process
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException ignore) {}
return true;
}
#Override
protected void onPostExecute(Boolean tRouteFound) {
mActivity.dismissSomeDialog();
}
}
The Activity looks like this:
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
public class SomeActivity extends FragmentActivity {
public void someMethod() {
...
displaySomeDialog();
new SomeTask(this).execute();
...
}
public void displaySomeDialog() {
DialogFragment someDialog = new SomeDialogFragment();
someDialog.show(getFragmentManager(), "dialog");
}
public void dismissSomeDialog() {
SomeDialogFragment someDialog = (SomeDialogFragment) getFragmentManager().findFragmentByTag("dialog");
someDialog.dismiss();
}
....
}
Works fine UNLESS the app switches to background while SomeTask is still running. In that case, when SomeTask tries to dismissSomeDialog(), I get an IllegalStateException.
05-25 16:36:02.237: E/AndroidRuntime(965): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
All of the posts I've seen seem to point in some kludgy direction with elaborate workarounds. Isn't there some android way of handling this? If it were a Dialog instead of a DialogFragment, then the Activity's dismissDialog() would handle it correctly. If it were a real DialogFragment instead of one from the ACP, then dismissAllowingStateLoss() would handle it. Isn't there something like this for the ACP version of DialogFragment?
Fragments are saved as part of each Activity's state, so performing transactions after onSaveInstanceState() has been called technically doesn't make sense.
You definitely don't want to use commitAllowingStateLoss() to avoid the exception in this case. Consider this scenario as an example:
The Activity executes an AsyncTask. The AsyncTask shows a DialogFragment in onPreExecute() and starts executing its task on a background thread.
The user clicks "Home" and the Activity is stopped and forced into the background. The system decides that the device is pretty low on memory so it decides that it should also destroy the Activity too.
The AsyncTask completes and onPostExecute() is called. Inside onPostExecute() you dismiss the DialogFragment using commitAllowingStateLoss() to avoid the exception.
The user navigates back to the Activity. The FragmentManager will restore the state of its fragments based on the Activity's saved state. The saved state doesn't know about anything after onSaveInstanceState() has been called, so the request to dismiss the DialogFragment will not be remembered and the DialogFragment will be restored even though the AsyncTask has already completed.
Because of weird bugs like these that can occasionally happen, it's usually not a good idea to use commitAllowingStateLoss() to avoid this exception. Because the AsyncTask callback methods (which are called in response to a background thread finishing its work) have absolutely nothing to do with the Activity lifecycle methods (which are invoked by the system server process in response to system-wide external events, such as the device falling asleep, or memory running low), handling these situations require you to do a little extra work. Of course, these bugs are extremely rare, and protecting your app against them will often not be the difference between a 1 star rating and a 5 star rating on the play store... but it is still something to be aware of.
Hopefully that made at least some sense. Also, note that Dialogs also exist as part of the Activitys state, so although using a plain old Dialog might avoid the exception, you would essentially have the same problem (i.e. dismissing the Dialog wouldn't be remembered when the Activity's state is later restored).
To be frank, the best solution would be to avoid showing a dialog throughout the duration of the AsyncTask. A much more user-friendly solution would be to show a indeterminate progress spinner in the ActionBar (like the G+ and Gmail apps, for example). Causing major shifts in the user interface in response to asynchronous callbacks is bad for the user experience because it is unexpected and abruptly yanks the user out of what they are doing.
See this blog post on the subject for more information.
To get around the illegal state exception issue and essentially implement a dismissAllowingStateLoss() can be done using the following.
getFragmentManager().beginTransaction().remove(someDialog).commitAllowingStateLoss();
This should solve the issue without the hacky code. The same can also be applied for show if you have threads communicating through a handler with the UI thread using dialog.show(); Which can cause an illegal state exception as well
getFragmentManager().beginTransaction().add(someDialog).commitAllowingStateLoss();
#joneswah is correct, given the posters question.
If you are using the support library, replace
getFragmentManager()
with
getSupportFragmentManager()
For future Googlers:
#Alex Lockwood raises good and valid concerns with this solution. The solution does solve the error and will work in most cases, but hints that there are issues with the approach in the original question, from a UX perspective.
The Activity should assume that the async task may not complete and that it will not perform onPostExecute(). Whatever UI action (ie, spinner, ideally not a dialog) is started to notify the user of the async operation, should have provisions to stop automatically either on a timeout or by tracking state and checking in onRestore/onResume type lifecycle events to ensure the UI is updated properly. Services may also be worth investigating.
You should cancel your AsyncTask in onPause() if the onPostExecute() is going to update the UI. You shouldn't try to update the UI while your activity has been paused.
Eg. in your onPause():
if (task != null) task.cancel(true);
If you want the changes from the task to persist to the next time, then store the data/changes in doInBackground() and then update the UI when your activity/fragment/dialog gets resumed.
If you don't want the changes from the task to persist, then don't store the changes until onPostExecute()
When Android stops your app because the user hit the back or home button, your dialogs are closed for you. Usually the trick is to preserve the dialogs between onStop()/onStart(). So unless you need to do more than just close the dialog, I'd say don't worry about it.
EDIT: On your activity that hosts the dialog, you may still want to close the dialog if it's still open inside onStop(). This helps prevent memory leaks. But this doesn't need to be triggered from AsyncTask.
Like i said above, the problem is what happens when you hit onStart() again and your AsyncTask is NOT finished yet. You'll need to figure out a way to determine that and re-open that dialog if needed.
After numerous redesigns I finally settled on an approach that seems to work. However, I haven't been able to find certain aspects of my design documented elsewhere in discussions of Android development. So I'd be interested in any feedback.
In summary, what I had to do is this:
onSaveInstanceState() - if SomeTask is running, I use a simple lock to block SomeTask from exiting doInBackground() during pause.
onResume() - I had to put in a switch statement to handle different resume situations. If launching app I do nothing as nothing is paused, if restarting after being hidden or after config change I release the lock so that the preserved SomeTask instance can resume where it left off, etc.
onDestroy() - I cancel SomeTask if it is running.
I'll put the code fragments for this solution in my original post.
getActivity().finish(); in the DialogFragment worked for me.

Categories

Resources