異なるインテントで起動されたアクティビティの複数のインスタンスを防ぐ方法


121

アプリケーションがGoogle Playストアアプリ(以前はAndroidマーケットと呼ばれていました)の[ 開く ]ボタンを使用して起動したときに、バグが発生しました。PlayストアIntentからの起動と、携帯電話のアイコンのアプリケーションメニューからの起動では使用方法が異なるようです。これにより、同じアクティビティの複数のコピーが起動され、互いに競合します。

たとえば、私のアプリがアクティビティABCで構成されている場合、この問題はABCAのスタックにつながる可能性があります。

android:launchMode="singleTask"この問題を修正するためにすべてのアクティビティを使用してみましたが、ホームボタンを押すたびに、アクティビティスタックがルートにクリアされるという望ましくない副作用があります。

予想される動作は次のとおりです。ABC->ホーム->アプリが復元されたときに、次のものが必要です:ABC->ホーム-> ABC

ホームボタンを使用するときにルートアクティビティにリセットせずに、同じタイプの複数のアクティビティを起動しないようにする良い方法はありますか?


回答:


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マーケット内の問題だけでなく、サーバーにアプリをアップロードしたり、携帯電話にメールで送信したりすることでもこの問題が発生することにも注意してください。これらすべては、パッケージインストーラーを使用してアプリをインストールします。さらに、不明確な場合に備えて、このコードをルートアクティビティのonCreateメソッドに追加するだけで済みます。
ubzack

2
デバイスにデプロイされた署名付きアプリでこれが発生するのは非常に奇妙ですが、Eclipseからデプロイされたデバッグバージョンでは発生しません。デバッグが非常に難しくなります!
Matt Connolly

6
これ、Eclipse(またはIntelliJまたは他のIDE)を介して起動する限り、Eclipseからデプロイされたデバッグバージョンで発生します。アプリがデバイスにインストールされる方法とは関係ありません。問題はアプリの起動方法にあります。
David Wasser

2
このコードによってアプリの既存のインスタンスが確実にフォアグラウンドに配置されるかどうか、誰かが知っていますか?または、単にfinish()を呼び出しますか?そして、何かが起こったという視覚的な兆候をユーザーに残さないのですか?
Carlos P

5
@CarlosP作成中のアクティビティがタスクのルートアクティビティでない場合、(定義により)その下に少なくとも1つの他のアクティビティが存在する必要あります。このアクティビティが呼び出されるとfinish()、ユーザーにはその下にあったアクティビティが表示されます。そのため、アプリの既存のインスタンスがフォアグラウンドに移動すると想定しても問題ありません。そうでない場合は、個別のタスクにアプリの複数のインスタンスがあり、作成されるアクティビティがそのタスクのルートになります。
David Wasser 2013年

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、アクション「MAIN」およびカテゴリ「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

シングルトップ起動モードを試しましたか?

http://developer.android.com/guide/topics/manifest/activity-element.htmlの説明の一部を次に示します

...新しいインテントを処理するために、「singleTop」アクティビティの新しいインスタンスを作成することもできます。ただし、ターゲットタスクのスタックの最上位にアクティビティの既存のインスタンスが既にある場合、そのインスタンスは新しいインテントを(onNewIntent()呼び出しで)受け取ります。新しいインスタンスは作成されません。その他の状況-たとえば、「singleTop」アクティビティの既存のインスタンスがターゲットタスクにあるがスタックの最上部にない場合、またはスタックの最上部にあるがターゲットタスクにはない場合-新しいインスタンスが作成され、スタックにプッシュされます。


2
私はそれを考えましたが、アクティビティがスタックの一番上にない場合はどうなりますか?たとえば、singleTopはAAを防止しますが、ABAは防止しません。
bsberkeley

Activity内でsingleTopおよびfinishメソッドを使用して、希望どおりの結果を得ることができますか?
Eric Levine

