So verhindern Sie, dass mehrere Instanzen einer Aktivität mit unterschiedlichen Absichten gestartet werden

121

Ich habe einen Fehler in meiner Anwendung festgestellt, als sie über die Schaltfläche "Öffnen" in der Google Play Store-App (zuvor Android Market genannt) gestartet wurde . Es scheint, dass das Starten über den Play Store eine andere Verwendung hat Intentals das Starten über das Anwendungsmenü der Symbole des Telefons. Dies führt dazu, dass mehrere Kopien derselben Aktivität gestartet werden, die miteinander in Konflikt stehen.

Zum Beispiel, wenn meine Anwendung der Aktivitäten ABC besteht, dann kann dieses Problem zu einem Stapel von ABCA führen.

Ich habe versucht, android:launchMode="singleTask"bei allen Aktivitäten dieses Problem zu beheben, aber es hat den unerwünschten Nebeneffekt, dass der Aktivitätsstapel bei jedem Drücken der HOME-Taste auf root gelöscht wird.

Das erwartete Verhalten ist: ABC -> HOME -> Und wenn die App wiederhergestellt ist, brauche ich: ABC -> HOME -> ABC

Gibt es eine gute Möglichkeit, das Starten mehrerer Aktivitäten desselben Typs zu verhindern, ohne bei Verwendung der HOME-Schaltfläche auf die Stammaktivität zurückzusetzen?

bsberkeley
quelle

Antworten:

187

Fügen Sie dies zu onCreate hinzu und Sie sollten bereit sein:

// Possible work around for market launches. See https://issuetracker.google.com/issues/36907463
// for more details. Essentially, the market launches the main activity on top of other activities.
// we never want this to happen. Instead, we check if we are the root and if not, we finish.
if (!isTaskRoot()) {
    final Intent intent = getIntent();
    if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(intent.getAction())) {
        Log.w(LOG_TAG, "Main Activity is not the root.  Finishing Main Activity instead of launching.");
        finish();
        return;       
    }
}
Duane Homick
quelle
25
Ich habe jahrelang versucht, diesen Fehler zu beheben, und dies war die Lösung, die funktioniert hat. Vielen Dank! Ich muss auch beachten, dass dies nicht nur ein Problem im Android Market ist, sondern auch das Seitenladen einer App durch Hochladen auf einen Server oder E-Mail an Ihr Telefon dieses Problem verursacht. All diese Dinge installieren die App mit dem Package Installer, wo meiner Meinung nach der Fehler liegt. Für den Fall, dass es nicht klar ist, müssen Sie diesen Code nur zur onCreate-Methode Ihrer Stammaktivität hinzufügen.
Ubzack
2
Ich finde es sehr seltsam, dass dies in einer signierten App geschieht, die auf dem Gerät bereitgestellt wird, aber nicht in einer von Eclipse bereitgestellten Debug-Version. Macht das Debuggen ziemlich schwierig!
Matt Connolly
6
Dies ist geschehen mit einer Debug - Version von Eclipse - Einsatz so lange wie Sie es auch über Eclipse - START (oder IntelliJ oder andere IDE). Es hat nichts damit zu tun, wie die App auf dem Gerät installiert wird. Das Problem ist auf die Art und Weise zurückzuführen, wie die App gestartet wird .
David Wasser
2
Weiß jemand, ob dieser Code sicherstellt, dass die vorhandene Instanz der App in den Vordergrund gerückt wird? Oder ruft es einfach finish () auf; und dem Benutzer keinen visuellen Hinweis darauf geben, dass etwas passiert ist?
Carlos P
5
@CarlosP Wenn die Aktivität, die erstellt wird, nicht die Stammaktivität der Aufgabe ist, muss (per Definition) mindestens eine weitere Aktivität darunter sein. Wenn diese Aktivität finish()aufgerufen wird, sieht der Benutzer die darunter liegende Aktivität. Aus diesem Grund können Sie davon ausgehen, dass die vorhandene Instanz der App in den Vordergrund gerückt wird. Wenn dies nicht der Fall wäre, hätten Sie mehrere Instanzen der App in separaten Aufgaben, und die zu erstellende Aktivität wäre die Wurzel ihrer Aufgabe.
David Wasser
27

Ich werde nur erklären, warum dies fehlschlägt und wie dieser Fehler programmgesteuert reproduziert werden kann, damit Sie ihn in Ihre Testsuite integrieren können:

  1. Wenn Sie eine App über Eclipse oder Market App starten, wird sie mit Absichtsflags gestartet: FLAG_ACTIVITY_NEW_TASK.

  2. Beim Start über den Launcher (Home) werden die folgenden Flags verwendet: FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED und verwendet die Aktion " MAIN " und die Kategorie " LAUNCHER ".

