web-dev-qa-db-fra.com

Intention de compensation

Mon application Android est appelée par une intention qui transmet des informations (en attente dans la barre d'état).

Lorsque je clique sur le bouton d'accueil et que je rouvre mon application en maintenant le bouton d'accueil enfoncé, il rappelle l'intention et les mêmes extras sont toujours là.

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
      super.onSaveInstanceState(savedInstanceState);
    }
    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
      super.onRestoreInstanceState(savedInstanceState);
    }

c'est le code qui ne fonctionne pas comme si on le supposait

    String imgUrl;
    Bundle extras = this.getIntent().getExtras();


    if(extras != null){
        imgUrl = extras.getString("imgUrl");
        if( !imgUrl.equals(textView01.getText().toString()) ){

            imageView.setImageDrawable( getImageFromUrl( imgUrl ) );
            layout1.setVisibility(0);
            textView01.setText(imgUrl);//textview to hold the url

        }

    }

Et mon intention:

public void showNotification(String ticker, String title, String message, 
    String imgUrl){
    String ns = Context.NOTIFICATION_SERVICE;
    NotificationManager mNotificationManager = 
        (NotificationManager) getSystemService(ns);
    int icon = R.drawable.icon;        // icon from resources
    long when = System.currentTimeMillis();         // notification time
    CharSequence tickerText = ticker;              // ticker-text

    //make intent
    Intent notificationIntent = new Intent(this, activity.class);
    notificationIntent.putExtra("imgUrl", imgUrl);
    notificationIntent.setFlags(
        PendingIntent.FLAG_UPDATE_CURRENT | 
        PendingIntent.FLAG_ONE_SHOT);
    PendingIntent contentIntent = 
        PendingIntent.getActivity(this, 0, 
        notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT | 
        PendingIntent.FLAG_ONE_SHOT);

    //make notification
    Notification notification = new Notification(icon, tickerText, when);
    notification.setLatestEventInfo(this, title, message, contentIntent);
    //flags
    notification.flags = Notification.FLAG_SHOW_LIGHTS | 
        Notification.FLAG_ONGOING_EVENT | 
        Notification.FLAG_ONLY_ALERT_ONCE | 
        Notification.FLAG_AUTO_CANCEL;
    //sounds
    notification.defaults |= Notification.DEFAULT_SOUND;
    //notify
    mNotificationManager.notify(1, notification);
}

Existe-t-il un moyen de clarifier l’intention ou de vérifier si elle a déjà été utilisée?

87
Mars

METTRE À JOUR:

Je n'avais pas réalisé que cette réponse ferait tellement allusion lorsque je l'ai écrite pour la première fois il y a plus de 5 ans!

Je préciserai que, selon la réponse de @ tato-rodrigo, cela ne vous aidera pas à détecter une intention déjà gérée dans certaines situations. 

Aussi, je devrais souligner que je mets "clair" entre guillemets pour une raison - vous êtes pas effacez vraiment l'intention en faisant cela, vous utilisez simplement la suppression de l'extra comme indicateur que cette intention a été vue par l'activité déjà.


J'ai eu exactement le même problème.

La réponse ci-dessus m'a mis sur la bonne voie et j'ai trouvé une solution encore plus simple, utilisez le:

getIntent().removeExtra("key"); 

appel de méthode pour "effacer" l'intention.

Il est un peu tardif depuis que cela a été demandé il y a un an, mais j'espère que cela aidera d'autres à l'avenir. 

149
Maks

EDIT: J'édite pour publier la solution complète que j'utilise.

Cette solution fonctionnera si le problème est "Ne pas exécuter de code lorsque l'activité démarre à partir de l'historique (Applications récentes)".

Tout d’abord, déclarez une boolean dans votre Activity pour indiquer si la Intent a déjà été consommée:

    private boolean consumedIntent;

Ensuite, stockez et restaurez en toute sécurité cette valeur à l'aide des méthodes onSaveInstanceState et onCreate pour gérer les modifications de configuration et les cas où le système peut tuer votre Activity lorsqu'elle passe en arrière-plan.

    private final String SAVED_INSTANCE_STATE_CONSUMED_INTENT = "SAVED_INSTANCE_STATE_CONSUMED_INTENT";

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean(SAVED_INSTANCE_STATE_CONSUMED_INTENT, consumedIntent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //set content view ...

        if( savedInstanceState != null ) {
            consumedIntent = savedInstanceState.getBoolean(SAVED_INSTANCE_STATE_CONSUMED_INTENT);
        }

        //other initializations
    }

