activity not calling onDestroy() after finish()

10k Views Asked by At

I have three activities, let's call them ONE, TWO and THREE. From activity ONE, a button press starts activity TWO. From activity TWO, a button press starts activity THREE.

Simple enough.

Now, activity THREE requires a bit of data accessible from the application (which may or may not be there). In THREE's onResume() method, a check is made for the data and the activity is finished if it doesn't exist, like so:

@Override
protected void onResume() {
    super.onResume();

    /* ... get data from app ... */

    if (data == null) {
        Toast.makeText(this, "Data not found", Toast.LENGTH_SHORT).show();
        finish();
        return;
    }

    /* ... other logic ... */
}

When data == null, THREE finishes, destroys and returns to TWO. All is well. Now in TWO, pressing the back button calls finish() on TWO, but TWO never calls onDestroy(). The user is returned to ONE just fine, but any subsequent intent to go back to TWO does not work and no errors are thrown. TWO is left in a state where it has been finished (and paused), but never destroyed, and as a result cannot resume.

So, why is THREE important in this case? If I remove the finish() call in the code block above, and rely on a "natural" finishing of THREE (by using the back button), when the user gets back to ONE, TWO has been destroyed correctly.

OK here's where it gets really confusing...

Leaving the finish() call in place, I can alleviate the hangup by launching THREE directly from ONE, then "naturally" finish it (back button). After THREE is destroyed (a second time), TWO opens as expected.

Everything I've read has said I should be ok calling finish() within onResume() for an activity. But in this case, it is leaving something in a bad state and prevents me from destroying the calling activity down the line.

Ideas? or did I turn your brains inside out?

EDIT:

Further exploration uncovered this gem...

Surrounding the finish() call in THREE with a postDelay() handler of around 500 millis will allow TWO to close as expected. Like this:

@Override
protected void onResume() {
    super.onResume();

    /* ... get data from app ... */

    if (data == null) {
        Toast.makeText(this, "Data not found", Toast.LENGTH_SHORT).show();
        Handler h = new Handler();
        h.postDelayed(new Runnable() {
            @Override
            public void run() {
                finish();
            }
        }, 500);
        return;
    }

    /* ... other logic ... */
}

Not exactly my idea of a fix...

3

There are 3 best solutions below

3
On

Since I can not comment I will write here.

I am not 100% sure if I followed you but right near the end you mention that

After THREE is destroyed (a second time), TWO opens as expected.

What do you mean by that since if I followed you correctly you said that you open TWO with a button in ONE and THREE with a button in TWO. So how can TWO open as expected, or you mean then it goes to onDestroy() when you exit it?

What I am aiming at is that perhaps you open more instances of the same activity, as is mentioned in the here if you take a look at Figure 3.

3
On

an activity is not finished / destroyed on back pressed.

Use

 @Override
    public void onBackPressed()
    {
        finish();
    }
0
On

Since this issue occurs only from onResume() [see comments] I would argue it sounds like an issue with state persistence rather than tinkering with the stack.

Take a look at this Stack Over Flow Question Saving Android Activity state using Save Instance State

If onPause() you save "data" (I do not know its object type as you have not said) into a Bundle then onResume() go get it.

Another example found here by Google, uses SharedPreferences instead of a Bundle to achieve the same result.

Overall this will mean you can handle/prevent "data" being null as you can restore it therefore saving the effort of destroying THREE and trying to fiddle with the stack, simply put in your terms keeping the workflow "natural".