Search code examples
androidandroid-intentandroid-launcherback-stacklaunchmode

Understanding Android Launch modes with splash screens


I have 3 activities: Splash (launcher), Login, Dashboard. When app is opened Splash waits some time and then starts Login, finishing itself. Then user can then navigate from Login to Dashboard.

I want to maintain a single instance of the app whenever the user clics on the home button, or is directed towards the app by a browsable Intent. So that the user is returned to where he or she was before switching the app.

The following is the behaviour with android:launchMode="standard" and singleInstancePerTask (red squares are finished activities) standard mode

singleTask behaves similar to the additional web intent for standard mode, i can add logic to Splash so that it interrupts processings and finish if it is not root: single taask

Lastly If I choose singleInstance, the following happens:

single instance This seems to work, but there is something that I don't understand. Why is the second Splash ignoring the Intent to launch Login activity?. The logic tells me that a Login #2 should've launched.

Manifest in matter:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.testintentflags">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.TestIntentFLags">

        <activity
            android:name=".SplashActivity"
            android:exported="true" android:launchMode="singleInstance">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:host="demo.intents.com"
                    android:scheme="https" />
            </intent-filter>
        </activity>
        <activity
            android:name=".DashboardActivity"
            android:exported="false" />
        <activity
            android:name=".LoginActivity"
            android:exported="false" />
    </application>

</manifest>

Solution

  • There are a number of things going on here, I'll try to address these:

    First off, using launchMode="singleInstance" is problematic here, because you have not declared any taskAffinity for any of your activities. So your SplashActivity and your other activities have the same (default) taskAffinity which tells Android that all these activities want to run in the same task, which conflicts with your launch mode declaration of SplashActivity. This is bad, and you shouldn't do it. If you really want to have activities running in separate tasks, you should ensure that they have different taskAffinity so as not to cause confusion.

    Secondly, you wrote:

    This seems to work, but there is something that I don't understand. Why is the second Splash ignoring the Intent to launch Login activity?. The logic tells me that a Login #2 should've launched.

    What is happening here is this: When SplashActivity calls startActivity() to launch LoginActivity, the Intent used contains FLAG_ACTIVITY_NEW_TASK set (because SplashActivity is declared with launchMode="singleInstance" so it has to launch other activities into a new task), so Android looks for an existing task that has LoginActivity as its root Activity. If it didn't find one, it would simply launch a new instance of LoginActivity into a new task. However, in this case, it finds one (this is task #2 in your diagram), so instead, it just brings that task to the foreground without launching a new instance of LoginActivity.

    This is the same behaviour as what happens when you have an app running, then press the HOME button and then click the app icon for that app again. Android doesn't launch a new instance of the app's root Activity in this case, it simply looks for an existing task that has the app's launch Activity as its root Activity and brings that task to the foreground in whatever state it was in.

    My suggestion for you is as follows:

    • Get rid of the use of the special launch modes singleInstance and singleTask
    • Create a new BridgeActivity which is used as a bridge between the web browser and your app. This Activity should have the <intent-filter> for the browser Intent.
    • The BridgeActivity should check if it is the root Activity in its task. If it is not, it means that it has been launched into an existing task that was brought to the foreground and it can just quietly finish() in onCreate().
    • If BridgeActivity detects that it is the root of its task, it can launch SplashActivity (make sure to set FLAG_ACTIVITY_NEW_TASK) to begin a new task (since there obviously wasn't already an existing task of your app)
    • BridgeActivity should be declared so that it doesn't end up in the task stack history or in the list of recent tasks (noHistory="true" and excludeFromRecents="true")