web-dev-qa-db-fra.com

NavUtils.navigateUpTo () ne démarre aucune activité

J'ai deux activités

  • MainActivity
  • DeepLinkActivity

J'ai tout mis en place pour utiliser la NavUtils pour naviguer comme conseillé ici , ici et ici .

Ce que je veux réaliser, c'est:

  1. Démarrer DeepLinkActivity via un lien profond
  2. Appuyez vers le haut
  3. Aller à MainActivity

Tout fonctionne bien tant qu'il y a une tâche de mon application dans les applications récentes. 

Cependant, lorsque je balaye mon application des applications récentes, elle se comporte comme suit:

  1. Balayer mon application à partir d'applications récentes
  2. Démarrer DeepLinkActivity via un lien profond
  3. Appuyez vers le haut
  4. Mon application se ferme, comme lorsque vous appuyez à nouveau

J'ai débogué le code et découvert que NavUtils.shouldUpRecreateTask() renvoie false.upIntent tout est réglé sur normal, comme mon Component set . Mais néanmoins, NavUtils.navigateUpTo() se comporte comme un appel à finish(). , rien.

Des idées pour réparer celà?

AndroidManifest.xml

<activity
    Android:name=".DeepLinkActivity"
    Android:parentActivityName="my.package.MainActivity">
    <meta-data
        Android:name="Android.support.PARENT_ACTIVITY"
        Android:value="my.package.MainActivity"/>
    <intent-filter>
        <!-- Some intent filter -->
    </intent-filter>
</activity>

DeepLinkActivity.Java

@Override
public boolean onOptionsItemSelected(final MenuItem item) {
    switch (item.getItemId()) {
        case Android.R.id.home:
            Intent upIntent = NavUtils.getParentActivityIntent(this);
            if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
                // create new task
                TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent)
                        .startActivities();
            } else {
                // Stay in same task
                NavUtils.navigateUpTo(this, upIntent);
            }
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

----- Modifier -----

Je me suis rendu compte que quelques Google Apps sont cassés de la même manière. Si vous sautez par exemple Dans Contacts depuis la recherche, appuyez en haut sur AB et vous vous retrouverez sur l'écran d'accueil au lieu de l'application Contacts. (API19/cm11)

41
flx

Je pense que cette méthode est buggée. J'ai lu le code source de la bibliothèque de support et cette méthode vérifie l'action de l'intention. Elle ne fonctionne que lorsque votre application a été créée précédemment. Ainsi que vous l'avez décrit, si vous la supprimez de l'aperçu des applications, la méthode ShouldUp ne fonctionne plus.

J'ai corrigé cela en utilisant ma propre "shouldUpRecreateTask". Lorsque je reçois une notification qui crée directement une activité (comme votre comportement), j'envoie de mon BroadCastReceiver une action personnalisée à l'intérieur de l'intention. Ensuite, dans ma méthode, je fais la chose suivante:

private final boolean shouldUpRecreateTask(Activity from){
    String action = from.getIntent().getAction();
    return action != null && action.equals(com.xxxxxx.activities.Intent.FROM_NOTIFICATION);
}

..........................

 if (shouldUpRecreateTask(this)) {
      TaskStackBuilder.create(this)
       .addNextIntentWithParentStack(upIntent)
       .startActivities();
 } else {
       fillUpIntentWithExtras(upIntent);
       NavUtils.navigateUpTo(this, upIntent);
 }
21
noni

Ma solution au problème des PO:

public void navigateUp() {
    final Intent upIntent = NavUtils.getParentActivityIntent(this);
    if (NavUtils.shouldUpRecreateTask(this, upIntent) || isTaskRoot()) {
        Log.v(logTag, "Recreate back stack");
        TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent).startActivities();
    } else {
        NavUtils.navigateUpTo(this, upIntent);
    }
}

