Як запобігти декільком екземплярам діяльності, коли вона запускається з різними інтентами


121

У моїй програмі я натрапив на помилку, коли він запускається за допомогою кнопки "Відкрити" в додатку Google Play Store (раніше називався Android Market). Здається, що для запуску його з Play Store використовується інше, Intentніж запуск з меню програм піктограм телефону. Це призводить до запуску декількох копій однієї й тієї самої діяльності, які суперечать один одному.

Наприклад, якщо моє додаток складається з Активності ABC, то ця проблема може призвести до скупчення ABCA.

Я намагався використовувати android:launchMode="singleTask"всі дії, щоб виправити цю проблему, але вона має небажаний побічний ефект, як очистити стек "Активність", коли він натискає кнопку ДОМАШНЯ.

Очікувана поведінка: ABC -> HOME -> І коли програма відновиться, мені потрібно: ABC -> HOME -> ABC

Чи є хороший спосіб запобігти запуску декількох заходів одного типу, не скидаючись на кореневу діяльність при використанні кнопки HOME?


Відповіді:


187

Додайте це до 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;       
    }
}

25
Я роками намагався вирішити цю помилку, і це рішення, яке спрацювало, тож дуже дякую! Мені потрібно також зазначити, що це не лише проблема в Android Market, але й проблема завантаження програми завантаженням на сервер або надсиланням електронною поштою на ваш телефон. Усі ці речі встановлюють додаток за допомогою програми встановлення пакунків, де я вважаю, що помилка перебуває. Крім того, на випадок, коли це не зрозуміло, вам потрібно лише додати цей код до методу onCreate про те, яка ваша коренева діяльність.
ubzack

2
Мені здається дуже дивним, що це відбувається в підписаному додатку, розгорнутому на пристрої, але не у версії налагодження, розгорнутій від Eclipse. Це робить досить важким налагодження!
Метт Конноллі

6
Це трапляється з налагодженою версією, розгорнутою з Eclipse до тих пір, як Ви її запускаєте також через Eclipse (або IntelliJ або інший IDE). Це не має нічого спільного з тим, як програма встановлюється на пристрої. Проблема пов’язана із способом запуску програми .
Девід Wasser

2
Хтось знає, чи забезпечить цей код наявний екземпляр програми на перший план? Або це просто виклик закінчення (); і не залишати користувача без візуальних ознак, що щось сталося?
Карлос П

5
@CarlosP, якщо діяльність, яка створюється, не є кореневою активністю завдання, повинна (за визначенням) бути принаймні ще однією діяльністю під нею. Якщо ця діяльність зателефонує, finish()користувач побачить активність, яка була під нею. Через це можна сміливо припускати, що наявний екземпляр програми буде виведений на перший план. Якби це не так, у вас було б кілька примірників програми в окремих завданнях, а створена діяльність була б коренем її завдання.
Девід Вассер

27

Я просто поясню, чому вона не працює, і як програму відтворити цю помилку, щоб ви могли включити її у свій тестовий набір:

  1. Коли ви запускаєте додаток через Eclipse або Market App, він запускається з прапорами намірів: FLAG_ACTIVITY_NEW_TASK.

  2. Під час запуску через пусковий апарат (домашній) він використовує прапори: FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED, і використовує дію " ГОЛОВНЕ " та категорію " 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 (), це відтворить проблему. Ми використовуємо це в автоматичному тестуванні, щоб переконатися, що ця помилка більше не виникає.

Сподіваюся, це допомагає!


8

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

Ось деякі описи з http://developer.android.com/guide/topics/manifest/activity-element.html :

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


2
Я думав про це, але що робити, якщо діяльність не знаходиться у верхній частині стека? Наприклад, схоже, що SingleTop заважатиме AA, але не ABA.
bsberkeley

Чи можете ви досягти того, що ви хочете, використовуючи методи SingleTop та фінішу в рамках діяльності?
Ерік Левін

