如何使用不同的意图启动活动时,如何防止活动的多个实例

时间:2010-12-03 01:07:01

标签: android back-stack activity-stack

我在使用Android市场上的“Open”按钮启动应用程序时遇到了一个错误。似乎从市场上推出它会使用不同的意图,然后从手机的应用程序菜单中启动它。这导致启动相同活动的多个副本,这些副本相互冲突。

例如,如果我的应用包含活动A-B-C,则上述问题可能导致堆栈A-B-C-A。

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

示例: A-B-C - >首页 - > A当我需要的是A-B-C - >首页 - > A-B-C

使用HOME时,是否有一种防止启动相同类型的多个活动而不重置root活动的好方法?

11 个答案:

答案 0 :(得分:174)

将此添加到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 :(得分:23)

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

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

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

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

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

    希望这有帮助!

答案 2 :(得分:6)

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

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

  

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

答案 3 :(得分:4)

也许是this issue?或者同一个bug的其他形式?

答案 4 :(得分:2)

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

你有不同的额外内容(结果是app重复):

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

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

答案 5 :(得分:2)

我意识到这个问题与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,这似乎使事情再次正常运行。不幸的是,不确定这会对其他事情产生什么影响。

答案 6 :(得分:0)

我也有这个问题

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

答案 7 :(得分: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();

P.S。 Controller是我的Application课程。

答案 8 :(得分:-1)

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

在您的主要活动中,将此代码添加到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" />
希望它可以帮到你。

答案 9 :(得分:-2)

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

答案 10 :(得分:-2)

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

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