首页 文章

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

提问于
浏览
113

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

For example, 如果我的应用程序包含活动A-B-C,则上述问题可能导致堆栈A-B-C-A .

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

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

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

11 回答

  • 2

    将此添加到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;       
        }
    }
    
  • 174

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

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

    • 通过启动器(home)启动时,它使用标志:FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,并使用操作“MAIN " and category " 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()解决方法,则会重现该问题 . 我们在自动测试中使用它来确保此错误不再发生 .

    希望这可以帮助!

  • 0

    你试过 singleTop 启动模式吗?

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

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

  • -1

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

  • 23

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

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

    从市场或主屏幕图标(由市场自动放置)启动应用程序时

    • 当您通过启动器启动应用程序或手动创建主屏幕图标时

    这是一个解决方案(SDK_INT> = 11用于通知),我相信这些情况和状态栏通知也是如此 .

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

    我意识到这个问题与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

    我也有这个问题

    • 不要调用finish();在家庭活动中它将无休止地运行 - ActivityManager在完成时调用home活动 .

    • 通常当配置发生变化时(即旋转屏幕,更改语言,电话服务更改,即mcc mnc等),活动将重新创建 - 如果主动活动正在运行,则它再次调用A.以满足需要添加到清单 android:configChanges="mcc|mnc" - 如果您已连接到蜂窝电话,请参阅http://developer.android.com/guide/topics/manifest/activity-element.html#config,了解启动系统时的配置或按下打开或随你 .

  • 2

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

    public static boolean IS_APP_RUNNING = false;
    

    然后在 onCreate 之前的第一个(Launcher)活动中 setContentView(...) 添加:

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

    附: Controller 是我的 Application 班 .

  • -2

    我有同样的问题,我使用以下解决方案修复它 .

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

    希望它能帮助你 .

  • 4

    我发现了一种防止开始相同活动的方法,这对我来说很有用

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

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

相关问题