Я не знаю, чи вдасться виконати те, що я хочу. Приклад: Якщо після активації A і B я перебуваю на діяльності C, тоді починається нова активність A, і у мене з’явиться щось на зразок CA, чи не так?
bsberkeley

Важко відповісти на це, не розуміючи більше, чим займаються ці види діяльності. Чи можете ви надати більш детальну інформацію про вашу заявку та діяльність? Цікаво, чи є невідповідність між тим, що робить кнопка «Головна», і тим, як ви хочете, щоб вона діяла. Кнопка "Домашня сторінка" не виходить із "Діяльності", вона "перетворює" її, щоб користувач міг перейти на щось інше. Кнопка "Назад" - це те, що закриває / завершує та активує. Порушення цієї парадигми може збентежити / розчарувати користувачів.
Ерік Левін

Я додав ще одну відповідь до цього потоку, щоб ви могли побачити копію маніфесту.
bsberkeley

4

Можливо, саме це питання ? Або якась інша форма тієї самої помилки?


Дивіться також code.google.com/p/android/isissue/detail?id=26658 , де показано, що його спричиняють інші речі, ніж Eclipse.
Крістофер Джонсон

1
Тож я мушу скопіювати та вставити опис проблеми, який може стати несвіжим? Які частини? Чи слід зберігати суттєві частини, якщо посилання змінюється, і чи є моя відповідальність за те, щоб відповідь оновлювалася? Слід подумати, що посилання стає недійсним лише тоді, коли проблема вирішена. Зрештою, це не посилання на блог.
DuneCat

2

Я вважаю, що прийнята відповідь ( Дуейн Хомік ) має бездоганні випадки:

У вас є додаткові додатки (і копії програми як результат):

  • коли ви запускаєте програму з 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);

2

Я розумію, що питання не має нічого спільного з Xamarin Android, але я хотів щось опублікувати, оскільки не бачив його більше ніде.

Щоб виправити це в Android Xamarin, я використав код від @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, що, здається, знову налагодив роботу. Не впевнений, який вплив це може мати на інші речі, хоча, на жаль.


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" />

сподіваюся, що тобі це допоможе.


0

У мене була і ця проблема

  1. Не дзвоніть до кінця (); в домашній діяльності воно буде працювати нескінченно - активність вдома викликає ActivityManager, коли вона закінчиться.
  2. Зазвичай, коли конфігурація змінюється (тобто екран обертання, зміна мови, зміна послуги телефонії, тобто mcc mnc тощо), активність відтворюється - і якщо домашня активність запущена, вона знову викликає А. для цього потрібно додати до маніфесту android:configChanges="mcc|mnc"- якщо Ви маєте підключення до стільникового зв'язку, див. http://developer.android.com/guide/topics/manifest/activity-element.html#config, для якого є конфігурація під час завантаження системи або відкриття чи будь-що інше.

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();

PS Controller- мій Applicationклас.


Ви повинні використовувати примітивні булеві, що робить перевірку на нульову непотрібність.
WonderCsabo

Це не завжди спрацює. Ви ніколи не зможете запустити додаток, вийти з нього, а потім швидко запустити його знову. Android не обов'язково знищує процес хостинг ОС, як тільки немає активних дій. У цьому випадку, коли ви знову запускаєте додаток, змінна IS_APP_RUNNINGбуде, trueі ваш додаток негайно вийде. Користувач, мабуть, не дивиться.
Девід Вассер

-2

спробуйте скористатися режимом запуску SingleInstance з налаштованістю спорідненості, щоб дозволитиповторне перегляду. Це завжди створить активність у новому завданні, але також дозволить його переробити. Перевірити dis: атрибут Affinity


2
Ймовірно, не буде працювати, оскільки, згідно з документацією, "повторне батьківство обмежене режимами" стандартний "та" єдиний "." тому що "діяльність з режимами запуску" singleTask "або" singleInstance "може бути лише в корені завдання"
bsberkeley

-2

Я знайшов спосіб запобігти початку тієї самої діяльності, це для мене чудово працює

if ( !this.getClass().getSimpleName().equals("YourActivityClassName")) {
    start your activity
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.