Maintenant, vérifiez si vous pouvez exécuter votre code avec la méthode onResume.

    @Override
    protected void onResume() {
        super.onResume();

        //check if this intent should run your code
        //for example, check the Intent action
        boolean shouldThisIntentTriggerMyCode = [...];
        Intent intent = getIntent();
        boolean launchedFromHistory = intent != null ? (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0 : false;
        if( !launchedFromHistory && shouldThisIntentTriggerMyCode && !consumedIntent ) {
            consumedIntent = true;
            //execute the code that should be executed if the activity was not launched from history
        }
    }

De plus, si votre Activity est configuré sur singleTop, vous devez réinitialiser votre indicateur lorsqu'un nouveau Intent est remis.

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
        consumedIntent = false;
    }
37
tato.rodrigo

Maks answer travaille pour dégager un extra:

    getIntent().removeExtra("key"); 

Une autre commande utile est:

    getIntent().setAction("");

Vous pouvez également marquer une intention en appelant:

    getIntent().putExtra("used", true);

et puis juste pour vérifier la valeur.

18
Slynk

Lorsque nous lançons des applications Android à partir de History (Recent Apps), l'application peut être lancée principalement avec trois indicateurs d'intention différents.

  1. FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
    C'est à ce moment que l'activité est lancée à partir de l'historique d'une application qui a été réduite au minimum (touche d'accueil à l'appui long).
    Valeur constante: 1048576 (0x00100000)
  2. FLAG_ACTIVITY_NEW_TASK
    C'est à ce moment que l'activité est lancée via "en cliquant sur l'icône de l'application" ou par " Filtres d'intention ". Ici, l'activité deviendra le début d'une nouvelle tâche sur cette pile d'historique.
    Valeur constante: 268435456 (0x10000000)
  3. FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY | FLAG_ACTIVITY_NEW_TASK
    C’est à ce moment que l’application a été fermée en appuyant sur le bouton Précédent puis reprise de l’historique (applications récentes).
    Valeur constante: 269484032 (0x10100000)

La valeur constante peut être récupérée via getIntent().getFlags()

Dans le troisième cas, Android recharge les dernières valeurs d’intention de sa mémoire. L'intention de votre application (getIntent) aura donc les valeurs de la dernière intention qui a lancé l'application.

En fait, l'application devrait se comporter comme s'il s'agissait d'un nouveau lancement, avec les valeurs d'intention pour un nouveau lancement plutôt que les valeurs d'intention du lancement précédent. Ce comportement peut être observé si vous lancez l'application en cliquant sur l'icône de l'application, il n'aura jamais d'anciennes valeurs d'intention. En effet, Android utilise le filtre d'intention suivant pour ce scénario

 <intent-filter>
        <action Android:name="Android.intent.action.MAIN" />
        <category Android:name="Android.intent.category.LAUNCHER"/>
 </intent-filter>

