We have crash reports from our App that we cannot explain. The crash occurs in MyActivity because an expected "extra" is missing from the Intent. We have extensive logging in our app and this is the sequence of lifecycle callbacks that we see when this occurs:
06:04:22.574#a.b.c.MyActivity.onCreate() with flags 0 a.b.c.MyActivity#80773a0
06:04:22.592#a.b.c.MyActivity.onStart() a.b.c.MyActivity#80773a0
06:04:22.596#a.b.c.MyActivity.onResume() a.b.c.MyActivity#80773a0
06:04:23.601#a.b.c.MyActivity.onPause() a.b.c.MyActivity#80773a0
06:04:23.614#a.b.c.MyActivity.onNewIntent() with flags 30000000 a.b.c.MyActivity#80773a0
06:04:23.654#a.b.c.MyActivity.onResume() a.b.c.MyActivity#80773a0
We log the object ID (in this case 80773a0) so that we can tell how many instances of a given Activity are in use.
You can see (due to the object ID) that there is only a single instance of MyActivity involved here. The Activity is created, started and resumed as usual. There are no special flags in the original Intent (we log the flags in onCreate() and onNewIntent()).
Approximately 1 second after onResume() we see a call to onPause(), immediately followed by a call to onNewIntent() and then onResume(). The Intent passed to onNewIntent() contains flags 0x30000000 which is FLAG_ACTIVITY_NEW_TASK (0x10000000) and FLAG_ACTIVIY_SINGLE_TOP (0x20000000). The Intent passed to onNewIntent() has no extras, which cause the app to crash.
We have double-checked and there is absolutely no place in our code where we set both these flags in an Intent.
We do not understand what is causing the calls to onPause(), onNewIntent() and onResume() and we do not understand why the Intent has this set of flags nor why it does not contain the necessary "extras". There is only one place in the app where this Activity is launched and the necessary "extras" are put in the Intent before startActivity() is called.
The Activity in question works correctly almost all the time, so there is no general problem with it. There must be some specific behaviour that causes this problem and we have exhausted our ideas about what it might be.
The manifest entry for MyActivity has no special launch mode specified.
If possible then please extract parts of code necessary to examine this issue because we're shooting blind.
What I can read in docs is:
An activity can never receive a new intent in the resumed state. You can count on onResume() being called after this method, though not necessarily immediately after the completion this callback. If the activity was resumed, it will be paused and new intent will be delivered, followed by onResume(). If the activity wasn't in the resumed state, then new intent can be delivered immediately, with onResume() called sometime later when activity becomes active again.
So it seems that your case is exactly matched in that part of docs!
I assume some basterd is calling startActivity with flag FLAG_ACTIVITY_SINGLE_TOP twice in a row.
I think this can happen for example if you do startIntent from button's onclicklisteners - fast clickers may be able to do it twice. Solution for that comes with RxJava debounce.
Related
I have an application that connects to a server and can hold the connection for a long time.
I have another application that automates stuff. E.g. it can launch the above client based on rules and pass parameters to make it do stuff without user action (inside an Intent).
Now: This works fine if the client application isn't running or doesn't have focus. I catch onResume() and extract the parameters from the intent.
However if the client already is in the foreground (because the user didn't close it after last use) and it is called from the automate-app (e.g. based on time) it keeps focus all the time and doesn't lose it. So how do I detect it being launched again? onResume() doesn't seem to fire, nor does onNewIntent().
I'd appreciate any ideas.
The documentation states the following instructions:
This is called for activities that set launchMode to "singleTop" in their package, or if a client used the FLAG_ACTIVITY_SINGLE_TOP flag when calling startActivity(Intent). In either case, when the activity is re-launched while at the top of the activity stack instead of a new instance of the activity being started, onNewIntent() will be called on the existing instance with the Intent that was used to re-launch it.
An activity will always be paused before receiving a new intent, so you can count on onResume() being called after this method.
Note that getIntent() still returns the original Intent. You can use setIntent(Intent) to update it to this new Intent.
If you follow everything, your method onNewIntent() should be called.
Say i have started activity A with new Intent(context,class) i thats has sensitive data in it, when is this intent gets destroyed? in particular, in what cases among the following would getIntent() in Activity A's code return the exact same intent i?
Press on the activity's task on android's task manager
App icon was clicked and the activity was recreated and brought to front
Ive tried it with my app, iand i get weird results... normally it doesnt get the same intent, but sometimes it seem that it does, so i am not sure whats going on, anyway If i can be returned from any of the upper options how to avoid it?
I think a glance on lifecycle of an Intent would be helpfull if any1 know of any documentation regarding this...
Say i have started activity A with new Intent(context,class) i that's has sensitive data in it, when is this intent gets destroyed?
as long as there's object/class that holds reference to your Intent object - it will not be garbage collected. the Activity (Activity A) holds reference to the intent that started it, so as long as Activity A object is not garbage collected - then i also won't be garbage collected.
important comment: onDestroy() activity callback and class distractor are to different things!!!
in what cases among the following would getIntent() in Activity A's code return the exact same intent i?
assuming you are not calling setIntent() explicitly:
1) Press on the activity's task on android's task manager:
if the activity was previously stopped in reaction to back button navigation or someone called finish() explicitly on it, then the activity passed on the onDestroy() callback. in that case - pressing on the "application" from the recent tasks manager would re-created the activity with a new intent from scratch, and thus - getIntent() would bring this new intent that don't contain your extras or other overloads.
otherwise (the activity was sent to background via home button, or other activity started on top of it) : when you'll launch it back from the recent task - it will be intent object with the original extras you passed it before...
2) App icon was clicked and the activity was recreated and brought to
front
basically the same cases I mentioned in (1) apply to (2) , but basically it depends on two more things:
the intent flags that the specific launcher you are using is overloading on the intent it creates when it launch your activity.
the launch mode and activity flags you overloaded on the intent that you used to start your own activity.
assuming you are not using any of the above, and you are using normal good functional launcher application - the behavior would be exactly as I explained in (1)
In my application there is an activity started using the FLAG_ACTIVITY_SINGLE_TOP and FLAG_ACTIVITY_CLEAR_TOP flags because I want to make sure that only one instance of that activity is at the top of the stack and all activities on top of the old instance are closed. So far so good.
Next I wanted to test if the activity restores correctly after being created more than once and successively destroyed. I take care to manually set the intent using Activity.setIntent() when Activity.onNewIntent() is called so that the most recent intent is returned by Activity.getIntent(). In order to test that I activated the "Don't keep activities" option in the developer options, but the intent returned by Activity.getIntent() when the activity is re-created is the very first intent that created it and not the most recent one.
This happens on JB and ICS, I haven't tested it on older versions. Am I doing something wrong or did I misunderstand something in the docs?
If you kill your app while it is in the foreground, this is not the same as when Android kills your app (which it will only do when your app is in the background). If you kill and then restart the app, it is like starting it all over again from scratch. There is no "restore" going on here. If you add logging to onCreate() you should see that after you kill and restart your app, the Bundle that is passed to onCreate() is null.
Unfortunately it is pretty difficult to simulate what happens when Android kills your app.
EDIT: Added more stuff after OP's comment
Here's a concrete example for discussion purposes. First without the developer option "Don't keep activities":
ActivityA is the root activity
We start ActivityA
ActivityA.onCreate() is called
ActivityA now starts ActivityB
ActivityB.onCreate() is called (The activity stack now contains ActivityA->ActivityB)
ActivityB starts ActivityA with FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP and an extra "foo"
ActivityA.onNewIntent() gets called with the Intent containing FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP and an extra "foo"
ActivityB.onDestroy() is called since the activity stack was cleared back to ActivityA
Now, let's do the exact same thing but enable the developer option "Don't keep activities" (I've highlighted in bold the stuff that is different from the previous scenario):
ActivityA is the root activity
We start ActivityA
ActivityA.onCreate() is called
ActivityA now starts ActivityB
ActivityB.onCreate() is called (The activity stack now contains ActivityA->ActivityB)
Because ActivityA has stopped, Android destroys it and calls ActivityA.onDestroy()
Note: The activity stack still contains ActivityA->ActivityB, even though there is no instance of ActivityA at the moment. Android remembers all the state
ActivityB starts ActivityA with FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP and an extra "foo"
Since Android has no instance of ActivityA to reactivate, it needs to create one, so it does and then...
ActivityA.onCreate() is called with the same Intent that it was called with when the original instance of ActivityA was created (ie: LAUNCH intent with no flags and no extras)
ActivityA.onNewIntent() gets called with the Intent containing FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP and an extra "foo"
ActivityB.onDestroy() is called since the activity stack was cleared back to ActivityA
The important thing to note here is that Android always calls onCreate() whenever it creates an activity instance. Think of it like the constructor of an Activity. If Android has to recreate an instance of an Activity because the process was killed or the activity was destroyed, then it will instantiate a new object, then call onCreate() and then (if necessary) it will call onNewIntent().
When you call setIntent() this doesn't actually change the Intent that Android saves and restores. That only changes the in-memory Intent that will be returned from a call to getIntent().
I hope this is clearer now. If not, please let me know.
Not sure if you've found a solution to this or not, but overriding the onNewIntent(Intent theNewIntent) method for the target activity & calling setIntent(theNewIntent) solved it for me.
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
/*
* This overrides the original intent.
*/
setIntent(intent);
}
While experiencing a problem similar to this question, I began to wonder why we explicitly have to call setIntent when overriding onNewIntent, and why this code isn't performed already by super.onNewIntent.
#Override
public void onNewIntent(Intent intent)
{
super.onNewIntent(intent);
// Why isn't this performed by the framework in the line above?
setIntent(intent);
}
Intent objects are persistently attached to the Activity, Service and other components for as long as those components are running. They don't simply go away because you moved off to another application. The reason for this is because Android may kill the process at any time, but the user may still want to go back and continue what they were doing. This makes Intents ideal for storing or transmitting small (and sometimes large) bits of information, via the Extras.
The onNewIntent() method is specifically for handling application components that are more persistent and hence may be called more than once during its LifeCycle but needs to keep track of the reasons why it was called (and hence the data it was called with). Whether you call setIntent() or not depends on what you need to do.
If you don't care why it was subsequently called, you may keep the original Intent by not calling setIntent(). This is particularly useful when your Activity (or some other component) does the same thing no matter who called it and what data it provides.
If you have a need to respond to each event individually, then you must at least store the new Intent's information. This means you may avoid setIntent(), however, then none of the components it links to will have any of the Intent information unless you send it to them directly. This may be the desired behavior for an application that cannot insure the original Intent was completely handled.
If you need to respond to each Intent individually and the original Intent does not matter, then you use setIntent(). This discards the original Intent, which is still in there... and places the new Intent so that if the user moves away (yet again), they will come back to the same place.
The reason why super.onNewIntent() does not handle this is because the core component classes cannot determine whether or not the new Intent is more important than the old one. All it cares is that it has an Intent, not what it is. This is why we override methods like these, so that we determine what is important and what is not. The general feeling is that base classes like Activity can use whatever data we have, in whatever ways it wants to (unless we override and tell it otherwise). However, they should not (and often cannot) get rid of our data unless we tell them to specifically. This is an argument you really don't want to have with some programmers. hehe
Hope this helps.
The docs for onNewIntent state: "Note that getIntent() still returns the original Intent. You can use setIntent(Intent) to update it to this new Intent." My guess is that onNewIntent exists so that your activity can be notified and the super probably does nothing.
I've just removed the setIntent(intent) code from my onNewIntent(Intent intent) implementation.
The reason:
My MainActivity is single top. While my app runs it launches other activities like the camera activity. When the app returns to the MainActivity, in onResume() the (last) intent returned by getIntent() would be re-processed even if it would already have been processed before.
My new implementation (working so far):
Store the intent from onNewIntent(Intent intent) in a private instance field. In onResume check this field for not null and reset it immediately to null, processing the intent once and only once.
See also https://groups.google.com/forum/#!topic/android-developers/vrLdM5mKeoY
I added TextToSpeech to my app, following the guidelines in the following post:
http://android-developers.blogspot.com/2009/09/introduction-to-text-to-speech-in.html
and now my onDestroy is no longer called when the back button is pressed.
I filed a bug report regarding this: http://code.google.com/p/android/issues/detail?id=7674
Figured i should also ask here if someone else has seen this, and found a solution?
It seems that it is the intent that causes the problem, i.e. the following:
Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, MY_DATA_CHECK_CODE);
If I skip this intent, and just go ahead and create a tts-instance, it works fine.
Any clues to what is wrong with this intent?
Think I've figured this one out.
My problem is that I was assuming that onDestroy would be called when my activity finished, so that I could store the state (and preferences, et c). And I assumed that onDestroy would always happen before a new instance of the activity was created, so that the new instance in onCreate could load the state stored by the old instance.
This does not hold in general. It does not even hold true for onStop.
The solution for me was simply to save what I wanted in onPause. It seems I can count on this one being called before any new instance can be created. But since onPause is called in many cases where I don't need to save, I also check isFinishing(). I.e. if isFinishing() in onPause, then I save.
Note that it didn't seem to matter if I launched my activity in singleTop mode, I would still get two "alive" instances. One which was on its way to being destroyed (onPause was called but was yet to enter onStop or onDestroy) and one which was in onCreate.
Anyway, I hope I've solved it now.
Reproduced.
It appears that the key is where you call your initTTS() method (or equivalent) from.
If it is called from onCreate(), I also see the behaviour above (onDestroy never called).
Hinted by the doc for startActivityForResult (where calling from onCreate is a special case), i tried invoking the intent via a delayed message to my own Handler.
Now, onDestroy is called again!
(also commented this on your bug report)