问题
I\'ve come across a bug in my application when it is launched using the \"Open\" button on the Google Play Store app (previously called Android Market). It seems that launching it from the Play Store uses a different Intent
than launching it from the phone\'s application menu of icons. This is leading to multiple copies of the same Activity being launched, which are conflicting with each other.
For example, if my app consists of the Activities A-B-C, then this issue can lead to a stack of A-B-C-A.
I tried using android:launchMode=\"singleTask\"
on all the Activities to fix this problem, but it has the unwanted side-effect of clearing the Activity stack to root, whenever I hit the HOME button.
The expected behavior is: A-B-C -> HOME -> And when the app is restored, I need: A-B-C -> HOME -> A-B-C
Is there a good way to prevent launching multiple Activities of the same type, without resetting to the root activity when using the HOME button?
回答1:
Add this to onCreate and you should be good to go:
// 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:
I'm just going to explain why it fails, and how to reproduce this bug programmatically so you can incorporate this in your test suite:
When you launch an app through Eclipse or Market App, it launches with intent flags: FLAG_ACTIVITY_NEW_TASK.
When launching through the launcher (home), it uses flags: FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED, and uses action "MAIN" and category "LAUNCHER".
If you would like to reproduce this in a test case, use these steps:
adb shell am start -f 0x10000000 -n com.testfairy.tests.regression.taskroot/.MainActivity
Then do whatever is needed to get to the other activity. For my purposes, I just placed a button that starts another activity. Then, go back to the launcher (home) with:
adb shell am start -W -c android.intent.category.HOME -a android.intent.action.MAIN
And simulate launching it via the launcher with this:
adb shell am start -a "android.intent.action.MAIN" -c "android.intent.category.LAUNCHER" -f 0x10600000 -n com.testfairy.tests.regression.taskroot/.MainActivity
If you haven't incorporated the isTaskRoot() workaround, this will reproduce the problem. We use this in our automatic testing to make sure this bug never occurs again.
Hope this helps!
回答3:
Have you tried the singleTop launch mode?
Here is some of the description from http://developer.android.com/guide/topics/manifest/activity-element.html:
... a new instance of a "singleTop" activity may also be created to handle a new intent. However, if the target task already has an existing instance of the activity at the top of its stack, that instance will receive the new intent (in an onNewIntent() call); a new instance is not created. In other circumstances — for example, if an existing instance of the "singleTop" activity is in the target task, but not at the top of the stack, or if it's at the top of a stack, but not in the target task — a new instance would be created and pushed on the stack.
回答4:
Perhaps it is this issue? Or some other form of the same bug?
回答5:
I think the accepted answer (Duane Homick) has unhandled cases:
You have different extras (and app duplicates as a result):
- when you launch application from Market or by home screen icon (which is placed by Market automatically)
- when you launch application by launcher or manually created home screen icon
Here is a solution (SDK_INT>=11 for notifications) which i belive handle these cases and statusbar notifications also.
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);
回答6:
I realize that the question does not have anything to do with Xamarin Android but I wanted to post something since I did not see it anywhere else.
To fix this in Xamarin Android I used the code from @DuaneHomick and added into MainActivity.OnCreate()
. The difference with Xamarin is that is must go after Xamarin.Forms.Forms.Init(this, bundle);
and LoadApplication(new App());
. So my OnCreate()
would look like:
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
}
}
}
*Edit: Since Android 6.0, the above solution is not enough for certain situations. I have now also set LaunchMode
to SingleTask
, which seems to have made things work correctly once again. Not sure what effects this might have on other things though unfortunately.
回答7:
I had this problem also
- Don't call finish(); in the home activity it would run endlessly - home activity is being called by ActivityManager when it finished.
- Usually when the configuration is changing (i.e. rotate screen, change language, telephony service changes i.e. mcc mnc etc.) the activity recreate - and if the home activity is running then it calls again to A. for that need to add to manifest
android:configChanges="mcc|mnc"
- if you have connection to cellular, see http://developer.android.com/guide/topics/manifest/activity-element.html#config for which configuration there is when booting the system or push open or whatever.
回答8:
Try this solution:
Create Application
class and define there:
public static boolean IS_APP_RUNNING = false;
Then in your first (Launcher) Activity in onCreate
before setContentView(...)
add this:
if (Controller.IS_APP_RUNNING == false)
{
Controller.IS_APP_RUNNING = true;
setContentView(...)
//Your onCreate code...
}
else
finish();
P.S. Controller
is my Application
class.
回答9:
I had the same problem, and I fixed it using the following solution.
In your main activity add this code on the top of the onCreate
method:
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();
}
}
don't forget to add this permission in your manifest.
< uses-permission android:name="android.permission.GET_TASKS" />
hope it helps you.
回答10:
try using SingleInstance launch mode with affinity set to allowtaskreparenting This will always create the activity in new task but also allow its reparenting. Check dis :Affinity attribute
回答11:
I found a way to prevent starting same activities, this works great for me
if ( !this.getClass().getSimpleName().equals("YourActivityClassName")) {
start your activity
}
来源:https://stackoverflow.com/questions/4341600/how-to-prevent-multiple-instances-of-an-activity-when-it-is-launched-with-differ