128

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

例如,如果我的应用程序包含活动 ABC,那么这个问题可能会导致一堆 ABCA。

我尝试android:launchMode="singleTask"在所有活动上使用来解决此问题,但是每当我点击 HOME 按钮时,它都会产生将 Activity 堆栈清除为 root 的不良副作用。

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

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

4

11 回答 11

197

将此添加到 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;       
    }
}
于 2011-10-13T01:23:51.733 回答
29

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

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

  2. 通过启动器(主页)启动时,它使用标志:FLAG_ACTIVITY_NEW_TASK | 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() 解决方法,这将重现问题。我们在我们的自动测试中使用它来确保这个错误不再发生。

希望这可以帮助!

于 2014-04-05T15:10:58.053 回答
10

您是否尝试过singleTop启动模式?

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

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

于 2010-12-03T01:15:49.640 回答
4

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

于 2011-09-28T19:31:05.583 回答
4

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

为了在 Xamarin Android 中解决这个问题,我使用了来自 @DuaneHomick 的代码并将其添加到MainActivity.OnCreate(). Xamarin.Forms.Forms.Init(this, bundle);与 Xamarin 的不同之处在于必须使用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,这似乎使事情再次正常工作。不幸的是,不确定这可能会对其他事情产生什么影响。

于 2016-02-15T20:43:57.463 回答
2

我认为接受的答案(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);
于 2015-07-22T09:05:53.383 回答
0

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

在您的主要活动中,将此代码添加到方法的顶部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" />

希望它可以帮助你。

于 2011-09-05T14:33:28.770 回答
0

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

public static boolean IS_APP_RUNNING = false;

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

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

PSController是我的Application课。

于 2015-02-17T10:45:10.187 回答
0

我也有这个问题

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

尝试使用 将关联设置为allowtaskreparenting的SingleInstance启动模式 这将始终在新任务中创建活动,但也允许其重新设置。检查 dis : Affinity 属性

于 2010-12-03T03:41:20.403 回答
-2

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

if ( !this.getClass().getSimpleName().equals("YourActivityClassName")) {
    start your activity
}
于 2015-12-29T13:39:00.060 回答