当使用不同的 Intent 启动 Activity 时,如何防止 Activity 的多个实例

发布于 2024-10-05 08:32:31 字数 537 浏览 4 评论 0原文

当我使用 Google Play 商店应用(以前称为 Android Market)上的“打开”按钮启动应用程序时,我遇到了一个错误。从 Play 商店启动它与从手机的应用程序图标菜单启动它似乎使用了不同的 Intent 。这会导致启动同一 Activity 的多个副本,这些副本彼此冲突。

例如,如果我的应用程序由 Activity ABC 组成,则此问题可能会导致 ABCA 堆栈。

我尝试在所有 Activity 上使用 android:launchMode="singleTask" 来解决此问题,但每当我按下 HOME 按钮时,它都会产生将 Activity 堆栈清除到根目录的不良副作用。

预期行为是: ABC ->首页->当应用程序恢复时,我需要: ABC ->首页-> ABC

有没有一种好方法可以防止启动多个相同类型的 Activity,而无需在使用 HOME 按钮时重置为根 Activity?

I've come across a bug in my application when it is launched using the "Open" button on the Google Play Store app (previously called Android Market). It seems that launching it from the Play Store uses a different Intent than launching it from the phone's application menu of icons. This is leading to multiple copies of the same Activity being launched, which are conflicting with each other.

For example, if my app consists of the Activities A-B-C, then this issue can lead to a stack of A-B-C-A.

I tried using android:launchMode="singleTask" on all the Activities to fix this problem, but it has the unwanted side-effect of clearing the Activity stack to root, whenever I hit the HOME button.

The expected behavior is: A-B-C -> HOME -> And when the app is restored, I need: A-B-C -> HOME -> A-B-C

Is there a good way to prevent launching multiple Activities of the same type, without resetting to the root activity when using the HOME button?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

许你一世情深 2024-10-12 08:32:31

将其添加到 onCreate 中,您应该可以开始了:

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

Add this to onCreate and you should be good to go:

// 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;       
    }
}
明媚如初 2024-10-12 08:32:31

我只是要解释它失败的原因,以及如何以编程方式重现此错误,以便您可以将其合并到您的测试套件中:

  1. 当您通过 Eclipse 或 Market App 启动应用程序时,它会使用意图标志启动:FLAG_ACTIVITY_NEW_TASK。

  2. 通过启动器(主页)启动时,它使用标志:FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_BRUGHT_TO_FRONT | FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,并使用操作“MAIN”和类别“LAUNCHER”。

如果您想在测试用例中重现此情况,请使用以下步骤:

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

然后执行执行其他活动所需的操作。出于我的目的,我只是放置了一个启动另一个活动的按钮。然后,返回到启动器(主页):

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

并通过启动器模拟启动它:

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

如果您尚未合并 isTaskRoot() 解决方法,这将重现该问题。我们在自动测试中使用它来确保此错误不再发生。

希望这有帮助!

I'm just going to explain why it fails, and how to reproduce this bug programmatically so you can incorporate this in your test suite:

  1. When you launch an app through Eclipse or Market App, it launches with intent flags: FLAG_ACTIVITY_NEW_TASK.

  2. When launching through the launcher (home), it uses flags: FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED, and uses action "MAIN" and category "LAUNCHER".

If you would like to reproduce this in a test case, use these steps:

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

Then do whatever is needed to get to the other activity. For my purposes, I just placed a button that starts another activity. Then, go back to the launcher (home) with:

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

And simulate launching it via the launcher with this:

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

If you haven't incorporated the isTaskRoot() workaround, this will reproduce the problem. We use this in our automatic testing to make sure this bug never occurs again.

Hope this helps!

静水深流 2024-10-12 08:32:31

您尝试过singleTop启动模式吗?

以下是 http://developer.android.com/ 的一些描述指南/topics/manifest/activity-element.html

...“singleTop”的新实例
还可以创建活动来处理
一个新的意图。然而,如果目标
任务已经有一个现有实例
其顶部的活动
堆栈,该实例将收到
新意图(在 onNewIntent() 调用中);
不会创建新实例。在
其他情况——例如,如果
的现有实例
“singleTop”活动位于目标中
任务,但不在堆栈顶部,
或者如果它位于堆栈的顶部,但是
不在目标任务中——一个新的
实例将被创建并推送
在堆栈上。

Have you tried the singleTop launch mode?

Here is some of the description from http://developer.android.com/guide/topics/manifest/activity-element.html:

... a new instance of a "singleTop"
activity may also be created to handle
a new intent. However, if the target
task already has an existing instance
of the activity at the top of its
stack, that instance will receive the
new intent (in an onNewIntent() call);
a new instance is not created. In
other circumstances — for example, if
an existing instance of the
"singleTop" activity is in the target
task, but not at the top of the stack,
or if it's at the top of a stack, but
not in the target task — a new
instance would be created and pushed
on the stack.

美胚控场 2024-10-12 08:32:31

也许是这个问题?或者同一错误的其他形式?

Perhaps it is this issue? Or some other form of the same bug?

