意图清除

144

我的安卓应用程序通过一个传递信息的意图(在状态栏中是pendingintent)被调用。

当我按下主屏幕按钮并通过长按主屏幕按钮重新打开我的应用程序时,它会再次调用该意图,并且相同的额外信息仍然存在。

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

这是代码,但它没有按照预期运行。

    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

        }

    }

我的意图:

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);
}

有没有办法清除意图或检查它是否已被使用过?


你能发布你的代码吗? - xandy
我已经将代码添加到我的问题中。 - Mars
不要清除Intent,而是确定启动类型并相应地处理应用程序流程。仅在从通知启动而不是后台启动时获取Extras。 https://dev59.com/32855IYBdhLWcg3w1oHa#41381757 - B.B.
21个回答

204

更新:

我在五年前写下这篇回答时,没有意识到它会被引用这么多次!

我想澄清一下,根据 @tato-rodrigo 的回答,在某些情况下,这并不能帮助您检测已处理的意图。

另外,我应该指出,我用引号括起来的“clear”是有原因的 - 你并不是真正地清除了这个意图,而是利用删除额外信息作为标志,表明这个意图已经被活动看到过。


我也遇到了完全相同的问题。

上面的答案让我找到了正确的方向,我发现甚至还有更简单的解决方法,使用:

getIntent().removeExtra("key"); 

调用 "clear" 方法清除 Intent。

虽然这个问题是一年前提出的,但希望它能帮助到未来的其他人。


4
removeExtra() 方法不是接受一个字符串参数吗?例如:getIntent().removeExtra("String"); - tony9099
32
我可能错了,但我认为在以下情况下这不起作用:1)通过通知打开活动;2)通过按返回按钮完成活动;3)通过历史记录(最近使用的应用程序)重新打开活动。另一种情况是当系统因资源不足而杀死应用程序时(在开发人员选项下启用“不保留活动”,然后仅按主屏幕键,再从历史记录中重新打开活动)。我在下面发布了我正在使用的解决方案。如果您能对此发表评论,那就太好了。 - tato.rodrigo
3
很不幸,它对我们不起作用。我们发现启动一个新的活动,该活动又启动初始活动会导致OnNewIntent再次使用相同的意图触发。 - Le-roy Staines
2
不要清除Intent,而是确定启动类型并相应地处理应用程序流程。仅在从通知启动而不是后台启动时获取Extras。 https://dev59.com/32855IYBdhLWcg3w1oHa#41381757 - B.B.
2
对我没用。我遇到了与@tato.rodrigo提到的相同问题,即如果通过通知或从历史记录或其他原因打开活动,则意图不会被清除,因此在使用意图信息后,我重置了意图,像这样setIntent(new Intent()),现在它可以正常工作了。 - Shubhral
显示剩余5条评论

55

编辑:我编辑帖子以发布我正在使用的完整解决方案。

如果问题是“当活动从历史记录(最近使用的应用程序)启动时不执行某些代码”,则此解决方案可行。

首先,在您的Activity中声明一个boolean,以指示Intent是否已被使用:

    private boolean consumedIntent;

然后,使用onSaveInstanceStateonCreate方法安全地存储和恢复此值,以处理配置更改以及系统在其转入后台时可能会关闭您的Activity的情况。

    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
    }

现在,请检查您是否可以在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
        }
    }

此外,如果您的Activity被配置为singleTop,在传递新的Intent时应重置您的标志。

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

16
非常感谢!这段代码 (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) 帮助了我,现在我可以知道 Activity 是否从历史记录中启动,从而忽略我的额外信息。 - Roman Nazarevych
1
@Lemberg 我也遇到了同样的问题,我解决方法和你一样。如果你使用了一些来自推送通知的额外信息,那么有可能会从历史记录中启动你的活动,并且总是消耗你的额外信息并重定向到与你的推送通知相同的操作。标志 launchedFromHistory 可以帮助你了解这一点。 - Stoycho Andreev
即使活动已被销毁并且我们再次从历史堆栈中重新打开它,它是否仍然能够正常工作? - user25
太好了!似乎即使应用程序被销毁,它也能正常工作...但请@tato.rodrigo从答案中删除 boolean shouldThisIntentTriggerMyCode = [...];(这是用于什么的?) - user25
在我这种情况下,对于某些用户的多个通知,最好使用consumedIntent作为包含通知Uid的String。这个Uid可以简单地在后端添加到通知中作为当前时间戳。此外,如果Intent是从onCreate传来的,你应该只在onSaveInstanceState中保存这个Uid。这意味着你不应该保存来自onNewIntent的Uid。 - Konstantin Konopko
这个回答应该能得到一百万个赞。谢谢! - AndrzejO

34
当我们从历史记录(最近使用的应用程序)中启动Android应用程序时,该应用程序可以使用主要三个不同的Intent标志来启动。
  1. FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
    这是当从被最小化的应用程序的历史记录中启动活动时。
    常量值:1048576 (0x00100000)
  2. FLAG_ACTIVITY_NEW_TASK
    这是当通过“点击应用程序图标”或通过“意向过滤器”启动活动时。在这里,活动将成为此历史堆栈上的新任务的开始。
    常量值:268435456 (0x10000000)
  3. FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY | FLAG_ACTIVITY_NEW_TASK
    这是当通过按下返回按钮退出应用程序,然后从历史记录(最近使用的应用程序)中恢复时。
    常量值:269484032 (0x10100000)
常量值可通过getIntent().getFlags()检索。
在第三种情况下,Android 会从其内存中重新加载上一个 Intent 的值。因此,您的应用程序的 Intent (getIntent) 将具有来自启动应用程序的上一个 Intent 的值。
实际上,该应用程序应该表现得像是全新启动,具有全新启动的意图值,而不是上一次启动的意图值。如果通过点击应用程序图标启动应用程序,则可以看到此行为,它永远不会有旧的意图值。这是因为,Android 对于此场景使用以下意图过滤器。
 <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER"/>
 </intent-filter>