それが私が望むものをかなり達成するかどうか私は知りません。例:AとBをポップした後にアクティビティCにいる場合、新しいアクティビティAが起動され、CAのようなものはありませんか?
bsberkeley

これらの活動が何をしているかについて理解を深めることなくこれに答えることは難しい。アプリケーションとアクティビティの詳細を教えてください。ホームボタンの動作と、ボタンの動作の間に不一致があるのでしょうか。ホームボタンはアクティビティを終了せず、「バックグラウンド」にしてユーザーが別のアクティビティに切り替えられるようにします。戻るボタンは、終了/終了およびアクティビティです。そのパラダイムを破ると、ユーザーを混乱させたり苛立たせたりする可能性があります。
Eric Levine

このスレッドに別の回答を追加して、マニフェストのコピーを確認できるようにしました。
bsberkeley

4

おそらくそれはこの問題ですか?または同じバグの他の形式?


code.google.com/p/android/issues/detail?id=26658も参照してください。これは、Eclipse以外の原因が原因であることを示しています。
クリストファージョンソン

1
古くなる可能性のある問題の説明をコピーして貼り付けますか?どの部分?リンクが変更された場合、重要な部分を保持する必要がありますか?回答を最新に保つことは私の責任ですか?問題が解決した場合にのみリンクが無効になると考える必要があります。結局のところ、これはブログへのリンクではありません。
DuneCat

2

受け入れられた回答(Duane Homick)には未処理のケースがあると思います:

あなたは別のエキストラ(そして結果としてアプリの複製)を持っています:

  • マーケットから、またはホーム画面アイコン(マーケットによって自動的に配置されます)からアプリケーションを起動したとき
  • ランチャーまたは手動で作成したホーム画面アイコンでアプリケーションを起動したとき

以下は、これらのケースとステータスバー通知も処理するソリューションです(通知の場合、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とは何の関係もないことを理解していますが、他のどこにも表示されなかったため、何かを投稿したいと思いました。

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。これにより、物事がもう一度正しく機能するようになりました。残念ながら、これが他の事柄にどのような影響を与えるかはわかりません。


0

私もこの問題を抱えていました

  1. finish()を呼び出さないでください。ホームアクティビティでは、無限に実行されます。ホームアクティビティは、終了時にActivityManagerによって呼び出されます。
  2. 設定が変更されたときに通常の活動の再作成(すなわち回転スクリーン、言語変更、電話サービス、すなわち、MCC、MNCなどを変更) -と家庭の活動が、その後実行されている場合、それは、マニフェストに追加することの必要性のためにAに再び呼び出す 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クラスです。


nullのチェックが不要になるプリミティブブール値を使用する必要があります。
WonderCsabo、2015

これは常に機能するとは限りません。アプリを起動し、アプリを終了して、すぐに再び起動することはできません。Androidは、アクティブなアクティビティがなくなるとすぐにホスティングOSプロセスを終了させるわけではありません。この場合、アプリをもう一度起動すると、変数IS_APP_RUNNINGはにtrueなり、アプリはすぐに終了します。ユーザーがおもしろいと感じるものではありません。
David Wasser

-1

同じ問題があり、次の解決策を使用して修正しました。

メインアクティビティで、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" />

お役に立てば幸いです。


-2

アフィニティをallowtaskreparentingに設定してSingleInstance起動モードを使用してみてください。 これにより、常に新しいタスクでアクティビティが作成されますが、再ペアレント化も許可されます。チェックディス:アフィニティ属性


2
ドキュメントによると、「再ペアレント化は「標準」モードと「シングルトップ」モードに制限されている」ため、おそらく機能しません。「「singleTask」または「singleInstance」起動モードのアクティビティは、タスクのルートにのみ存在できるため」
bsberkeley

-2

同じ活動を開始しないようにする方法を見つけました、これは私にとってはうまくいきます

if ( !this.getClass().getSimpleName().equals("YourActivityClassName")) {
    start your activity
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.