isTaskRoot() retournera true si DeepLinkActivity est la racine d'une tâche (le lancement initial de l'application ou l'application était précédemment terminé via le gestionnaire de tâches). De cette façon, je ne perds pas la pile de réserve existante si une activité était lancée via un lien alors que la tâche des applications était déjà au premier plan.

52
Sokolov

Il semble que NavUtils.shouldUpRecreateTask(this, upIntent) ne soit pas préparé à ce cas particulier.

Ma solution actuelle consiste à vérifier si la Activity était liée de manière profonde et à forcer une nouvelle pile de tâches pour des cas comme celui-ci.

Dans mon cas, je passe des objets dans EXTRAs en interne . Mais ACTION et Uri est défini si Activity est démarré de l'extérieur en suivant un lien vers une URL spécifique ou en touchant des périphériques NFC.

@Override
public boolean onOptionsItemSelected(final MenuItem item) {
    switch (item.getItemId()) {
        case Android.R.id.home:
            Intent upIntent = NavUtils.getParentActivityIntent(this);
            if (NavUtils.shouldUpRecreateTask(this, upIntent)
                    || getIntent().getAction() != null) { // deep linked: force new stack
                // create new task
                TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent)
                        .startActivities();
            } else {
                // Stay in same task
                NavUtils.navigateUpTo(this, upIntent);
            }
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}
7
flx

Utilisez-vous un <activity-alias> comme MAIN/LAUNCHER? Lorsque je change le <activity-alias> en <activity>, la navigation vers le haut fonctionne correctement. Pour une raison étrange, la navigation ne fonctionne pas correctement lorsque des alias sont impliqués.

Il y a aussi un petit problème d'interface utilisateur étrange qui indique que le ActivityManager natif a un bogue. Après avoir accédé à l'activité principale de l'application à l'aide de navigateUpTo(), appuyez sur la touche Retour de l'appareil. L'application en cours exécute l'animation de sortie d'activité, puis immédiatement après l'application la plus récente, exécute également une animation de sortie d'activité. Cela se produit même si vous avez lancé l'application actuelle à partir de l'écran d'accueil d'Android. Si vous effacez toutes les applications récentes et recommencez, une application aléatoire (c'est-à-dire inattendue) sera présentée lors de la sortie de l'animation.

D'autres applications ne devraient jamais être présentées dans ces cas. Il semble que le gestionnaire d'activités ne gère pas correctement la pile d'historique.

Le bogue incriminé peut être trouvé dans la méthode suivante qui apparaît au bas de this diff :

+    public boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
+            Intent resultData) {
+        ComponentName dest = destIntent.getComponent();
+
+        synchronized (this) {
+            ActivityRecord srec = ActivityRecord.forToken(token);
+            ArrayList<ActivityRecord> history = srec.stack.mHistory;
+            final int start = history.indexOf(srec);
+            if (start < 0) {
+                // Current activity is not in history stack; do nothing.
+                return false;
+            }
+            int finishTo = start - 1;
+            ActivityRecord parent = null;
+            boolean foundParentInTask = false;
+            if (dest != null) {
+                TaskRecord tr = srec.task;
+                for (int i = start - 1; i >= 0; i--) {
+                    ActivityRecord r = history.get(i);
+                    if (tr != r.task) {
+                        // Couldn't find parent in the same task; stop at the one above this.
+                        // (Root of current task; in-app "home" behavior)
+                        // Always at least finish the current activity.
+                        finishTo = Math.min(start - 1, i + 1);
+                        parent = history.get(finishTo);
+                        break;
+                    } else if (r.info.packageName.equals(dest.getPackageName()) &&
+                            r.info.name.equals(dest.getClassName())) {
+                        finishTo = i;
+                        parent = r;
+                        foundParentInTask = true;
+                        break;
+                    }
+                }
+            }
+
+            if (mController != null) {
+                ActivityRecord next = mMainStack.topRunningActivityLocked(token, 0);
+                if (next != null) {
+                    // ask watcher if this is allowed
+                    boolean resumeOK = true;
+                    try {
+                        resumeOK = mController.activityResuming(next.packageName);
+                    } catch (RemoteException e) {
+                        mController = null;
+                    }
+
+                    if (!resumeOK) {
+                        return false;
+                    }
+                }
+            }
+            final long origId = Binder.clearCallingIdentity();
+            for (int i = start; i > finishTo; i--) {
+                ActivityRecord r = history.get(i);
+                mMainStack.requestFinishActivityLocked(r.appToken, resultCode, resultData,
+                        "navigate-up");
+                // Only return the supplied result for the first activity finished
+                resultCode = Activity.RESULT_CANCELED;
+                resultData = null;
+            }
+
+            if (parent != null && foundParentInTask) {
+                final int parentLaunchMode = parent.info.launchMode;
+                final int destIntentFlags = destIntent.getFlags();
+                if (parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE ||
+                        parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TASK ||
+                        parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP ||
+                        (destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
+                    parent.deliverNewIntentLocked(srec.app.uid, destIntent);
+                } else {
+                    try {
+                        ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
+                                destIntent.getComponent(), 0, UserId.getCallingUserId());
+                        int res = mMainStack.startActivityLocked(srec.app.thread, destIntent,
+                                null, aInfo, parent.appToken, null,
+                                0, -1, parent.launchedFromUid, 0, null, true, null);
+                        foundParentInTask = res == ActivityManager.START_SUCCESS;
+                    } catch (RemoteException e) {
+                        foundParentInTask = false;
+                    }
+                    mMainStack.requestFinishActivityLocked(parent.appToken, resultCode,
+                            resultData, "navigate-up");
+                }
+            }
+            Binder.restoreCallingIdentity(origId);
+            return foundParentInTask;
+        }
+    }
4
James Wald