但在第三种情况下(从最近应用程序历史记录中启动已退出的应用程序),Android操作系统使用退出应用程序之前(通过按返回按钮)启动应用程序的最后一个意图。因此,您最终会得到旧的意图值,应用程序流程不正确。
删除意图是解决问题的一种方法,但这并不能完全解决问题!由于Android操作系统重新加载应用程序上次启动的意图,而不是上次启动意图的最后一个实例。
避免发生这种情况的一种清晰的方法是通过获取Intent类型来确定启动类型进行处理。
因此,在您的LaunchActivity(在清单中定义了意图过滤器的Activity)中,您可以在 onCreate() onStart() 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();
        }
    }

我认为normalLaunch()不应该使用来自Intent的参数;否则,您需要将默认启动方法分离和优化,以不使用Intent参数。

1
不是所有的英雄都戴着帽子! - Sdghasemi
你需要与其他标志一起使用它,可能是FLAG_ACTIVITY_NEW_TASK。 - B.B.
2
当活动因开发设置“不保留活动”而在后台被杀死时,这将无法工作。在这种情况下,getIntent()getFlags()与第一次启动活动时相同。 - Malachiasz
感谢解释,但它并没有按预期运行。 - Azlan Jamal
对我来说,从最近列表中点击应用程序总是返回268435456。 - Bink
显示剩余2条评论

32

清除一个意图 对象

intent.replaceExtras(new Bundle());
intent.setAction("");
intent.setData(null);
intent.setFlags(0);

3
这应该是被接受的答案。非常有效! - Martin Erlic
5
当开发者选项中勾选了“不保留活动”时,此功能无法使用。 - Jemshit Iskenderov
仅仅移除额外的内容对我来说并没有起作用,它可以在片段位于前台时清除意图,但是尝试导航回到它时仍然会保留额外的内容。只需使用新的Bundle替换额外的内容(intent.replaceExtras(new Bundle());)就足以清除任何已设置为额外内容的东西。 - Victor Oliveira

26

Maks的回答适用于清除额外的内容:

    getIntent().removeExtra("key"); 

另一个有用的命令是:

    getIntent().setAction("");

您还可以通过调用以下方式标记意图:

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

然后只需检查该值即可。


10

简短回答是没门

长话短说,不存在所谓的“一次性”意图。从实验中可以看出,现代安卓手机的最近活动历史不过是“意图历史”。最新的意图传递给活动只是被记录在系统中而已。上面的人建议使用

setAction("")
但是这样做行不通,因为在onNewIntent()或onStart()方法内获取到的intent已经被记录了。我通过避免使用intent解决了这个问题,我的问题与作者发布的那个类似。我试图通过通知区域中的控件实现应用程序的全局退出。它应该停止底层服务并关闭应用程序的所有活动。您可以在Waze应用程序中找到相同的行为。
算法:
  1. 创建一个PendingIntent,将“Exit”操作传递给一个简单的代理activity。
  2. Proxy activity的onStart()代码解析intent,检查操作并将某些模型的状态设置为“Exited”。
  3. Proxy activity的onStart()代码使用setIntent("")清除原始intent,然后通过调用startActivity(intent)将其转发到目标“Root”activity。
  4. Proxy activity的onStart()代码调用finish()。
  5. 在目标activity的onStart()和onNewIntent()中检查模型状态,如果它为“Exited”,则调用finish()(在我的情况下还会调用stopService())。
希望这对某些人有所帮助,因为我在互联网上没有找到答案。

我认为这是最准确的答案,因为“清除意图”并不一定意味着“删除某些额外内容”。我也认为没有简单的方法可以做到这一点。 - mdelolmo

6

请确保在使用PendingIntent时,使用的是PendingIntent.FLAG_UPDATE_CURRENT标志。

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

其中mPutIntent是您的Intent

希望这能帮到您。


1
拯救了我的生命!! - Eren
2
我不明白为什么这不是被接受的答案。我唯一的遗憾就是只有一个赞。干杯。 - Andy
这个答案对我有用。 - undefined

2

我最近遇到了这个问题,并通过向意图添加时间戳作为额外参数来解决它:

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

接下来,将时间戳保存到共享偏好中:

@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();
        }
    }
}

1
我找不到删除Intent Extra的方法。如果您从开发者选项中启用“不保留活动”(这样您就可以销毁活动并返回测试Extra是否仍然存在),则与“从意图中删除额外信息”有关的任何答案都无效。
作为解决该问题的方法,我在处理Intent Extras后将布尔值存储在SharedPreferences中。当相同的Intent重新传递给Activity时,我检查SharedPreferences值并决定是否处理Intent Extra。如果您向同一Activity发送另一个新的Intent Extra,则将SharedPreferences值设置为false,Activity将处理它。 例如:
// 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();
         }
    }

}

偏好设置没有意义,因为您不知道是使用 startActivity 还是从历史堆栈中重新打开了活动... - user25
@user25,我认为有一种方法可以检测它是否是从最近使用的应用程序中启动的。但是,这是否重要,意图额外信息无论是否被消耗,如果被消耗了,您可以从共享首选项中知道它。我已经用它来消耗推送通知额外信息,对于我的情况,活动如何打开并不重要。 - Jemshit Iskenderov

1
当你完成处理意图时,执行以下操作:

setIntent(null);

你不会再看到那个处理过的Intent,也不会通过编辑处理过的Intent内容来掩盖问题。

这个不起作用 - Astha Garg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接