What makes a singleTask activity have 2 instances?
Asked Answered
B

1

11

As per the docs, singleTask activities can't have multiple instances. The only activity of my app is singleTask, and it has 2 instances at the same time.

Steps to recreate the issue

Step 1

Create a new project in Android Studio 3.3.1, Add No Activity, name it singleTaskBug, (package com.example.singletaskbug), using Java language with minimum API level 21 without support for instant apps.

Step 2

Add a new activity manually by editing AndroidManifest.xml then creating a new Java Class in appjavacom.example.singletaskbug named LauncherActivity.

Content of AndroidManifest.xml:

<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/AppTheme">
    <activity
        android:name=".LauncherActivity"
        android:excludeFromRecents="true"
        android:launchMode="singleTask">

        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

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

        </intent-filter>

    </activity>
</application>

Content of LauncherActivity.java:

package com.example.singletaskbug;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;

public class LauncherActivity extends Activity {

    static int instanceCounter = 0;
    final int instanceId;
    final String TAG = "STB";

    public LauncherActivity() {
        instanceId = ++instanceCounter;
        Log.d(TAG, "Constructed instance " + instanceId + ".");
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "Created instance " + instanceId + ".");
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.d(TAG, "New intent to instance " + instanceId + ".");
    }

    @Override
    protected void onDestroy() {
        Log.d(TAG, "Destroyed instance " + instanceId + ".");
        super.onDestroy();
    }
}

Step 3

Go to RunEdit Configurations... and in the Launch Options section set Launch: to Specified Activity, and Activity: com.example.singletaskbug.LauncherActivity, then click OK, and Run 'app' ShiftF10.

Step 4

Wait until the activity becomes visible. Now on the test device (API 21 in my case), go to settings to set this app as the default launcher. Then press the home button. At this point you'll see this in Logcat:

02-15 17:22:01.906 26429-26429/com.example.singletaskbug D/STB: Constructed instance 1.
02-15 17:22:01.916 26429-26429/com.example.singletaskbug D/STB: Created instance 1.
02-15 17:22:24.228 26429-26429/com.example.singletaskbug D/STB: Constructed instance 2.
02-15 17:22:24.248 26429-26429/com.example.singletaskbug D/STB: Created instance 2.
Bait answered 15/2, 2019 at 15:12 Comment(29)
Is this a known bug of Android?Merna
This app is for API 21. Is the behavior described in the docs relevant for this version?Merna
There is a log command in the constructor of the activity, and in onCreate and onDestroy too. The activitiy is instantiated first by the Run command of Android Studio (configured to launch specified activity). Then I navigate to an other app, then press the home button, which should bring my activity to front (it's a launcher app), but a second instance is created instead. In my logs: Instance 1 is constructed., Instance 1 is created., Instance 2 is constructed., Instance 2 is created.. Nothing is destroyed.Merna
Someone advised me to use android:taskAffintiy="". I've tried it, it doesn't help.Merna
Using android:launchMode="singleInstance" there is only 1 instance, but this won't let other activities in the same task, so this is not what I need.Merna
adb shell dumpsys activity activities shows one instance of my activity in a task of a stack, and shows the second instance of my activity in a different stack of an other stack.Merna
I've checked tens of SO questions, and all the relevant docs, blog posts etc. Still don't get it.Merna
Sounds odd. What is the smallest amount of code you are able to replicate this with?Maisiemaison
I'll add more code, and step-by-step instructions in a few minutes.Merna
I've created a new Android Studio project from zero, and the bug is there, so I hope it'll be easy to reproduce it.Merna
I'll simplify the steps in a few hours.Merna
I think this is a similar issue (asked 6 months ago, no solution): #51456263Merna
Interesting when running this app with dumpsys the actual intent for the 2 copies is different. the rootWasReset value is also different. Anyway, just to confirm you've definitely replicated the issue reliably.Maisiemaison
And then after a reboot, if you set this app as default launcher, then behaviour becomes as expected. Only one instance exists at any one time.Maisiemaison
Exactly, the first intent's category is launcher, the second's is home. The flags also differ. But there should be only 1 instance even if it gets a hundred different start intents, I guess. I don't know what rootWasReset is, I'll read about it. Thank you for confirming the issue.Merna
I suppose the problem will show up again after a reboot too, if the activity gets an explicit intent. Not sure though.Merna
I would recommend logging a bug and linking to it in your post. I got no further ideas, raised it on another forum and got no useful feedback. Your example app is great for proving a bug. Hope to see an answer on this one day, even if it is just for "why" it does this.Maisiemaison
The phrasing (and particularly the use of tables with titles like Multiple Instances?) used in the docs is imprecise. God knows how anyone who does not have English as a first language cope with these docs. See Tasks + Back Stack. Specialized launches ONLY (not recommended for general use). singleTask "The system creates the activity at the root of a new task and routes the intent to it. However, if an instance of the activity already exists, the system routes the intent to existing instance".Voidance
Basically we have to assume that the code is right (and not a bug) and the docs are wrong (or misleading/badly worded), because the code is out there (being used) and the docs have bugs, just like the code. I know, I have raised problems with docs and have had them changed (e.g. raw assets ).Voidance
@JonGoodwin The 4-digit code to my safe is MTIzNA==. This might be hard to understand for anyone who doesn't know what base64 encoding is. But the real problem is this: my safe won't open for 1234. It's the same with the docs in this case: even if you understand every word, the information is incorrect.Merna
@JonGoodwin I don't know whether this is a bug in Android or in the docs. A buggy code can be out there too, so I can't assume that the code is right just because it's being used.Merna
@JonGoodwin ...not to mention if this strange behaviour is caused by a bug of Android Studio, or something else.Merna
This is the problem with the written word, it's not mathematically correct, but code is (more so). Is the information incorrect ? (or have you not understood it in the larger context ?) . I don't know. I did not say "assume that the code is right just because it's being used", I said assume the docs are wrong {if they disagree with experiment} (and raise an issue) . ;O)Voidance
You say "even if you understand every word, the information is incorrect.". So you have identified the information that is wrong and can correct/query the documentation, no ? So do it. I've shown you the link already.Voidance
Your whole premise that there is any problem at all is based on your understanding of the documentation (which you provide a link to, but do not say which part is wrong).Voidance
hmm we seem to have brocken stackoverflow ;O)Voidance
Remove android:excludeFromRecents="true" from activity tag.Veterinarian
Could be related to the strange behaviors described in Unpredictable relaunch — Strange App launcher behaviors (part 2) medium.com/@elye.project/…Hyonhyoscine
I am struggiling with this as well. Seems like the OS creates a 'special task' for Home (Launcher) activities, which cannot be accessed by third-party apps. Hence, when launching the Home activity from a third-party app, a new task (so a new activity instance ass well) is created. My attempt to fix this goes through creating a shortcut application which indeed brings the activity from the 'Home special task' to the foreground, but can't find the way to do it. The shortcut activity would allow to ignore the way each launcher can start an activityOlly
A
0

An Android application can have multiple tasks. Each task can have multiple activities. singleTask and singleInstance control the behaviour of the activity (its uniqueness) inside the task, but it can happen that an App has two or more tasks with the same Activity inside.

This is what is actually seen here, an App with two tasks with an LauncherActivity inside, one for each tasks. As workaround, ensure that there is always one task in the app. On the LauncherActivity onCreate add:

val appTasks = getSystemService(ActivityManager::class.java).appTasks
if (appTasks.size >= 2) {
    appTasks.dropLast(1).forEach { it.finishAndRemoveTask() }
    appTasks.last().moveToFront()
    return
}
Alexandretta answered 29/7, 2021 at 13:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.