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)
Related
I am not understanding how android activities are managed.
I have an activity and every once in a while i have noticed that, when my app goes into the background, android destroys whatever the current activity is (say Activity3) and several other singletons and objects etc. Thats fine. The problem is when the app is resumed then intuition tells me that since android has destroyed the activity and objects for memory or whatever, then android would just restart the app completely from Activity1 so all the objects and data members would get properly initalized.
NOT SO!
It seems that when my app is resumed, the Activity3 is recreated and onCreate is called with the same parameters as it was the first time (when it was called from Activity2) only this time all the singletons and other objects that were initialized in Activity1 and Activity2 are recreated with their default values and are rendered useless.
How is this a safe policy/technique? How can android just randomly destroy objects and activities and then when the user resumes just call onCreate on the recent activity and expect everything to be hunky doory and NOT have to go through the proper startup procedure/initialization?
UPDATE / SOLUTION
Thanks to the commentors for their excellent info.
ACCORDING TO ANDROID DOCUMENTATION
onCreate
Bundle: If the activity is being re-initialized after previously being shut down then this Bundle contains the data it most recently supplied in onSaveInstanceState(Bundle). Note: Otherwise it is null.
THEREFORE what I ended up doing is I set TWO flags. One in onSaveInstanceState in the Bundle so to know that it is a valid Bundle set by me. The other in the class itself to determine if onCreate was called because of recreation or Auto-Rotation. And so in onCreate I checked to see if onSaveInstanceState is not null, check the Bundle flag, and check bInit (which defaults to false). If both flags are true then it means android dumped and destroyed our apps memory and the safest way to ensure everything is initialized again in a linear-style application is to just restart it and launch the beginning activity.
public class SomeMiddleActivity extends AppCompatActivity
{
private static boolean bInit = false; // only way it will be false again is if android cleared our memory and we are recreating
#Override
public void onSaveInstanceState(Bundle state)
{
// set a flag so that onCreate knows this is valid
state.putBoolean("StateSaved", true);
super.onSaveInstanceState(state);
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
// this must be called first always for some reason
super.onCreate(savedInstanceState);
if (savedInstanceState != null)
{
if (savedInstanceState.getBoolean("StateSaved", false) && !bInit)
{
// we were recreated... start app over
Intent intent = new Intent(getApplicationContext(), Startup.class);
startActivity(intent);
finish();
return;
}
}
bInit = true; // this will stay true until android has cleared our memory
.......
}
Although this has worked thus far, if anyone has a different suggestion let me know. I will be posting another article on this.
And FYI: the onSaveInstanceState(Bundle, PersistableBundle) version of onSaveInstanceState is never called ever so I dont know why they even implement it. (?)
#goldenb #Rishabh Thanks to goldenb and Rishabh for the insight.
Android, if destroys, also gives you tools to handle it.
Mobile devices have limited amount of memory which needs to be shared among Applications running simultaneously. Thus, smart resource allocation is necessary. The Apps running on foreground are used by End-User and gain high priority for better performance and user experience. Thus, applications running in background need to release the resources to suffice the memory requirements for foreground applications. Hence, background applications are destroyed (not completely) sometimes (in case of low memory).
Android Activities have Callbacks likes onSaveInstanceState() and onRestoreInstanceState() which enable you to save your current state of Activity (i.e., values of variables) when it is destroyed and retrieve them when the Activity is recreated.
You can get more information from here: How to save and retrieve the state of Activity using onSaveInstanceState and onRestoreInstanceState.
You can perform validations on the retreived state to ensure the Activity performs exactly as it was doing pre-destruction. You would find it very easy and logical once you get hands-on it.
Just giving my 50 cents on the issue. The correct way to deal with the issue of an activity being killed by the system for its resources in background is a common problem in android and according to Google the solution for this is:
onPause() is where you deal with the user leaving your activity. Most
importantly, any changes made by the user should at this point be
committed (usually to the ContentProvider holding the data).
Emphasis is mine. But what this means is that the Android lifecycles are designed so that under normal conditions onPause should be called as an Activity or Fragment is sent to the background. They hint at this in several of the android documentation pages:
As your activity enters the paused state, the system calls the onPause() method on your Activity, which allows you to stop ongoing actions that should not continue while paused (such as a video) or persist any information that should be permanently saved in case the user continues to leave your app.
Also worthy of your attention: if you wish that views are restored during Activity recreation, you should have set the ID attribute of all views ;)
Note: In order for the Android system to restore the state of the
views in your activity, each view must have a unique ID, supplied by
the android:id attribute.
PS.
You were wondering why onSaveInstanceState(Bundle, PersistableBundle) is not called, one possibility is that you do not have the right activity attribute set
This is the same as onRestoreInstanceState(Bundle) but is called for
activities created with the attribute persistableMode set to
persistAcrossReboots..
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.
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
So I have the following:
A Common class that many of my Activities access in my android application, via setting the class in my manifest:
<application android:name="com.dev.games.phraseparty.Common"... />
Now, within this class I have several objects that are constructed to preserve application state and common services to perform applications that are constructed in the Common constructor
ie
GameStateVO gameState;
public Common()
{
gameState = new GameStateVO();
}
My problem is that my Activity has an Admob ad. When the user clicks on the ad, it calls the webbrowser intent to open the ad URL in a webbrowser.
Now, when I click back from the webbrowser launched by admob, it takes me back to the caling activity and the OnCreate method is called.
This activity then gets a null pointer exception because it will do something like:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Common common = this.getApplication();
//null pointer here since the game state VO is null since the Common has lost its state.
int score = common.getGameState().getScore();
}
If you have no active foreground activity, then your process is ripe for shutdown by the OS to obtain more resources. The browser app in particular i've noticed uses a lot of resources and can quickly lead to background activities and later processes being killed off. Having a service can help keep your process around, but even then it can still be killed off if needed. I think you'll need to use the activity lifetime cycle method to save & restore your state. See the process lifecycle section of the docs for more info
You might want to look into implementing the onSaveInstanceState method: this lets you store any relevant information before it gets killed off. See http://developer.android.com/reference/android/app/Activity.html#onSaveInstanceState%28android.os.Bundle%29 for the actual call you need to implement, and Saving Android Activity state using Save Instance State for a quite excellent example.
In my Android app, when a user tries to transition from one activity to another, there may be some global state that indicates they need to complete some other action first.
To accomplish this, I've written a class with the following code:
private static WeakReference<Activity> oldActivityReference;
private static Intent waitingIntent;
public static void pushActivity(Activity currentActivity, Intent newActivityIntent) {
Intent blockingIntent = ThisClass.getBlockingActivity();
if (blockingIntent != null) {
ThisClass.oldActivityReference = new WeakReference<Activity>(currentActivity);
ThisClass.waitingIntent = newActivityIntent;
currentActivity.startActivity(blockingIntent);
return;
}
currentActivity.startActivity(newActivityIntent);
}
When the blocking activity finishes, it calls ThisClass.blockingActivityFinished(). That will check to see if the weak reference to the old activity still exists and, if so, launch the original intent from that activity. If not, it will launch the original intent from my application's context.
My question is,
Does this sound sane? Are there any potential memory leak issues with this technique? Is there a better way to accomplish this?
EDIT -
To be clear, the types of events that might trigger an interruption are 1) a server ping indicating that the current app version is deprecated 2) any server RPC indicating that the user's credentials are no longer valid. I do not want to add logic to every Activity to handle checking for these, and resuming business as usual once they complete. That is a violation of DRY, and error-prone in a team environment.
Does this sound sane?
I'd never use this technique. Mutable static data members are dangerous, WeakReference notwithstanding. In particular, I'd expect this to fail if the user does the unthinkable and, say, uses their phone as a phone, or otherwise leaves your application flow for an extended period of time. Your activities may be destroyed and your process terminated to free up RAM, yet the activities would remain in the task and might be reactivated. At that point, your state is whack, because the statics got nuked.
Are there any potential memory leak issues with this technique?
You're leaking an Intent.
Is there a better way to accomplish this?
For the purposes of the rest of this answer, I'm going to refer to your starting point as Activity A, the "some other action" as Activity B, and the desired end as Activity C. So, in your code, newActivityIntent is for Activity C, blockingIntent is for Activity B, and currentActivity is Activity A.
Option #1: Put the decision-making process in Activity C, rather than Activity A. Have Activity C check the condition in onCreate() and immediately calls startActivity() for Activity B if the conditions require Activity B to be shown.
Option #2: Leave the decision-making process in Activity A, but pass the boolean (e.g., true for "we gotta show Activity B") in an Intent extra for the startActivity() call for Activity C. Activity C checks the boolean in onCreate() and immediately calls startActivity() for Activity B if the boolean says so.
In these options, you avoid the statics.