歌入人心 2024-10-12 08:32:31

我意识到这个问题与 Xamarin Android 没有任何关系,但我想发布一些内容,因为我在其他地方没有看到它。

为了在 Xamarin Android 中修复此问题,我使用了 @DuaneHomick 中的代码并将其添加到 MainActivity.OnCreate() 中。与 Xamarin 的区别在于它必须位于 Xamarin.Forms.Forms.Init(this, bundle);LoadApplication(new App()); 之后。所以我的 OnCreate() 看起来像:

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

*编辑:从 Android 6.0 开始,上述解决方案对于某些情况来说是不够的。我现在还将 LaunchMode 设置为 SingleTask,这似乎使事情再次正常工作。不幸的是,不确定这可能会对其他事情产生什么影响。

I realize that the question does not have anything to do with Xamarin Android but I wanted to post something since I did not see it anywhere else.

To fix this in Xamarin Android I used the code from @DuaneHomick and added into MainActivity.OnCreate(). The difference with Xamarin is that is must go after Xamarin.Forms.Forms.Init(this, bundle); and LoadApplication(new App());. So my OnCreate() would look like:

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

*Edit: Since Android 6.0, the above solution is not enough for certain situations. I have now also set LaunchMode to SingleTask, which seems to have made things work correctly once again. Not sure what effects this might have on other things though unfortunately.

一萌ing 2024-10-12 08:32:31

我认为接受的答案(Duane Homick)有未处理的情况:

您有不同的额外内容(因此应用程序重复):

  • 当您从市场或主屏幕图标(由市场自动放置)启动应用程序时
  • 当您通过启动器或手动创建的主屏幕图标启动应用程序时

这是一个解决方案(SDK_INT>=11 用于通知),我相信它可以处理这些情况并且状态栏通知也。

清单

    <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" />

启动器活动

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

服务

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

通知

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

I think the accepted answer (Duane Homick) has unhandled cases:

You have different extras (and app duplicates as a result):

  • when you launch application from Market or by home screen icon (which is placed by Market automatically)
  • when you launch application by launcher or manually created home screen icon

Here is a solution (SDK_INT>=11 for notifications) which i belive handle these cases and statusbar notifications also.

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 activity:

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

Notification:

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);
最美的太阳 2024-10-12 08:32:31

我遇到了同样的问题,并使用以下解决方案修复了它。

在您的主要活动中,将此代码添加到 onCreate 方法的顶部:

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

不要忘记在清单中添加此权限。

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

希望对你有帮助。

I had the same problem, and I fixed it using the following solution.

In your main activity add this code on the top of the onCreate method:

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

don't forget to add this permission in your manifest.

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

hope it helps you.

乖乖兔^ω^ 2024-10-12 08:32:31

我也遇到了这个问题

  1. 不要调用 finish();在家庭活动中,它将无休止地运行 - 家庭活动在完成时由 ActivityManager 调用。
  2. 通常,当配置发生更改(即旋转屏幕、更改语言、电话服务更改即 mcc mnc 等)时,活动会重新创建 - 如果主活动正在运行,则它会再次调用 A.,因为需要添加到清单 android:configChanges="mcc|mnc" - 如果您已连接到蜂窝网络,请参阅 http://developer.android.com/guide/topics/manifest/activity-element.html#config 用于启动系统或推开或其他任何操作时的配置。

I had this problem also

  1. Don't call finish(); in the home activity it would run endlessly - home activity is being called by ActivityManager when it finished.
  2. Usually when the configuration is changing (i.e. rotate screen, change language, telephony service changes i.e. mcc mnc etc.) the activity recreate - and if the home activity is running then it calls again to A. for that need to add to manifest android:configChanges="mcc|mnc" - if you have connection to cellular, see http://developer.android.com/guide/topics/manifest/activity-element.html#config for which configuration there is when booting the system or push open or whatever.
ゞ记忆︶ㄣ 2024-10-12 08:32:31

尝试这个解决方案:
创建 Application 类并在那里定义:

public static boolean IS_APP_RUNNING = false;

然后在 onCreate 中的第一个(启动器)Activity 中,在 setContentView(...) 之前添加以下内容:

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

PS < code>Controller 是我的 Application 类。

Try this solution:
Create Application class and define there:

public static boolean IS_APP_RUNNING = false;

Then in your first (Launcher) Activity in onCreate before setContentView(...) add this:

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

P.S. Controlleris my Application class.

冷月断魂刀 2024-10-12 08:32:31

尝试使用 SingleInstance 启动模式,并将亲和力设置为 allowtaskreparenting
这将始终在新任务中创建活动,但也允许其重新设置父级。
检查dis:亲和力属性

try using SingleInstance launch mode with affinity set to allowtaskreparenting
This will always create the activity in new task but also allow its reparenting.
Check dis :Affinity attribute

素食主义者 2024-10-12 08:32:31

我找到了一种方法来防止开始相同的活动,这对我来说非常有用

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

I found a way to prevent starting same activities, this works great for me

if ( !this.getClass().getSimpleName().equals("YourActivityClassName")) {
    start your activity
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文