3

I have a sample app with 3 activities: Main, A and B. None of them has any launchMode.

Now I do this:

  1. open the app, Main activity is displayed
  2. leave the app using back button, so that there is no activity running
  3. receive a broadcast which starts a new activity A (new_task flag)
  4. open activity B by clicking on a button in activity A (no flags)
  5. receive a broadcast which starts a new activity A (new_task flag)
  6. new activity A is not started (if I go back, there's still the previous Activity A)

At step #6, activity A should be presented but is not.

If I try to present activity C instead (in #5), it is presented as expected.

If in #2 I leave the app by homebutton instead, everything works as expected.

How is this possible? And how can I ensure that the activity is always presented? I could use FLAG_ACTIVITY_CLEAR_TOP|FLAG_ACTIVITY_SINGLE_TOP, but I want to maintain the backstack.

UPDATE - SOURCE: You can look at the source, but I am using Xamarin (which is basically native Android written in C#, very similar to Java). This is the content of the built manifest (removed the Xamarin stuff):

<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" android:compileSdkVersion="28" android:compileSdkVersionCodename="9" package="com.companyname.StartTest" platformBuildVersionCode="28" platformBuildVersionName="9">
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <application android:allowBackup="true" android:debuggable="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:name="android.app.Application" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
        <activity android:label="A" android:name="A"/>
        <activity android:label="B" android:name="B"/>
        <activity android:label="@string/app_name" android:name="MainActivity" android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <receiver android:name="Receiver"/>
    </application>
</manifest>
David Riha
  • 1,368
  • 14
  • 29

2 Answers2

1

Alright lets look at this as per the sequence of operations.

  1. open the app, Main activity is displayed

    Status of backstack: Main Activity (root)

  2. leave the app using back button, so that there is no activity running

    Status of backstack: <No activity present as back was pressed>

  3. receive a broadcast which starts a new activity A (new_task flag)

    Status of backstack: ActivityA (root)

  4. open activity B by clicking on a button in activity A (no flags)

    Status of backstack: ActivityA (root) -> ActivityB (top)

  5. receive a broadcast which starts a new activity A (new_task flag)

    Status of backstack: ActivityA (root) -> ActivityB (top)

    Here Actvitity A is not launched because it was launched with the intent flag 'FLAG_ACTIVITY_NEW_TASK' which has the following documentation:

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.

  1. new activity A is not started (if I go back, there's still the previous Activity A)

At step #6, activity A should be presented but is not.

So the current task looks like this ActivityA (root) -> ActivityB(top), ActivityA is already present in the task and so the task as a whole is just brought to the front which means ActivityB is still at the top of the task.

If I try to present activity C instead (in #5), it is presented as expected.

ActivityC is a new activity that is not there in the task and so it is created for you.

If in #2 I leave the app by homebutton instead, everything works as expected.

The thing about homebutton press is that the MainActivity is not destroyed in this case and is just pushed to the background.

So after #2 your status of backstack would look still have the MainActivity instance as the root like :

Main Activity (root)

Now, before #5, backstack would look like this:

Main Activity (root) -> ActivityA -> ActivityB (top)

After #5, your backstack looks like this:

Main Activity (root) -> ActivityA -> ActivityB -> ActivityA (top)

I believe the reason for this seems to be the way MainActivity is launched by default with the intent flags :

FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

This also triggers a task reparenting and so it has control over new activities created with the new task flag (with the same affinity as the parent application) and how it can move such an activity to its task.

You can find more information on this here

And how can I ensure that the activity is always presented? I could use FLAG_ACTIVITY_CLEAR_TOP|FLAG_ACTIVITY_SINGLE_TOP, but I want to maintain the backstack.

Since this is a stack data structure you cannot reorder your activities, however you can fix this by adding the flag : FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK in the receiver that creates ActivityA as this will ensure a new instance/task is created for the activity irrespective of whether it was already present or not. (Not a recommended solution - but works)

But if you want to maintain the same instance of ActivityA then you will have to add a taskAffinity parameter to your activities in the manifest.

<activity android:name=".ActivityB" android:taskAffinity="com.example.pendingintent"></activity>
<activity android:name=".ActivityA" android:taskAffinity="com.example.abctest" />

and then start both your activities A and B with the new_task flag

Here is an excellent slideshow explaining taskAffinity and how it affects creation of activities.

raxerz
  • 516
  • 4
  • 10
  • Thanks! It works with `FLAG_ACTIVITY_MULTIPLE_TASK`, but the docs say in bold text that `When used with FLAG_ACTIVITY_NEW_TASK do not use this flag unless you are implementing your own top-level application launcher.` I am not sure what a `top-level application launcher` means, but I don't think this is the case, is it? – David Riha Jun 15 '19 at 10:04
  • Yes, using FLAG_ACTIVITY_MULTIPLE_TASK is not a recommended solution, in fact some sources say that you should prefer using taskAffinity instead. The reason is that whenever an activity is launched with this flag it will start it in a new task irrespective of whether it was present in one of the backstack tasks and so it will be difficult to link it with the existing task in history and so this usually fits the use case where you want to launch a fresh application in a new task and that's what they mean by top level application launcher. – raxerz Jun 15 '19 at 16:15
  • You may want to read the discussion here for a better understanding of this: https://stackoverflow.com/questions/19771051/is-it-safe-to-use-intent-flag-activity-multiple-task – raxerz Jun 15 '19 at 16:16
  • Thanks, I gave you the bounty, but still it seems a lot messed up and using taskAffinity also doesn't look as a safe option - I looked on the slideshow you referenced and it describes some unexpected situations using taskAffinity, it doesn't seem reliable. For now, I will use`FLAG_ACTIVITY_CLEAR_TOP` + `FLAG_ACTIVITY_SINGLE_TOP` and lose the backstack. Shame on you, Android. – David Riha Jun 16 '19 at 07:27
  • Haha thanks @David Riha Android is particularly less comprehensible in this regard, maybe over the course of time they will sort it out – raxerz Jun 16 '19 at 07:30
0

Update Option 3: This should bring the Activity to top.

intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);

option1: set FLAG_ACTIVITY_REORDER_TO_FRONT when launching Activity A. This will bring Activity A to top of the stack.

activityAIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);

Option2: Going through the Android documentation, only "Standard" launch mode creates multiple instances of the activity.

Refer below table.

This documentation explains usage of each mode: https://developer.android.com/guide/topics/manifest/activity-element.html#lmode

Add standard launchMode to your Activity A if you want its new instance every time.

android:launchMode="standard"

enter image description here

TheAnkush
  • 885
  • 7
  • 10
  • Option1 did not help. Option2 - as I said, none of the activities has any launch mode and since the `standard` is the default, they already have `standard`. – David Riha Jun 14 '19 at 14:30
  • @DavidRiha - Try option3 that I just added. Yup agreed, just try and see if it works. also share the android manifest and the way you are starting Activity A. – TheAnkush Jun 14 '19 at 14:33
  • Option 3 also didn't help. – David Riha Jun 14 '19 at 15:50
  • @DavidRiha - I went through the code, when creating the broadcast please test it by passing the flags value as 0 like this PendingIntent.GetBroadcast(Application.Context, 12345, ringIntent, 0); – TheAnkush Jun 14 '19 at 17:31
  • This won't help, the broadcast is ok, starting the activity is the problem. `CALLED START ACTIVITY A` is logged in the console, so the broadcast is correctly received but the activity is not started. – David Riha Jun 14 '19 at 17:36