Wenn Sie dies in einem Testfall reproduzieren möchten, führen Sie die folgenden Schritte aus:

adb shell am start -f 0x10000000 -n com.testfairy.tests.regression.taskroot/.MainActivity 

Tun Sie dann alles, um zur anderen Aktivität zu gelangen. Für meine Zwecke habe ich gerade eine Schaltfläche platziert, mit der eine andere Aktivität gestartet wird. Gehen Sie dann zurück zum Launcher (nach Hause) mit:

adb shell am start -W -c android.intent.category.HOME -a android.intent.action.MAIN

Und simulieren Sie das Starten über den Launcher folgendermaßen:

adb shell am start -a "android.intent.action.MAIN" -c "android.intent.category.LAUNCHER" -f 0x10600000 -n com.testfairy.tests.regression.taskroot/.MainActivity

Wenn Sie die Problemumgehung isTaskRoot () nicht integriert haben, wird das Problem dadurch reproduziert. Wir verwenden dies in unseren automatischen Tests, um sicherzustellen, dass dieser Fehler nie wieder auftritt.

Hoffe das hilft!

Gilm
quelle
8

Haben Sie den SingleTop- Startmodus ausprobiert ?

Hier ist einige der Beschreibungen von http://developer.android.com/guide/topics/manifest/activity-element.html :

... kann auch eine neue Instanz einer "singleTop" -Aktivität erstellt werden, um eine neue Absicht zu verarbeiten. Wenn die Zielaufgabe jedoch bereits eine vorhandene Instanz der Aktivität oben auf ihrem Stapel hat, erhält diese Instanz die neue Absicht (in einem Aufruf von onNewIntent ()). Eine neue Instanz wird nicht erstellt. Unter anderen Umständen - zum Beispiel, wenn sich eine vorhandene Instanz der Aktivität "singleTop" in der Zielaufgabe befindet, jedoch nicht oben im Stapel, oder wenn sie sich oben auf einem Stapel befindet, jedoch nicht in der Zielaufgabe - a Eine neue Instanz wird erstellt und auf den Stapel verschoben.

Eric Levine
quelle
2
Ich habe darüber nachgedacht, aber was ist, wenn die Aktivität nicht ganz oben auf dem Stapel steht? Zum Beispiel scheint singleTop AA zu verhindern, nicht jedoch ABA.
Bsberkeley
Können Sie mit singleTop und den Finish-Methoden in Activity das erreichen, was Sie wollen?
Eric Levine
Ich weiß nicht, ob es das erreichen wird, was ich will. Beispiel: Wenn ich nach dem Poppen von A und B in Aktivität C bin, wird eine neue Aktivität A gestartet und ich habe so etwas wie CA, nicht wahr?
Bsberkeley
Es ist schwer, dies zu beantworten, ohne mehr darüber zu verstehen, was diese Aktivitäten bewirken. Können Sie weitere Details zu Ihrer Bewerbung und den Aktivitäten angeben? Ich frage mich, ob es eine Nichtübereinstimmung zwischen der Funktion der Home-Schaltfläche und der Funktionsweise gibt. Die Home-Schaltfläche beendet eine Aktivität nicht, sondern "hinterlegt" sie, damit der Benutzer zu etwas anderem wechseln kann. Die Zurück-Taste ist das, was beendet / beendet wird und Aktivität. Das Brechen dieses Paradigmas könnte Benutzer verwirren / frustrieren.
Eric Levine
Ich habe diesem Thread eine weitere Antwort hinzugefügt, damit Sie eine Kopie des Manifests sehen können.
Bsberkeley
4

Vielleicht ist es dieses Problem ? Oder eine andere Form des gleichen Fehlers?

DuneCat
quelle
Siehe auch code.google.com/p/android/issues/detail?id=26658 , das zeigt, dass es durch andere Dinge als Eclipse verursacht wird.
Kristopher Johnson
1
Also sollte ich eine Problembeschreibung kopieren und einfügen, die veraltet sein könnte? Welche Teile? Sollten die wesentlichen Teile erhalten bleiben, wenn sich der Link ändert, und liegt es in meiner Verantwortung, dass die Antwort auf dem neuesten Stand gehalten wird? Man sollte denken, dass der Link nur ungültig wird, wenn das Problem behoben ist. Dies ist schließlich kein Link zu einem Blog.
DuneCat
2

