当一个活动以不同的意图启动时,如何防止该活动的多个实例

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

例如, 如果我的应用程序包含活动 A-B-C,那么这个问题可能导致堆栈的 A-B-C-A。

我尝试在所有的活动中使用 android:launchMode="singleTask"来修复这个问题,但是它有一个不想要的副作用,那就是每当我点击 HOME 按钮时,都会清除活动堆栈到根目录。

预期的行为是: A-B-C-> HOME-> 当应用程序恢复时,我需要: A-B-C-> HOME-> A-B-C

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

78950 次浏览

你试过 单人秀发射模式吗?

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

... 一个新的例子,一个“单顶” 也可以创建活动来处理 一个新的意图。然而,如果目标 任务已经有一个现有的实例 活动的顶部 栈中,该实例将接收 新意图(在 onNewInent ()调用中) ; 未创建新实例 其他情况ー例如: 的现有实例 “ singleTop”活动在目标中 任务,但不在堆栈的顶部, 或者是在堆栈的顶部,但是 不在目标任务中ーー一个新的 实例将被创建并推送 在堆栈上。

尝试使用 单实例启动模式,将亲和力设置为 < strong > allowtaskreParenting 这将总是在新的任务中创建活动,但也允许其再生。 检查 dis: < a href = “ http://developer.android.com/guide/subject/Manif/activity-element.html # rerent”rel = “ nofollow”> Affinity 属性

我也有同样的问题,我用下面的方法解决了它。

在主活动中,在 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" />

希望能帮到你。

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

把它添加到 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;
}
}

我也有这个问题

  1. 不要调用 Finish () ; 在 home 活动中,它会无休止地运行—— home 活动在结束时会被 ActivityManager 调用。
  2. 通常情况下,当配置发生变化(比如旋转屏幕、改变语言、电话服务改变,比如 mcc mnc 等等)时,活动会重新创建——如果主活动正在运行,它会再次调用 A,因为需要添加显示 android:configChanges="mcc|mnc"——如果你连接到蜂窝,请参阅 http://developer.android.com/guide/topics/manifest/activity-element.html#config,在启动系统或者推开或者其他什么时候有配置。

我将解释为什么它会失败,以及如何通过编程方式重现这个 bug,这样你就可以把它合并到你的测试套件中:

  1. 当您通过 Eclipse 或 Market App 启动一个应用程序时,它会带有意向标志启动: FLAG _ ACTIVITY _ NEW _ TASK。

  2. 当通过启动器(主页)启动时,它使用标志: FLAG _ ACTIVITY _ NEW _ TASK | FLAG _ ACTIVITY _ BROUGHT _ TO _ FRONT | FLAG _ ACTIVITY _ RESET _ TASK _ IF _ NEEDED,并使用动作“ 总部”和类别“ 发射器”。

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

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 ()解决方案,这将重现问题。我们在自动测试中使用它来确保这个错误不会再次发生。

希望这个能帮上忙!

试试这个办法:
创建 Application类并在其中定义:

public static boolean IS_APP_RUNNING = false;

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

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

附注: Controller是我的 Application课程。

我认为公认的答案(Duane Homick)有未处理的案例:

你有不同的额外功能(结果是应用程序的副本) :

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

下面是一个解决方案(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);

我找到了一种方法来防止开始同样的活动,这对我很有效

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

我意识到这个问题与 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以来,上述解决方案在某些情况下是不够的。我现在还设置了 LaunchModeSingleTask,这似乎使事情再次正常工作。不过不幸的是,我不确定这会对其他事情产生什么影响。