Infinito Nirone 7

白羽の矢を刺すスタイル

Context#startActivity からすぐに Activity が起動しないパターン

前提条件

Service から次のように Activity を起動しようとした時、直近 5 秒以内に他の Activity を Home キーで閉じていると、startActivity の呼び出しからすぐには Activity が起動しません。

// this は Service
Intent intent = new Intent(this, SomeActivity.class);
// Service から起動するときには FLAG_ACTIVITY_NEW_TASK が必要
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

Affinity やその他 LaunchMode などの設定に関わらず、Activity を Home キーで閉じたあと 5 秒は Context#startActivity を呼んでもすぐに Activity は起動しないようになっています。

StackOverflow

同じようなことにハマっている人がいて、質問が上がっていました。

stackoverflow.com

Home キーをおした時の処理

PhoneWindowManager に、Home キーを押したときの処理があります。具体的にはPhoneWindowManager#launchHomeFromHotKey(boolean, boolean)がその処理を受け持つメソッドです。 このメソッドはまず、 KeyGuard が有効かどうかで処理を分岐し、有効でロックスクリーンが表示されている場合はなにもしません。 KeyGuard のアンロックに成功した時、または KeyGuard が関係ない場合にはホームに戻る処理を実行しますが、どちらも必ず次の処理が入ります。

try {
  ActivityManagerNative.getDefault().stopAppSwitches();
} catch (RemoteException e) {
}

名前からして何かを差し止めるための処理に見えますが、ActivityManagerNative.getDefault()で取得しているオブジェクトの実体はActivityManagerServiceです。

ActivityManagerService#stopAppSwitches()

そしてActivityManagerService#stopAppSwitches()を見ると、次にあげる処理を実行しています。

  1. メンバ変数のmAppSwitchesAllowedTimeに5000ミリ秒後(==5秒後)を代入
  2. メンバ変数のmDidAppSwitchにfalseを代入
  3. Handlerに送信した未実行のDO_PENDING_ACTIVITY_LAUNCHES_MSGを削除
  4. APP_SWITCH_DELAY_TIMEの遅延(5000ミリ秒==5秒)でDO_PENDING_ACTIVITY_LAUNCHES_MSGを再送信

これで"Home キー押下後 5 秒は startActivity がすぐに効かない"の 5 秒という時間の理由がはっきりしました。またDO_PENDING_ACTIVITY_LAUNCHES_MSGという定数名から分かるとおり、5 秒後にActivityManagerService#L1878で Activity の起動を再開します。

この 5 秒以内にContext#startActivity(Intent)を実行すると、ActivityManagerService#checkAppSwitchAllowedLocked()でfalseが返ります。このメソッドを読んでいるとandroid.Manifest.permission.STOP_APP_SWITCHESというパーミッションの存在に気が付きますが、このパーミッションの ProtectionLevel はsystemOrSignatureですから、通常のアプリは許可を得られません。

なぜ Home キー押下後の 5 秒間 Context#startActivity() を止められるのか

APP_SWITCH_DELAY_TIMEのコメントによると、Home キーを押したあとに信頼されない Activity の起動を防ぐためとあります。Home キーのイベントで Activity を起動するような、端末をハイジャックしたような挙動を防ぐためのもののようです。

Home キーのイベント処理からコードを追いかけて分かる通り、Activity#onUserLeaveHint()などでActivity#finish()をしていてもこの問題は回避できません。なぜなら問答無用で Home キーのイベントで強制的に 5 秒間の保留が決まるからです。