Ich denke, die akzeptierte Antwort ( Duane Homick ) hat unbehandelte Fälle:

Sie haben verschiedene Extras (und als Ergebnis App-Duplikate):

  • Wenn Sie die Anwendung über Market oder über das Startbildschirmsymbol starten (das von Market automatisch platziert wird).
  • Wenn Sie die Anwendung mit dem Launcher oder dem manuell erstellten Startbildschirmsymbol starten

Hier ist eine Lösung (SDK_INT> = 11 für Benachrichtigungen), die meiner Meinung nach auch diese Fälle und Statusleistenbenachrichtigungen behandelt.

Manifest :

    <activity
        android:name="com.acme.activity.LauncherActivity"
        android:noHistory="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>
    <service android:name="com.acme.service.LauncherIntentService" />

Launcher-Aktivität :

public static Integer lastLaunchTag = null;
@Override
public void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mInflater = LayoutInflater.from(this);
    View mainView = null;
    mainView = mInflater.inflate(R.layout.act_launcher, null); // empty layout
    setContentView(mainView);

    if (getIntent() == null || getIntent().getExtras() == null || !getIntent().getExtras().containsKey(Consts.EXTRA_ACTIVITY_LAUNCH_FIX)) {
        Intent serviceIntent = new Intent(this, LauncherIntentService.class);
        if (getIntent() != null && getIntent().getExtras() != null) {
            serviceIntent.putExtras(getIntent().getExtras());
        }
        lastLaunchTag = (int) (Math.random()*100000);
        serviceIntent.putExtra(Consts.EXTRA_ACTIVITY_LAUNCH_TAG, Integer.valueOf(lastLaunchTag));
        startService(serviceIntent);

        finish();
        return;
    }

    Intent intent = new Intent(this, SigninActivity.class);
    if (getIntent() != null && getIntent().getExtras() != null) {
        intent.putExtras(getIntent().getExtras());
    }
    startActivity(intent);
}

Service :

