web-dev-qa-db-ja.com

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

Google Playストアアプリの"Open"ボタン(以前はAndroid Market)と呼ばれていました)を使用して起動すると、アプリケーションのバグに遭遇しました。 Playストアから起動する場合は、電話機のアイコンのアプリケーションメニューから起動するのとは異なるIntentを使用するようです。これにより、同じアクティビティの複数のコピーが起動され、互いに競合します。

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

この問題を解決するためにすべてのアクティビティでAndroid:launchMode="singleTask"を使用しようとしましたが、HOMEボタンを押すたびにアクティビティスタックをルートにクリアするという望ましくない副作用があります。

予想される動作は次のとおりです A-B-C-> HOME->アプリが復元されたら、A-B-C-> HOME-> A-B-Cが必要です

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

116
bsberkeley

これを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;       
    }
}
179
Duane Homick

これが失敗する理由と、このバグをプログラムで再現してテストスイートに組み込む方法を説明するだけです。

  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()回避策を組み込んでいない場合、これは問題を再現します。自動テストでこれを使用して、このバグが二度と発生しないことを確認します。

お役に立てれば!

25
gilm

singleTop起動モードを試しましたか?

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

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

7
elevine

おそらく この問題 ?または、同じバグの他の形?

4
DuneCat

受け入れられた答え( 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
StanislavKo

この質問は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であるため、上記の解決策は特定の状況には十分ではありません。現在、LaunchModeSingleTaskに設定しました。残念ながら、これが他のものにどのような影響を与えるかはわかりません。

2
hvaughan3

この解決策を試してください:
Applicationクラスを作成し、そこで定義します:

_public static boolean IS_APP_RUNNING = false;
_

次に、setContentView(...)の前のonCreateでの最初の(ランチャー)アクティビティでこれを追加します。

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

追伸Controllerは私のApplicationクラスです。

0
Volodymyr Kulyk

この問題もありました

  1. Finish()を呼び出さないでください。ホームアクティビティでは、無限に実行されます。ホームアクティビティは、終了時にActivityManagerによって呼び出されます。
  2. 通常、構成が変更されると(画面の回転、言語の変更、テレフォニーサービスの変更、つまりmcc mncなど)、アクティビティが再作成されます。ホームアクティビティが実行されている場合、マニフェストAndroid:configChanges="mcc|mnc"-携帯電話に接続している場合は、 http://developer.Android.com/guide/topics/manifest/activity-element.html#config を参照してください。システムまたはプッシュオープンなど。
0
user1249350