How to finish destroyed Activity
Asked Answered
J

7

13

As I understand it, an activity being destroyed is not equivalently to an activity being finished.

  • Finished
    • The activity is removed from the back stack.
    • It can be triggered by the program (e.g. by calling finish()), or by the user pressing the back key (which implicitly calls finish()).
    • Finishing an activity will destroy it.
  • Destroyed
    • The Android OS may destroy an invisible activity to recover memory. The activity will be recreated when the user navigates back to it.
    • The activity is destroyed and recreated when the user rotates the screen.
    • Reference: Recreating an Activity

So how do I finish a destroyed activity? The finish() method requires an Activity object, but if the activity is destroyed, I have no Activity object - I am not supposed to be holding a reference to a destroyed activity, am I?


Case study:

I have an activity a, which starts b, which in turn starts c (using Activity.startActivity()), so now the back stack is:

a → b → c

In c, the user fills out a form and tap the Submit button. A network request is made to a remote server using AsyncTask. After the task is completed, I show a toast and finish the activity by calling c.finish(). Perfect.

Now consider this scenario:

While the async task is in progress, the user switches to another app. Then, the Android OS decided to destroy all 3 activities (a, b, c) due to memory constraints. Later, the async task is completed. Now how do I finish c?

What I have tried:

  • Call c.finish():
    • Can't, because c is destroyed.
  • Call b.finishActivity():
    • Can't, because b is destroyed.
  • Use Context.startActivity() with FLAG_ACTIVITY_CLEAR_TOP so as to raise b to the top, thus finishing c:

    // appContext is an application context, not an activity context (which I don't have)
    Intent intent = new Intent(appContext, B.class);    // B is b's class.
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    appContext.startActivity(intent);
    
    • Failed, appContext.startActivity() throws an exception:

android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

Edit: Clarification: I need to wait until the async task finishes and decide whether to finish c based on server's response.

Jocelin answered 4/2, 2013 at 5:36 Comment(2)
Try this Intent intent = new Intent(appContext, B.class); // B is b's class. intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); finsh(); appContext.startActivity(intent);Shapeless
@Sreekanthss *what*.finish()? I have no activity.Jocelin
J
0

Can't finish a destroyed activity directly, so just finish() it in its onCreate() (suggested by @Labeeb P). Here's how:

  1. If the activity is already destroyed when trying to finish it, save a boolean flag somewhere instead.

    if(activity != null)
    {
        // Activity object still valid, so finish() now.
        activity.finish();
    }
    else
    {
        // Activity is destroyed, so save a flag.
        is_activity_pending_finish = true;
    }
    
    • If the flag needs to stay even if the app is destroyed, use persistent storage, e.g. SharedPreferences (suggested by @Labeeb P).
  2. In the activity's onCreate(), check the flag and call finish().

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
    
        if(is_activity_pending_finish)
        {
            is_activity_pending_finish = false; // Clear the flag.
    
            // This activity should have been finished, so finish it now.
            finish();
            return;
        }
    
        ...
    }
    
    • If there're multiple instances of the same Activity class, you may need something more than a boolean flag to identify the specific instance of activity to finish.

Calling finish() in onCreate() is actually a legimate operation, as it is mentioned in the doc:

... you might call finish() from within onCreate() to destroy the activity. In this case, the system immediately calls onDestroy() without calling any of the other lifecycle methods.


Other considerations:

  • It may not be a good idea to finish an activity while the app is in background, especially if it is the only activity. Make sure that you don't confuse the user.
  • For better user experience, if you finish an activity while the app is in background, you may want to inform the user. Consider using toasts (good for short notices) or notifications (good for long operations that the user may have forgotten)(suggested by @Stephan Branczyk and @dilix).

Of course, an activity being destroyed doesn't necessary mean that the app is in background (there might be another foreground activity). Still, the above solution (calling finish() in onCreate()) works.

Jocelin answered 5/2, 2013 at 2:35 Comment(0)
M
3