Mais dans le troisième cas (application qui a été fermée, lancée à partir de l'historique des applications récentes), le système d'exploitation Android utilise la dernière intention qui a lancé l'application avant de la quitter (en appuyant sur le bouton Précédent). Donc, vous finissez par avoir d'anciennes valeurs d'intention et le flux d'application n'est pas correct.

Supprimer l’intention est un moyen de le résoudre, mais cela ne résoudrait pas complètement le problème! En tant que système d'exploitation Android recharge l'intention depuis le dernier lancement des applications, et non la dernière instance de l'intention de lancement.

Un moyen propre d'éviter ce problème consiste à le gérer en indiquant le type de Intention pour déterminer le type de lancement.

Ainsi, dans votre LaunchActivity (celui qui a le filtre d'intention défini dans le manifeste), vous pouvez utiliser le code suivant dans les méthodes onCreate(), onStart() ou onResume().

if(getIntent().getFlags() == (Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY)) {
    //app is launched from recent apps after it was closed
        normalLaunch();
    } else {
        String intentAction = getIntent().getAction();
        String scheme = getIntent().getScheme();
        //app is launched via other means
        // URL intent scheme, Intent action etc
        if("https".equalsIgnoreCase(scheme)) {
            // URL intent for browser
        } else if("com.example.bb".equalsIgnoreCase(intentAction)) {
            // App launched via package name
        } else {
            // App was launched via Click on App Icon, or other means
            normalLaunch();
        }
    }

Je suppose que normalLaunch(), ne devrait pas utiliser les paramètres de l’intention; sinon, vous devrez séparer et optimiser votre méthode de lancement par défaut pour ne pas utiliser les paramètres d'intention.

11
B.B.
intent.replaceExtras(new Bundle());
intent.setAction("");
intent.setData(null);
intent.setFlags(0);
9
Mickey Tin

La réponse courte est impossible

Longue réponse. Il n’existe pas d’intention «unique». Par expérience, on constate que l’histoire récente des activités d’Androïdes modernes n’est autre que «l’histoire de l’intention». La dernière intention transmise à l'activité est simplement connectée au système et c'est tout. Les gens ci-dessus suggèrent d'utiliser

setAction("")

Mais cela ne fonctionne pas parce que l'intention est déjà enregistrée jusqu'au moment où vous l'obtenez dans la méthode onNewIntent () ou onStart (). 

J'ai résolu le problème en évitant l'utilisation d'intentions. Mon problème était semblable à celui posté par l'auteur. J'ai essayé d'implémenter Global Exit de l'application via le contrôle dans la zone de notification. Il devrait arrêter le service sous-jacent et fermer toutes les activités de l'application. Vous pouvez trouver le même comportement dans l'application Waze.

L'algorithme:

  1. Créez PendingIntent pour le contrôle de notification qui transmet l'action "Exit" à l'activité. Mais à l'activité spéciale qui est un simple proxy. 
  2. L'activité proxy du code onStart () analyse l'intention, vérifie l'action et définit l'état d'un modèle sur "Exited". 
  3. L'activité proxy du code onStart () efface l'intention initiale à l'aide de setIntent (""), puis la transmet à l'activité "Racine" de destination en appelant startActivity (intention).
  4. L'activité proxy du code onStart () invoque finish ().
  5. À l'intérieur de onStart () et onNewIntent () de l'activité de destination, vérifiez l'état du modèle et appelez finish () s'il est "Quitté" (et appelez stopService () dans mon cas).

J'espère que cela aidera à quelqu'un parce que je n'ai pas trouvé la réponse sur Internet.

7
Eugene Wechsler

Assurez-vous que vous utilisez PendingIntent.FLAG_UPDATE_CURRENT flag pour PendingIntent .

PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, mPutIntent, PendingIntent.FLAG_UPDATE_CURRENT);

mPutIntent est votre Intent.

J'espère que ceci vous aidera.

4
Hiren Patel

Je ne pouvais pas trouver un moyen de supprimer Intent Extra . Aucune des réponses concernant supprimer les éléments supplémentaires de l'intention ne fonctionne si vous activez "Ne pas conserver les activités" dans Options du développeur (vous pouvez ainsi détruire Activité et revenir pour tester si des Extras existent toujours).

En tant que solution au problème, i stocke une valeur booléenne dans SharedPreferences après le traitement des suppléments Intent. Lorsque la même intention est redistribuée à l'activité, je vérifie la valeur SharedPreference et décide de traiter l'intention supplémentaire. Si vous envoyez une autre nouvelle Intent Extra à la même activité, vous définissez SharedPreference sur false et Activity la traite. Exemple :

// Start Activity with Intent Extras
Intent intent = new Intent(context, MyActivity.class);
intent.putExtra("someData", "my Data");
// Set data as not processed
context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE).edit().putBoolean("myActivityExtraProccessed", false).commit();
context.startActivity(intent);

...

public class MyActivity{

    ...
    public void someMethod(){
        boolean isExtrasProcessed = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE).getBoolean("myActivityExtraProccessed", false);  
         if (!isExtrasProcessed) {
              // Use Extras

              //Set data as processed
              context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE).edit().putBoolean("myActivityExtraProccessed", true).commit();
         }
    }

}
1
Jemshit Iskenderov