@Override
protected void onHandleIntent(final Intent intent) {
    Bundle extras = intent.getExtras();
    Integer lastLaunchTag = extras.getInt(Consts.EXTRA_ACTIVITY_LAUNCH_TAG);

    try {
        Long timeStart = new Date().getTime(); 
        while (new Date().getTime() - timeStart < 100) {
            Thread.currentThread().sleep(25);
            if (!lastLaunchTag.equals(LauncherActivity.lastLaunchTag)) {
                break;
            }
        }
        Thread.currentThread().sleep(25);
        launch(intent);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

private void launch(Intent intent) {
    Intent launchIintent = new Intent(LauncherIntentService.this, LauncherActivity.class);
    launchIintent.addCategory(Intent.CATEGORY_LAUNCHER);
    launchIintent.setAction(Intent.ACTION_MAIN); 
    launchIintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    launchIintent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 
    if (intent != null && intent.getExtras() != null) {
        launchIintent.putExtras(intent.getExtras());
    }
    launchIintent.putExtra(Consts.EXTRA_ACTIVITY_LAUNCH_FIX, true);
    startActivity(launchIintent);
}

Benachrichtigung :

ComponentName actCN = new ComponentName(context.getPackageName(), LauncherActivity.class.getName()); 
Intent contentIntent = new Intent(context, LauncherActivity.class);
contentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);    
if (Build.VERSION.SDK_INT >= 11) { 
    contentIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // if you need to recreate activity stack
}
contentIntent.addCategory(Intent.CATEGORY_LAUNCHER);
contentIntent.setAction(Intent.ACTION_MAIN);
contentIntent.putExtra(Consts.EXTRA_CUSTOM_DATA, true);
StanislavKo
quelle
2

Mir ist klar, dass die Frage nichts mit Xamarin Android zu tun hat, aber ich wollte etwas posten, da ich es nirgendwo anders gesehen habe.

Um dies in Xamarin Android zu beheben, habe ich den Code von @DuaneHomick verwendet und hinzugefügt MainActivity.OnCreate(). Der Unterschied zu Xamarin ist, dass es nach Xamarin.Forms.Forms.Init(this, bundle);und gehen muss LoadApplication(new App());. So OnCreate()würde mein aussehen:

protected override void OnCreate(Bundle bundle) {
    base.OnCreate(bundle);

    Xamarin.Forms.Forms.Init(this, bundle);
    LoadApplication(new App());

    if(!IsTaskRoot) {
        Intent intent = Intent;
        string action = intent.Action;
        if(intent.HasCategory(Intent.CategoryLauncher) && action != null && action.Equals(Intent.ActionMain, System.StringComparison.OrdinalIgnoreCase)) {
            System.Console.WriteLine("\nIn APP.Droid.MainActivity.OnCreate() - Finishing Activity and returning since a second MainActivity has been created.\n");
            Finish();
            return; //Not necessary if there is no code below
        }
    }
}

* Bearbeiten: Seit Android 6.0 reicht die oben genannte Lösung für bestimmte Situationen nicht aus. Ich habe jetzt auch eingestellt LaunchModeauf SingleTask, die Dinge gemacht zu haben scheint wieder einmal korrekt funktionieren. Ich bin mir jedoch nicht sicher, welche Auswirkungen dies auf andere Dinge haben könnte.

hvaughan3
quelle
0

Ich hatte auch dieses Problem

  1. Rufen Sie nicht finish () auf. In der Heimaktivität würde es endlos ausgeführt - die Heimaktivität wird von ActivityManager aufgerufen, wenn sie beendet ist.
  2. Wenn sich die Konfiguration ändert (dh Bildschirm drehen, Sprache ändern, Telefoniedienst ändert, z. B. mcc mnc usw.), wird die Aktivität normalerweise neu erstellt - und wenn die Heimaktivität ausgeführt wird, wird erneut A. aufgerufen, damit sie zum Manifest hinzugefügt werden muss android:configChanges="mcc|mnc"- wenn Sie haben eine Verbindung zum Mobiltelefon, siehe http://developer.android.com/guide/topics/manifest/activity-element.html#config, welche Konfiguration beim Booten des Systems oder beim Öffnen oder was auch immer vorhanden ist.
user1249350
quelle
0

Versuchen Sie diese Lösung:
Erstellen Sie eine ApplicationKlasse und definieren Sie dort:

public static boolean IS_APP_RUNNING = false;

Dann in Ihrer ersten (Launcher) Aktivität in onCreatevor setContentView(...)hinzufügen:

if (Controller.IS_APP_RUNNING == false)
{
  Controller.IS_APP_RUNNING = true;
  setContentView(...)
  //Your onCreate code...
}
else
  finish();

PS Controllerist meine ApplicationKlasse.

Volodymyr Kulyk
quelle
Sie sollten einen primitiven Booleschen Wert verwenden, wodurch die Überprüfung auf Null nicht erforderlich ist.
WonderCsabo
Das wird nicht immer funktionieren. Sie könnten Ihre App niemals starten, beenden und dann schnell wieder starten. Android beendet den Hosting-Betriebssystemprozess nicht unbedingt, sobald keine aktiven Aktivitäten vorhanden sind. In diesem Fall lautet die Variable, wenn Sie die App erneut starten IS_APP_RUNNING, trueund Ihre App wird sofort beendet. Nicht etwas, das der Benutzer wahrscheinlich amüsant findet.
David Wasser
-1

Ich hatte das gleiche Problem und habe es mit der folgenden Lösung behoben.

Fügen Sie in Ihrer Hauptaktivität diesen Code oben in die onCreateMethode ein:

ActivityManager manager = (ActivityManager) this.getSystemService( ACTIVITY_SERVICE );
List<RunningTaskInfo> tasks =  manager.getRunningTasks(Integer.MAX_VALUE);

for (RunningTaskInfo taskInfo : tasks) {
    if(taskInfo.baseActivity.getClassName().equals(<your package name>.<your class name>) && (taskInfo.numActivities > 1)){
        finish();
    }
}

Vergessen Sie nicht, diese Berechtigung in Ihr Manifest aufzunehmen.

< uses-permission android:name="android.permission.GET_TASKS" />

hoffe es hilft dir

Gugarush
quelle
-2

Versuchen Sie, den SingleInstance- Startmodus mit der Affinität " allowtaskreparenting" zu verwenden. Dadurch wird die Aktivität immer in einer neuen Aufgabe erstellt, aber auch die Reparatur ermöglicht. Überprüfen Sie das Attribut dis: Affinity

Shaireen
quelle
2
Funktioniert wahrscheinlich nicht, da laut Dokumentation "die Wiedererziehung auf die Modi" Standard "und" SingleTop "beschränkt ist". weil "Aktivitäten mit" singleTask "oder" singleInstance "Startmodi nur die Wurzel einer Aufgabe sein können"
bsberkeley
-2

Ich habe einen Weg gefunden, um zu verhindern, dass dieselben Aktivitäten gestartet werden. Dies funktioniert hervorragend für mich

if ( !this.getClass().getSimpleName().equals("YourActivityClassName")) {
    start your activity
}
Odhik Susanto
quelle