J'ai trouvé ce qui suit a fonctionné pour moi. Si l'activité était profondément liée à une autre activité ou notification, la pile serait créée au fur et à mesure de votre navigation, sinon les activités seraient simplement mises en avant.

case Android.R.id.home:
Intent upIntent = NavUtils.getParentActivityIntent(this);
upIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(upIntent);
finish();
return true;

Pourvu que vous ayez 

Android:parentActivityName="parent.activity"

dans votre manifeste

3
David

Si vous accédez à votre activité depuis une notification, vous devrez créer la pile lors de la création de la notification, vérifiez cette réponse: NavUtils.shouldUpRecreateTask échoue sur JellyBean

2
Ciprian

Cela ne fonctionne pas pour moi aussi. Alors je me débrouille comme ça:

   @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == Android.R.id.home) {

                Intent intent = new Intent(this, MainActivity.class);
                startActivity(intent);
                finish();
                return true;

        }
        return super.onOptionsItemSelected(item);
    }

Mon MainActivity est marqué comme singleTask dans le manifeste Android

Android:launchMode="singleTask"
2
vandzi

Essaye ça :

@Override
public boolean onOptionsItemSelected(final MenuItem item) {
    switch (item.getItemId()) {
        case Android.R.id.home:
            Intent upIntent = NavUtils.getParentActivityIntent(this);
            if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
                // create new task
                TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent)
                        .startActivities();
            } else {
                // Stay in same task
                NavUtils.navigateUpTo(this, upIntent);
            }
            break;
        default:
            return super.onOptionsItemSelected(item);
    }
    return true;
}
1
SweetWisher ツ

Essayez la réponse suivante en travaillant pour moi dans les deux cas, démarrez l'activité à partir de la notification et démarrez une activité à partir de l'activité parent.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case Android.R.id.home:
            Intent upIntent = NavUtils.getParentActivityIntent(this);
            if (NavUtils.shouldUpRecreateTask(this, upIntent) || isTaskRoot()) {
                TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent)
                        .startActivities();
            } else {
                finish();
            }
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}
0
user2837615