The app has SplashActivity which is launched both normally and via deep link. In later case the SplashActivity creates an Intent, sets the data from deeplink, and launches MainActivity. MainActivity checks the data and behaves differently for normal launches and deep link launches.
When I enable "Don't keep activities" and launch the app via deeplink then when I go to background and return to the MainActivity it is recreated with deeplink intent.
I tried to modify the Intent in onDestroy and onSaveInstanceState with removeExtra and setIntent, but when I go to background and back the intent is still the same from deeplink and the extra is still there. I also tried to check intent flags for LAUNCHED_FROM_HISTORY but when I debugged the app flags were 0 every time.
I don't need the app to behave like it was launched with deeplink when it was launched from history. Is it possible to preserve intent modifications in that case?
You need to modify your intent in onPause, everything you do after onPause will not be saved, you may even consider modify your intent just after you consumed it to make your deeplink, i guess this will work as well
In onCreate block, Try removing the deep-linking keys in the Intent that you send to the activity
int flags = getIntent().getFlags();
if ((flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
// The activity was launched from history
// remove extras here to prevent the app from retaining the deep linking intent
getIntent().removeExtra(key);
} else {
// get data from intent normally
String s = getIntent().getStringExtra(key)
}
This should prevent the Activity from retaining the previous Intent.
Related
I have an android application which has two activities let's say A and B where A is the launcher activity. For activity A I registered an intent-filter which opens application for a specific URL. I want to have always a single task for my application in current running apps. To solve this problem I tried different combinations for launchMode attributes:
A singleTop, B standard but when I access app via intent URL I have two applications in current running apps
A singleTask B standard but every time when I open the app, it starts with activity A even the apps was already opened with activity B (in this case I want to resume the app)
A singleTask B singleTask the behavior is like A singleTask B standard
I want that my app to have the same behavior like for example gmail, always to have maximum one instance in current running apps, when I open it and there is already an instance in background, to resume it and if I open the app via intent filter I want to process the intent and provide content accordingly (for example gmail is in background and I receive a new mail notification after I click it, the android keep one instance for gmail and displays the new mail).
I use Samsung Galaxy Note 4 with Android 6.0
Problem solved
I found a solution for my problem, maybe it is not the best but it works. Firstly I added a new Activity to catch the intent filter which has launchMode = "SingleTask" and in its onCreate method I posted an event to kill all existing activities.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ("android.intent.action.VIEW".equals(getIntent().getAction())) {
// This Activity is started using an external app (eg: Gmail)
// So start the app from the beginning
// (redirecting to Activity A)
Intent mainIntent = getIntent(); // Copy the Intent used to launch me
// Launch the real root Activity (launch Intent)
mainIntent.setClass(this, StartActivity.class);
// Post an event to kill all existing activities
// To do this i use Guava
PubSub.getInstance().post(new KillActivityEvent());
startActivity(mainIntent);
finish();
} else {
// The activity wasn't started by an external app
finish();
}
}
My app and activity is in my list of recent apps when I receive a notification. When I click on the notification, I want the intent of the notification to be honored. In my case I want to restart the activity (brute force) and pass in the new intent: so, finish then re-create. I am reading about such tags as FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_SINGLE_TOP but I don't understand them enough to know how to force a "finish then re-create` of my activity. And, oh, the activity in question is MainActivity.
The snippet inside GcmListenerService uses
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
for sending the notification intent
Alternatively
If I go with onNewIntent things get complicated because there maybe DialogFragments being displayed, etc. And I would have to clear everything. That is why finish then re-create seem like the simplest solution to me.
Intent flag FLAG_ACTIVITY_CLEAR_TOP should produce the desired behavior. The documentation for Tasks and Back Stack says this in the section for Using Intent Flags:
If the launch mode of the designated activity is "standard", it too is
removed from the stack and a new instance is launched in its place to
handle the incoming intent. That's because a new instance is always
created for a new intent when the launch mode is "standard".
The documentation for FLAG_ACTIVITY_CLEAR_TOP describes the same behavior in more detail.
What happens when you click on an app's launch icon?
Is a new intent always sent, or is the result sometimes the same as resuming a task from recent tasks?
If an intent is sent, when does it get sent to the onCreate() method of a new activity instance and when does it get routed through onNewIntent() of an existing activity?
Let's suppose the intent gets routed through onNewIntent() of an existing activity in the task. Which activity does it get sent to? The one nearest the top or the one nearest the root? Will it always get sent to an instance of the application's launch activity or can it sometimes get sent to an activity with the same affinity as the root? Can it ever get sent to an activity which does not share the same affinity as the root?
Finally, how is this all affected by the various launch modes (standard, single top, single instance, single task) of the activities in the task?
If there is anyone out there who understands all this, please help me!
What happens when you click on an app's launch icon?
Launcher apps calls startActivity with an intent [action = Intent.ACTION_MAIN, category = Intent.CATEGORY_LAUNCHER and flag = Intent.FLAG_ACTIVITY_NEW_TASK].
Regarding Intent.FLAG_ACTIVITY_NEW_TASK, from docs:
When using this flag, if a task is already running for the activity
you are now starting, then a new activity will not be started;
instead, the current task will simply be brought to the front of the
screen with the state it was last in.
onNewIntent basics:
onNewIntent is delivered only when activity has set either singleTask, singleInstance launch modes. It is also delivered if activity has set singleTop launch mode or the intent to start the activity has set the flag FLAG_ACTIVITY_SINGLE_TOP and the activity instance is already at the top of the target task. It means an attempt was made to launch a new instance of activity, instead the existing instance itself need to handle the intent.
Here is the response to your queries:
Is a new intent always sent, or is the result sometimes the same as
resuming a task from recent tasks?
If the task is already running, it is brought to foreground. In case FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET flag was used to launch a activity and latter the task is brought to foreground, then the activity is killed. From docs:
This is useful for cases where you have a logical break in your
application. For example, an e-mail application may have a command to
view an attachment, which launches an image view activity to display
it. This activity should be part of the e-mail application's task,
since it is a part of the task the user is involved in. However, if
the user leaves that task, and later selects the e-mail app from home,
we may like them to return to the conversation they were viewing, not
the picture attachment, since that is confusing. By setting this flag
when launching the image viewer, that viewer and any activities it
starts will be removed the next time the user returns to mail.
-
If an intent is sent, when does it get sent to the onCreate() method
of a new activity instance and when does it get routed through
onNewIntent() of an existing activity?
onCreate is called while creating a new instance of activity. onNewIntent is called if already an activity instance exists and no new instance need to be created, as in case of singleInstance, singleTask and conditionally singleTop (as described above).
Let's suppose the intent gets routed through onNewIntent() of an
existing activity in the task. Which activity does it get sent to? The
one nearest the top or the one nearest the root? Will it always get
sent to an instance of the application's launch activity or can it
sometimes get sent to an activity with the same affinity as the root?
Can it ever get sent to an activity which does not share the same
affinity as the root?
In case of singleTask and singleInstance it has to be root of the task. In case of singleTop it has to be top activity of the task.
Finally, how is this all affected by the various launch modes
(standard, single top, single instance, single task) of the activities
in the task?
I hope the explanation provided till now, answers it.
Update 1:
Here is the Launcher code which adds the flags to intent:
void processShortcut(Intent intent) {
....
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
....
}
void startActivitySafely(Intent intent) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
...
startActivity(intent);
}
Your best bet is to read through the Developer docs here: http://developer.android.com/training/basics/activity-lifecycle/index.html
There is a flow chart in the first lesson(http://developer.android.com/images/training/basics/basic-lifecycle.png) which provides an excellent graphical representation of the Android activity life-cycle.
I have a problem regarding Android task and intent management.
Scenario
User gets a push with a deep-link into the app.
We show a notification putting the URI into the Intent Data.
User clicks the notification and is taken into the app and redirected to some Feature1Activity described by the deep-link.
User looks around, and backs out of the app.
Later, user opens the app from history (long-press home or multitasking button).
Now the same intent that were used from the notification (with the deep-link in the Intent Data) is used to start the app.
Hence, user is taken into the Feature1Activity again.
Problem:
Starting the app from history (long-press home or multitasking button) does not reset the Task (which it does when launching from app icon).
I understand that starting an app from history is not supposed to reset the task since it is intended to be used as "get-right-back-where-you-were". However, in my case this is an issue since the launch of the app from a notification is a one time thing.
Anyone else encountered this problem? Anyone know any solution?
More in-depth
The intent inside the PendingIntent is built like this:
Intent intent = new Intent (Intent.ActionView);
intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags (Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags (Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
intent.setData (Uri.Parse (DEEP_LINK_URL));
I found out about the FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET just this day and really thought that it would git rid of my problem but it made no difference.
There are three activities of interest:
SplashActivity (main launcher & listener of the deep-linking schema -- this activity just redirects either to login or OverviewActivity)
OverviewActivity (authorized user's main activity)
Feature1Activity (any feature that the deep-link is pointing to)
What happens when the user clicks the notification is that the SplashActivity acts as a listener for the schema and converts the deep-link url to two intents to start up OverviewActivity and Feature1Activity using Activity.startActivities (Intent[]).
When I look at the intent from the notification inside SplashActivity it always contain the deep-link in the Data.
One work around
There is a work around, setting some booleanExtra field to the notification intent (for instance "ignoreWhenLaunchedFromHistory" = true) and then check in SplashActivity before redirecting
boolean fromHistory = (getIntent().getFlags() & FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY;
if (fromHistory && getIntent().getBooleanExtra ("ignoreWhenLaunchedFromHistory", false))
// Don't follow deep-link even if it exists
else
// Follow deep-link
Except that it hackish and ugly, can you see any problems with this work around?
EDIT: The work around only works when I am responsible for sending the Intent with the deep-link. Since no external source can know about the "ignoreWhenLaunchedFromHistory" extra.
From what I get, maybe using android:excludeFromRecents="true"on your manifest (as a property for the Activity declaration) might ameliorate the issue?
I am puzzled over this behavior I am experiencing in an application I am developing...
Short:
Intent data is not clearing out when the user presses the back button to leave the application and then presses the recent button to re-enter the application. (Every other case, the intent data is cleared out)
Long:
I have an application with a splash screen that is used to collect data that is passed in from a URI scheme. I then setup an intent to forward the data to the main activity. The main activity has fragments and is based off the master/detail template.
The intent data is cleared out in all cases, such as pressing the home button and then going back to the application, pressing the recent apps button and then going back to the application, etc. The only case where the intent data is not cleared out is when the user presses the back button and then the recent apps button to get back into the application.
Relevant snippets of code that involve the intents:
// Splash Screen Activity
#Override
protected void onPostExecute(Void result) {
// Data is done downloading, pass notice and app ids to next activity
Intent intent = new Intent(getBaseContext(), ListActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("id1", id1);
intent.putExtra("id2", id2);
intent.putExtra("id3", id3);
startActivity(intent);
finish();
}
// ListActivity retrieving intent data
Intent intent = getIntent();
if (intent != null) {
this.id1 = intent.getExtras().getString("id1");
this.id2 = intent.getExtras().getString("id2");
this.id3 = intent.getExtras().getString("id3");
}
// ListActivity clearing intent data
#Override
public void onPause() {
super.onPause();
// Clear intent data
Intent intent = getIntent();
intent.putExtra("id1", "");
intent.putExtra("id2", "");
intent.putExtra("id3", "");
}
I want to note that I have also tried using intent.removeExtra("id1") but that too did not work.
Any idea what is going on? It is as if Android is keeping the old intent even though onPause() is always called to clear the intent data.
actually this is due to Android starting the app from the history hence the intent extras are still in there
refer to this questions Android: Starting app from 'recent applications' starts it with the last set of extras used in an intent
so adding this conditional to handle this special case fixed it for me
int flags = getActivity().getIntent().getFlags();
if ((flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
// The activity was launched from history
// remove extras here
}
I believe the difference here is that the Back key is actually causing your Activity to finish, where as pressing Home causes your activity to be paused, but not finished.So when your process is brought back to the front in the Home case, it is simply resuming an already existing Activity instance, whereas in the Back case, the system is instantiating a new copy of your Activity, calling onCreate(), and handing it a fresh copy of the last Intent recorded for that activity.
In onPause() you are clearing the extras in a "copy" of the Intent. You can try adding
setIntent(intent);
to onPause() after you've cleared the extras (although calling removeExtra() would probably also work instead of setting extras to empty strings).
NOTE:
However, I would suggest that this design is flawed. You shouldn't use the Intent to keep track of state in your application. You should save some state in shared preferences because this will survive your app being killed/restarted, a reboot of the phone, or whatever.
The problem is that the new Intent is not persisted, so that If the user presses the HOME button and your app goes to the background and then Android kills your app because it is not active, when the user returns to the app, Android will create a new process for your app and it will recreate the activity using the original Intent.
Also, if you read the documentation for getIntent() it says that it returns the Intent that started the activity.
To get around the issue I was facing, I opted to use SharedPreferences as a means to pass data between activities.
I know SharedPreferences isn't typically used for this purpose, but it solved my issue and works.