Как предотвратить несколько экземпляров действия, когда оно запускается с разными намерениями
Я столкнулся с ошибкой в моем приложении, когда оно запускалось с помощью кнопки "Открыть" в приложении 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
}