Как предотвратить несколько экземпляров действия, когда оно запускается с разными намерениями

Я столкнулся с ошибкой в моем приложении, когда оно запускалось с помощью кнопки "Открыть" в приложении Google Play Store (ранее называлось Android Market). Кажется, что запуск его из Play Store использует другое Intent чем запуск его из меню значков приложения телефона. Это приводит к тому, что запускается несколько копий одного и того же действия, которые конфликтуют друг с другом.

Например, если мое приложение состоит из ABC Activity, то эта проблема может привести к стеку ABCA.

Я попытался использовать android:launchMode="singleTask" на всех Деятельностях, чтобы исправить эту проблему, но у него был нежелательный побочный эффект очистки стека Деятельности от корня всякий раз, когда я нажимаю кнопку HOME.

Ожидаемое поведение: ABC → HOME → И когда приложение восстанавливается, мне нужно: ABC → HOME → ABC

Есть ли хороший способ предотвратить запуск нескольких операций одного и того же типа без сброса корневого действия при использовании кнопки HOME?

Ответ 1

Добавьте это в 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;       
    }
}

Ответ 2

Я просто объясню, почему это не удается, и как воспроизвести эту ошибку программно, чтобы вы могли включить ее в свой тестовый пакет:

  • Когда вы запускаете приложение через приложение Eclipse или Market, оно запускается с флагами намерений: FLAG_ACTIVITY_NEW_TASK.

  • При запуске через пусковую установку (home) используются флаги: 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 

Затем сделайте все, что необходимо для перехода к другому действию. Для моих целей я просто поместил кнопку, которая запускает другое действие. Затем вернитесь в пусковую установку (home) с помощью:

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(), это приведет к воспроизведению проблемы. Мы используем это в нашем автоматическом тестировании, чтобы убедиться, что эта ошибка больше не повторяется.

Надеюсь, это поможет!

Ответ 3

Вы пробовали режим запуска singleTop?

Ниже приведено описание из http://developer.android.com/guide/topics/manifest/activity-element.html:

... новый экземпляр "singleTop" активность также может быть создана для обработки новое намерение. Однако, если цель задача уже имеет существующий экземпляр деятельности в верхней части ее стек, этот экземпляр получит новое намерение (в вызове onNewIntent()); новый экземпляр не создается. В другие обстоятельства - например, если существующий экземпляр Активность "singleTop" находится в целевой но не в верхней части стека, или если он находится в верхней части стека, но а не в целевой задаче - новый экземпляр будет создан и нажат в стеке.

Ответ 4

Возможно, это эта проблема? Или какая-то другая форма такой же ошибки?

Ответ 5

Я думаю, что принятый ответ (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);

Ответ 6

Я понимаю, что вопрос не имеет ничего общего с 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, что, похоже, снова заработало. Не уверен, какие последствия это может иметь для других вещей, но, к сожалению.

Ответ 7

У меня была и эта проблема.

  • Не называть finish(); в домашней деятельности он будет работать бесконечно - домашняя активность вызывается ActivityManager по завершении.
  • Обычно, когда конфигурация изменяется (например, поворот экрана, изменение языка, изменения службы телефонии, т.е. mcc mnc и т.д.), активность воссоздается - и если домашняя активность запущена, то она снова обращается к A., для чего необходимо добавить manifest android:configChanges="mcc|mnc" - если у вас есть подключение к сотовой сети, см. http://developer.android.com/guide/topics/manifest/activity-element.html#config, для какой конфигурации есть при загрузке системы или нажатии кнопки "открыто" или что-то еще.

Ответ 8

Попробуйте это решение:
Создайте класс Application и определите там:

public static boolean IS_APP_RUNNING = false;

Затем в вашей первой (Launcher) Activity в onCreate до setContentView(...) добавьте это:

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

P.S. Controller - мой класс Application.

Ответ 9

У меня была та же проблема, и я исправил ее, используя следующее решение.

В вашей основной деятельности добавьте этот код в верхней части метода 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" />

надеюсь, что это поможет вам.

Ответ 10

попробуйте использовать режим запуска SingleInstance с аффинностью, установленной на allowtaskreparenting Это всегда будет создавать активность в новой задаче, но также позволит ее повторить. Проверьте: атрибут близости

Ответ 11

Я нашел способ предотвратить запуск таких же действий, это отлично работает для меня

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