25

I have two activities; let's say A and B. In activity A there is a broadcast receiver registered that listens for a particular event which will finish activity A. I am registering the broadcast receiver in onCreate(), and destroying it in onDestroy() of activity A.

For simplicity, there is one button in activity B named "Destroy Activity A". When a user clicks on button, activity A should be destroyed.

Normally all of this is running smoothly without any issues,but the problem occurs in following scenarios:

1) Suppose I am in activity B and i press the Home key to move the application to the background then if i use other resource-heavy applications, Android system will kill my application to free memory. Then If I open my application from recent tasks, activity B will be resumed, and it's onCreate(), onResume() etc method will be called. Now I press button to destroy activity A, but activity A has already been destroyed, so activity A's onCreate(), onResume() etc methods will not be called until and unless i go to activity A by pressing the back button. Thus broadcast receiver is not registered to listen for the event.

2) The same problem will arise when user has selected "Don't keep activities" from Developer options in the device's settings.

I have been looking to solve this issue for a long time, but i am unable to find a proper answer. What is the best way to handle this scenario? Is this an Android bug? There should be some solution for this issue.

Please help me.

dharmendra
  • 7,835
  • 5
  • 38
  • 71
Smeet
  • 4,036
  • 1
  • 36
  • 47
  • 1
    *...there is broadcast receiver registered that listen for a particular event which will finish activity A...* - If the activity is already destroyed(in the scenarios you mention) what's the purpose of registering that receiver to get an event that will finish the activity? Are you doing something extra besides killing the activity? – user Apr 13 '15 at 09:46
  • What would be wrong if `A` was destroyed with the OS? –  Apr 13 '15 at 09:51
  • 1
    When i press back button, i don't want to see Activity A if i pressed "Destroy Activity A" from "Activity B". – Smeet Apr 13 '15 at 10:07
  • read [this](http://stackoverflow.com/questions/2033914/quitting-an-application-is-that-frowned-upon) completely better to understand your first issue and also you can solve the second issue by check and ask user to disable "Don't keep activities" option refer [this](http://stackoverflow.com/questions/11649949/how-to-know-dont-keep-activities-is-enabled-in-ics) . – PAC Apr 13 '15 at 15:44
  • 2
    Whatever the real problem is here should be handled by using other techniques, such as `Intent` flags or `` attributes to help control the back stack. If you have one activity trying to destroy another activity, you're doing it wrong. – CommonsWare Apr 14 '15 at 15:26
  • I am curious in how your Activity B "destroy" Activity A? It sounds very suspiciously like you are doing something you are not supposed to do. – Kai Apr 15 '15 at 02:31
  • hi CommonsWare, welcome. Yes there are some situations, in that i can use activity's flag like clear top etc... I want to destroy some random activities... I have this same case in my application. That's why i am using BroadcastReceiver to destroy the activities. – Smeet Apr 15 '15 at 04:16
  • @CommonsWare, I have posted an answer based on intent flags. Would you like to comment on that? I am not sure why it got a downvote. – A.J. Apr 15 '15 at 11:08
  • Why would you want A to be removed by a button in B. How or why would an end user care. You can have a BroadcastReceiver class always listening on and call the Activity A's action whenever required. – ichthyocentaurs Apr 18 '15 at 13:49

7 Answers7

6

If your Activity A has destroyed by Android OS itself then there are no way to track.

Some people has suggested to track that Activity A by listning event in onDestroy method BUT if your Activity killed by system OS then note here it wont call those method .

dharmendra
  • 7,835
  • 5
  • 38
  • 71
3

This cannot be fixed while keeping your current broadcast logic.

Killing activities from the backstack, imo, is not a correct approach. You should strongly consider changing the logic of your navigation.

But if your project is big and time is a problem, and refactoring is out of the question, A.J. 's approach works, but you mentioned that you have lots of activities that needs to be killed, his solution becomes very tricky to implement.

What I suggest is the following. This might not be the best idea, but I cannot think of another. So maybe that could help.

You should have the following:

  • A Base Activity for all your activities.
  • A ArrayList<String> activitiesToKill object at the application level. (If you did not extend Application you can have it as static variable

First we have to make sure that the activitiesToKill is not lost when the OS kills the app in low memory. In the BaseActivity we save the list during onSaveInstanceState and restore it in the onRestoreInstanceState

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putSerializable("activitiesToKill", activitiesToKill);
}

private void onRestoreInstanceState(Bundle state) {
    if (state != null) {
        activitiesToKill = (ArrayList<String>) state.getSerializable("activitiesToKill");
    super.onRestoreInstanceState(state); 
}

}

The idea here is to save which activities should be killed in the list, by using their name.

The logic is as follow:

Let's say you have Activities A, B, C, D and E

From Activity E, you press the button and you want to kill B and D

When you press the Button in E, you add the names of B and D to the activitiesToKill object.

activitiesToKill.add(B.class.getSimpleName()
activitiesToKill.add(D.class.getSimpleName()

In the onCreate method of the BaseActivity, we have to check if the

if(savedInstanceState != null)
{
    //The activity is being restored. We check if the it is in the lest to Kill and we finish it                
    if(activitiesToKill.contains(this.getClass().getSimpleName()))
    {
        activitiesToKill.remove(this.getClass().getSimpleName())
        finish();
    }
}

Make sure to remove the name of the activity if it is killed through the broadcast.

So basically this is what happens in every scenario.

If the app is running normally, and you click the button, the broadcast gets sent and B and D will get killed. Make sure to remove B and D from the activitiesToKill

If the app was killed and restored, you press the button, the broadcast will have no effect, but you have added B and D to the activitiesToKill object. So when you click back, the activity is created and the savedInstanceState is not null, the activity is finished.

This approach consider that activity E knows which activities it has to kill.

In case you DON'T know which activities to kill from E, you have to modify this logic slightly:

Instead of using an ArrayList use a HashMap<String, bool>

When Activity B is created, it will register it self to the hashmap:

activitiesToKill.put(this.class.getSimpleName(), false)

Then from Activity E, all you have to do is set all the entries to true

Then in the on create of the base activity you have to check if this activity is registered in the activitiesToKill (the hashmap contains the key) AND the boolean is true you kill it (don't forget to return it to false, or remove the key)

This ensure that each activity register itself to the HashMap and Activity E doesn't have top know all the activities to kill. And don't forget to remove them in case the broadcast kills them.

This approach also ensure that the activity is not killed when opened normally from an intent because in that case onSaveInstanceState would be null in the onCreate, so nothing will happen.

More advanced checks can be accomplished in case you have groups of activities that needs to be terminated through different conditions (not only a button click) so you can have a HashMap of a HashMap to divide them in categories.

Also note, that you can use getName instead of getSimpleName if you have multiple activities with same name but different bundles.

I hope my explanation is clear enough as I wrote it from my head, let me know if any area is not clear.

Best of luck

Y2theZ
  • 10,162
  • 38
  • 131
  • 200
  • You have mention like : if(savedInstanceState != null) { //The activity is being restored. We check if the it is in the lest to Kill and we finish it if(activitiesToKill.contains(this.getClass().getSimpleName())) { activitiesToKill.remove(this.getClass().getSimpleName()) finish(); } } this.getClass().getSimpleName() this give me the name of BaseActivity and not the activities to be killed like A, B, C etc... It will never satisfies the condition. Have to you checked this? – Smeet Apr 20 '15 at 06:58
  • @Smeet Weird, because I just tried it and I got the child simple name. can you try getName instead? – Y2theZ Apr 20 '15 at 09:57
  • Great answer. Working fine. Thanks Youssef for your effort and time. – Smeet Apr 20 '15 at 12:28
  • @Smeet Great, Glad I could help. However I strongly suggest refactoring the entire navigation, and try to find a logic to avoid closing activities from the backstack. But I know that might be a year project :) – Y2theZ Apr 20 '15 at 12:45
  • Ya managing back stack in Android is very difficult. I face this issue when user press home key and my application goes to background. And long time user return to my application and if at the same time session is expired, then i want to kill some of the back stacked activities. However i try to find out the best solution and post here if any. – Smeet Apr 20 '15 at 14:40
1

One of the main rules with Activities is you can't rely on any activity being alive except the foreground activity. The thing you're trying to do with broadcasts has nothing to do with back stack -- back stack doesn't guarantee all activities are alive at all times, but it will make sure they're recreated when it's time to go foreground.

In your example (if my understanding of what you're aiming to do) you need to navigate to something underneath A -- say, Activity Z, and the stack looks like this: Z-A-[B]. There's normal course of events where you hit back and it takes you to A, then after another hit -- to Z but in a certain case (say pressing a button) you want to move back to Z bypassing A -- this is a classic case to use FLAG_ACTIVITY_CLEAR_TOP and launch Z explicitly:

Intent intent = new Intent(this, ActivityZ.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

This will finish both B and A, and deliver the intent to Z. You will, probably also need the FLAG_ACTIVITY_SINGLE_TOP flag, pay close attention to the description of FLAG_ACTIVITY_CLEAR_TOP, there's some trickery you should consider.

Ivan Bartsov
  • 19,664
  • 7
  • 61
  • 59
0

I don't know if it's possible to handle this on a "proper" way.

What it comes to my mind, is to flag the A activity in some way. You can't use startActivityForResult() because you will receive the result before onResume() is called i.e. UI was already inflated.

If you using an Otto, you could try with a sticky event. Otherwise you will need a singleton to handle the flag or save it to shared preferences.

You will have to check that flag on your onCreate() method before calling setContentView(), if the flag is true just finish the activity.

Axxiss
  • 4,759
  • 4
  • 26
  • 45
  • This is the simple scenario given to make question more clear and easy, but in real complex scenario i have to manage lots of flags and to do like this, it looks like flickering effects to user. Because it will be visible to user for few moments and then we destroy. – Smeet Apr 10 '15 at 12:47
  • When an activity is being created, if you don't call setContentView nothing is shown in the screen. A flicker will be shown if you call setContentView and then kill the activity. – Axxiss Apr 10 '15 at 12:53
  • Yes it will be shown with white or black background. However the main problem is that when i can do this? I should receive particular event so that i can destroy it. – Smeet Apr 10 '15 at 13:05
0

With the information you have given, how about you register the broadcast in onCreate of Activity B after checking if its registered already or not. If onDestroy of Activity A has been called in either of the scenarios you have mentioned, then the deregister of the Broadcast would have been called. So in that case, you can register your Broadcast in onCreate of Activity B, so that you can listen to it, even if you have only the Activity B in your backstack.

Arun Shankar
  • 2,295
  • 16
  • 20
0

Have you considered using Sticky Broadcast? Also you can register your receiver on application level (in manifest) and listen to this event regardless of Activity A state.

But, like already said Youssef, killing activities from the backstack is not a correct approach. You should strongly consider changing the logic of your navigation.

Community
  • 1
  • 1
Dmide
  • 6,422
  • 3
  • 24
  • 31
-1

Many solutions came to my mind but as you have not provided much information about your app, so I think this should work in general.

Instead of firing a broadcast to kill Activity A, just execute the following code when the "Kill Activity A" button is pressed in Activity B.

        Intent intent = new Intent(getApplicationContext(),
                ActivityA.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        intent.putExtra("EXIT", true);
        startActivity(intent);

Add the following code in activity A

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    if (intent.getBooleanExtra("EXIT", false)) {
        finish();
    }
}

protected void onCreate(Bundle savedInstanceState) {
    //Ideally, there should not be anything before this
    super.onCreate(savedInstanceState);
    if(getIntent().getBooleanExtra("EXIT", false)){
        finish();
        return;
    }

In the manifest set "singleTop" launch mode for activity A.

<activity
    android:name=".ActivityA"
    ...
    android:launchMode="singleTop" />

This will have the following consequences:

  • If Activity A is already running it will brought to the front of the activity stack and finished, thus removing it from the stack.
  • If Activity A has been destroyed but still present in the activity stack (to be launched when back button is pressed), it will be started, brought to front and finished, thus removing it from the activity stack.
  • If Activity A has already has already been destroyed and not present in the activity stack, and you still press the "Remove Activity A" button, it will be started, brought to front and finished.

Generally, you should not see any flicker.

Based on this idea, you may build a better performing solution for your particular app. For example, you may use the FLAG_ACTIVITY_CLEAR_TOP and finish the Activity A in onBackPressed() of Activity B.

A.J.
  • 1,520
  • 17
  • 22
  • You are correct. But i have given a simple scenario. I want to destroy multiple activities so i used Broadcast Receiver. For example, there are 10 activities in backstack(This is example, it may be any number of activities that i don't know, based on scenario of user navigation). I want to destroy activity 2nd, 4th, and 5th.. How can I destroy it? I can not do reorder to front because i don't know the back stacked activities. – Smeet Apr 14 '15 at 05:00
  • If you know which activities to destroy, you can destroy them all with this method. Just set the described intent and sent it to all of them, no issue with that. – A.J. Apr 14 '15 at 07:48