J'ai exactement le même problème. Ma solution consistait à ajouter la variable boolean définie lorsque Intent était 'utilisée' et l'instruction if basée sur cette boolean pour vérifier si vous deviez utiliser Intent ou non.

1
pixel

J'ai récemment eu ce problème et je l'ai résolu en ajoutant un horodatage en tant que paramètre supplémentaire à l'intention:

private void launchActivity(Context context) {
    Intent intent = new Intent(context, MainActivity.class);
    intent.putExtra("KEY_EXTRA_TIMESTAMP", System.currentTimeMillis());
    context.startActivity(intent);
}

Ensuite, enregistrez l’horodatage dans les préférences partagées:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    long time = getIntent().getLongExtra("KEY_EXTRA_TIMESTAMP", -1);
    long previousTime = getPreferences(MODE_PRIVATE).getLong("timestamp", -1);

    //ignore if the timestamp is the same as the previous one  
    if (time != previousTime) {
        handleIntent(getIntent());
        if (time != -1) {
            //save the timestamp
            getPreferences(MODE_PRIVATE).edit().putLong("timestamp", time).apply();
        }
    }
}

Lorsque vous avez terminé de traiter l'intention, procédez comme suit:

setIntent(null);

Vous ne verrez plus cette intention traitée et vous ne masquerez pas le problème en modifiant le contenu de cette intention traitée.

1
straya

Même après avoir effacé manuellement les extras d'intention et d'intention après leur analyse, il semble que Activity.getIntent () renvoie toujours l'intention d'origine qui a démarré l'activité.

Pour contourner cela, je recommande quelque chose comme ceci:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // The Intent provided by getIntent() (and its extras) will persist through a restore
    // via savedInstance.  Because of this, restoring this activity from a
    // an instance that was originally started with extras (deep-link or 
    // pre-defined destination) may cause un-desired behavior
    // (ie...infinite loop of sending the user directly to somewhere else because of a
    // pre-defined alternate destination in the Intent's extras).
    //
    // To get around this, if restoring from savedInstanceState, we explicitly
    // set a new Intent *** to override the original Intent that started the activity.***
    // Note...it is still possible to re-use the original Intent values...simply
    // set them in the savedInstanceState Bundle in onSavedInstanceState.
    if (savedInstanceState != null) {
        // Place savedInstanceState Bundle as the Intent "extras"
        setIntent(new Intent().putExtras(savedInstanceState));
    }

    processIntent(getIntent())
}

private void processIntent(Intent intent) {
    if (getIntent().getExtras() == null) {
        // Protection condition
        return;
    }

    doSomething(intent.getExtras.getString("SOMETHING_I_REALLY_NEED_TO_PERSIST"));

    final String somethingIDontWantToPersist = 
        intent.getExtras.getString("SOMETHING_I_DONT_WANT_TO_PERSIST");

    if(somethingIDontWantToPersist != null) {
        doSomething(somethingIDontWantToPersist);
    }
}

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save selective extras from original Intent...
    savedInstanceState.putString("SOMETHING_I_REALLY_NEED_TO_PERSIST", "persistedValued");
    super.onSaveInstanceState(savedInstanceState);
}

De cette façon, il existe un mécanisme permettant de vider l’intention initiale tout en conservant la possibilité de conserver explicitement certaines parties des extras d’intention/intention originaux.

Notez que je n'ai pas testé tous les modes de lancement d'activité.

0
dell116

La méthode la plus simple consiste à éviter d'appeler getIntent () à partir de méthodes autres que onCreate (). Mais cela causera un problème lors du prochain lancement si l'utilisateur quitte notre activité en appuyant sur le bouton Accueil. Je pense que ce problème n'a pas une solution entièrement fonctionnelle.

0
mifthi

Je suis confronté au même problème et j'essaie d'utiliser les méthodes ci-dessus, mais cela ne fonctionne pas. 

Je pense que c'est peut-être à cause du mode de lancement de l'activité que j'ai utilisé le mode singleTop.

Lorsque j'appuie en arrière-plan et que j'utilise RamEater pour simuler le problème, cette intention a toujours un caractère supplémentaire, même si je la définis comme nulle ou si je supprime la clé.

Le problème a disparu en utilisant le stockage de préférences sur Android pour vérifier le passage.