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
Related
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.
Any intent that gets passed either when an activity is created or as a param to onNewIntent(), lives as long as activity has not been destroyed. Well, even if the activity is launched from recent apps section after being destroyed but that's another issue.
My question is what should be the best practice in such cases to avoid duplicate processing due to old intent when activity is started/resumed from background or 'created' from recent apps section.
Say, am pulling getDataString() for example for analytics which should ideally be tracked only when the app has actually started via a deeplink. But it's very much available each time in the call-chain of onStart(), inside the old intent. What is recommended?
intent be set to null in onStop()? //seems most logical to me. pitfalls?
some local checks to ignore values?
setting temporary field(s) in onStop() to identify if it's an old one?
After trying out various cases, here is what I found. Some of these opinions may have best suited my codebase but am guessing they are generically applicable.
setting intent null is risky and can cause an NPE since we can't
guarantee what all is intent being used for in the codebase, including being used indirectly by some internal api, e.g activity getReferrer
local checks again is anyway a weak way to fix this.
Similar to above but much cleaner. Stored a field in intent itself indicating if it has been 'consumed' [for any processing]. Function looks like:
private void markIntentValuesTracked(final boolean status){
if(getIntent() != null){
getIntent().putExtra(LAUNCH_INTENT_VALUES_CONSUMED, status);
}
}
method calls:
onCreate():
boolean isOldIntent = (getIntent().getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0;
markIntentValuesTracked(isOldIntent);
onNewIntent(): markIntentValuesTracked(false);
onStop(): markIntentValuesTracked(true)
There seem to be (at least) two ways to send Intents in Android:
PendingIntent.send(...)
Activity.startIntentSenderForResult(PendingIntent.getIntentSender(), ...)
Other than the fact that the latter only works starting API level 5 and that the results are passed back in a different way (via PendingIntent.OnFinished vs. Activity.onActivityResult(...)) is there any fundamental difference between the two?
I find the first one a lot more convenient as it can be entirely encapsulated inside a library without requiring the calling activity to override onActivityResult(...) to forward the result (like this: yuck!). Is it ok to still use that approach?
A quick clarification, because I've seen someone complain about this on another question: The methods above are not static methods. I wrote them that way simply for readability.
Seems like these two approaches are very different:
The start...forResult(...) methods start an intent or sub-activity in a way that allows for a result to be returned to the activity that executed the start...forResult(...). The result will be passed back to the activity's onActivityResult(...) method.
All other ways of launching intents or sub-activities (including PendingIntent.send(...)) act in a fire-and-forget-manner and don't allow for any results to be returned. The OnFinished handler is called as soon as the launch is sent, whether or not it takes a while to complete. The data passed into this handler, therefore, does not necessarily have anything to do with what you would otherwise receive via onActivityResult(...). In fact, in my case, the OnFinished handler is always called right away, before the dialog of the sub-activity even shows up, with a resultCode of Activity.RESULT_CANCELED.
What a mess...
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.
I currently have an activity's onCreate() method set to capture an intent the first thing it does. The intent will always have an extra int "ACTIONCODE" that determines what the activity should do.
Activity A might want activity Z to set up variables for the first time, so it calls startActivity(includedIntent) which has some extra int ActivityZ.SET_UP_FIRST_TIME (which is a constant in Activity Z.) Activity B might want to change the variables around a bit, so it does a startActivity(includedIntent) with the intent now including an extra int ActivityZ.CHANGE_VARIABLES as well as other data to change those variables.
Activity Z could just be a bunch of textviews that display what its variables are. Depending on what ACTIONCODE it receives from getIntent(), it will perform things just as it needs to.
I feel like I have a lot more control over the activities in my app my doing this, yet I fear as though it might be a really naive and inefficient implementation. I basically do not trust (nor fully understand) onStart(),onResume(),onPause(),and onStop(). From what I've heard, there is no guarantee that an activity will always return back to onResume(). While it was in its onPause() or onStop() state, it could have been killed or completely destroyed by the system, and thus would only return back to onCreate() again. It's the only method that I trust.
I even do all of my data saving from onCreate(). Why? I heard that if an activity is in onPause() or onStop(), it is liable to be killed, and may not even finish running through all the lines included in the overridden onPause() or onStop() method. I don't want to perform a data saving function from within a method that could abruptly stop!
Is my thinking wrong here? Are my fears irrational? If so, what should I do instead?
Here are my app screenshots by the way:
Thanks, Luksprog. After some reading, I think I can trust the activity lifecycle better now.
I will implement setting up in onCreate, loading in onResume, and saving in onPause! Hopefully that's correct.
Now all I need is some advice on the right way to set up activities and fragments. I just need some general rule of thumb for deciding when and when not to start a new activity, or if it may be better to use fragments.