android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

  • This exception used to occur when you are starting an activity from
    the background thread or service. You need to pass
    FLAG_ACTIVITY_NEW_TASK flag whenever you need the "launcher"
    type of behavior.

    • Just add mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); to avoid this exception.

  • The way you are trying to kill the activity is not recommended, let the android handle itself. There isn't any meaning to finish an activity which is already destroyed.

Now, what you can do?

  • If you are facing problem in finishing activity when app is not in foreground, what you can do is to implement a security check which will finish the activity only when app is in foreground to go to back-stack activity or else just skip that step.

  • I think you are trying to kill the activity when app is in background. It seems a little bit difficult to do so, but you can make use of onUserLeaveHint to decide when app is going in the background in-order to finish the activity or you can finish the activity by adding finish(); in onStop(). Just make sure that asynctask's onPost() don't finish it again in-order to avoid the exception.

  • Have a look at android:clearTaskOnLaunch attribute and set it to true.

    Google Doc says about this attribute is:

    for example, that someone launches activity P from the home screen, and from there goes to activity Q. The user next presses Home, and then returns to activity P. Normally, the user would see activity Q, since that is what they were last doing in P's task. However, if P set this flag to "true", all of the activities on top of it (Q in this case) were removed when the user pressed Home and the task went to the background. So the user sees only P when returning to the task.

    and i think this is the exact case which you want.

Hope this will give you some hint to achieve your desired task.

Musca answered 4/2, 2013 at 6:10 Comment(2)
1. The exception occurred in a method called by onPostExecute() of the async task, which is executing on the main/UI thread, not a background thread. 2. I don't want to kill an app. I just want to finish one of the activities. 3. I want to finish the destroyed activity c so that when the user switches back to my app, c is gone and the user sees b.Jocelin
android:clearTaskOnLaunch is interesting. Let me see if I can alter this flag programmatically.Jocelin
O
1

you can broadcast your action from the onPostExecute method in c and register a broadcast receiver to receive for that action in a and b. Then do finish in that receiver onRevice method

In c , AsyncTask,

 void onPostExecute(Long result) {
         ----
         Intent intent1 = new Intent("you custom action");
    context.sendBroadcast(intent1);
     }

In a and b

registerReceiver(new BroadcastReceiver() {

            @Override
            public void onReceive(Context context, Intent intent) {
                finish();

            }
        },new IntentFilter("you custom action"));
Ottinger answered 4/2, 2013 at 6:9 Comment(5)
Then I'll need to unregisterReceiver() in onDestroy() of a and b, otherwise the system will keep a reference to the broadcast receiver which will keep a reference to the activity forever, isn't it?Jocelin
exactly you need to do that I forgot to mention thatOttinger
Well, in my case a, b, c are all destroyed, now what?Jocelin
when you navigate back to app the current activity will be recreated. If you are so worried about recreating your activity, you can set some flag in SharedPreferences and check that value in onCreate and call finish from there.Ottinger
Calling finish() in onCreate() solved my case. Thanks for the hint.Jocelin
Q
1

Personally, I'd use the notification bar to notify the user of the status of his query.

This way, I'd avoid the entire issue of having an unfinished activity. And I'd only keep the activity unfinished only if the user had not clicked on the submit button yet.

Quern answered 4/2, 2013 at 6:40 Comment(5)
I don't want to finish c as soon as the user taps Submit. Imagine that c is an account registration form. If account registration failed (e.g. account name already in use), the user can edit the form and quickly resubmit; if account registration succeeded, the activity is finished and the user can start using the app right away. Is that possible?Jocelin
Ah ok, just don't finish anything, and if the activity gets interrupted by a phone call or something. Have your thread update a boolean flag to signal that the submission was accepted. Then, verify that the boolean flag was a success in the onResume() of your activity.Quern
I know the wording of onResume() seems confusing, but it will get called when your activity gets restarted, even if it was destroyed. onResume() even gets called the first time the activity gets started. onResume() is about the UI thread resuming (even if the app gets called for the first time, it will get called that first time).Quern
Now, if your user presses the home key or something, then the user will be forced to relocate the launcher icon for your application and relaunch it himself. And once your application relaunches, its activity will just check the boolean flag to see if he's registered.Quern
+1ed for mentioning notification, good for an app which went to background.Jocelin
U
0

Regarding android manual onDestroy() called exactly before activity is destroyed, so you can call finish in it (even you can stop your bg thread before killing the activity completly).

We can assume that if activity was killed we don't interested in bg thread either, and for example if bg thread is to download image or etc that needs to be completed - so you have to use service instead of asynctask.

Unbosom answered 4/2, 2013 at 6:9 Comment(2)
I don't want to kill the background thread because the user needs to know whether the operation succeeded or not. I want to finish c if and only if the operation succeeded.Jocelin
I think in this case it's better to use service and notify user when receive notification about success delivery.Unbosom
J
0

Can't finish a destroyed activity directly, so just finish() it in its onCreate() (suggested by @Labeeb P). Here's how:

  1. If the activity is already destroyed when trying to finish it, save a boolean flag somewhere instead.

    if(activity != null)
    {
        // Activity object still valid, so finish() now.
        activity.finish();
    }
    else
    {
        // Activity is destroyed, so save a flag.
        is_activity_pending_finish = true;
    }
    
    • If the flag needs to stay even if the app is destroyed, use persistent storage, e.g. SharedPreferences (suggested by @Labeeb P).
  2. In the activity's onCreate(), check the flag and call finish().

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
    
        if(is_activity_pending_finish)
        {
            is_activity_pending_finish = false; // Clear the flag.
    
            // This activity should have been finished, so finish it now.
            finish();
            return;
        }
    
        ...
    }
    
    • If there're multiple instances of the same Activity class, you may need something more than a boolean flag to identify the specific instance of activity to finish.

Calling finish() in onCreate() is actually a legimate operation, as it is mentioned in the doc:

... you might call finish() from within onCreate() to destroy the activity. In this case, the system immediately calls onDestroy() without calling any of the other lifecycle methods.


Other considerations:

  • It may not be a good idea to finish an activity while the app is in background, especially if it is the only activity. Make sure that you don't confuse the user.
  • For better user experience, if you finish an activity while the app is in background, you may want to inform the user. Consider using toasts (good for short notices) or notifications (good for long operations that the user may have forgotten)(suggested by @Stephan Branczyk and @dilix).

Of course, an activity being destroyed doesn't necessary mean that the app is in background (there might be another foreground activity). Still, the above solution (calling finish() in onCreate()) works.

Jocelin answered 5/2, 2013 at 2:35 Comment(0)
T
0

When the system tries to destroy your Activity, it calls onSaveInstanceState. In here you can call finish(). That's it.

Warning: I've never tried this, so I don't know for sure if there are any issues with calling finish() from onSaveInstanceState. If you try this, please comment and let me know how it works out.

Transistor answered 22/10, 2014 at 20:7 Comment(1)
I don't want to finish the activity when the system tries to destroy it. I want to wait until an async task finishes and decide whether to finish it based on the server's response. At that time, the activity is already destroyed and that's why I can't find a way to finish it.Jocelin
A
0

Sorry for answering this almost 10 years later.

In my understanding the premise of the question is wrong, mainly this part:

"While the async task is in progress, the user switches to another app. Then, the Android OS decided to destroy all 3 activities (a, b, c) due to memory constraints. Later, the async task is completed. Now how do I finish c?"

In my understanding if the operating system decides to destroy all three activities due to memory constraints, it won't destroy only them, but the whole process, and this should be including the AsyncTask in question. So, the async task won't be able to complete as well.

Resource: https://medium.com/androiddevelopers/who-lives-and-who-dies-process-priorities-on-android-cb151f39044f

mainly this line from the article: "Note that while we’ll talk about specific components (services, activities), Android only kills processes, not components."


In todays world, I guess that a WorkManager would be used for running work that needs to be guaranteed to run.

Aggravate answered 29/6, 2022 at 12:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.