Activityの連続起動を検知する

事の発端
自作のアプリケーションランチャーでHomeボタンの押下を検知したくなった。
Homeボタンを一回押したらランチャーを表示し、ランチャーが表示されている状態でHomeボタンを押したら別の外部のHomeアプリを起動したい(PreHomeみたいな動作)。


というわけでwebで調査していたところ、日本androidの会にActivity#onUserLeaveHintをオーバーライドすればHomeボタンの検知ができるとの情報があった。
しかしAPIリファレンスを読んでも、記述があいまいと言うか、いまいち要領を得ない。


試してみた
ActivityLifeCycleというテストアクティビティをつくってlogをトレースしてみる。


(1)Homeボタン押下(HomeApp起動)

ActivityLifeCycle(10487): onUserInteraction
ActivityLifeCycle(10487): onUserLeaveHint
ActivityLifeCycle(10487): onPause
ActivityManager(120): Displayed activity com.example.android.home/.HomeApp: 285 ms (total 285 ms)


(2)Homeボタン長押し (アプリ履歴表示)

dalvikvm(204): GC_EXPLICIT freed 3053 objects / 178712 bytes in 79ms
dalvikvm(120): GC_EXTERNAL_ALLOC freed 21870 objects / 973168 bytes in 105ms
StatusBar(120): DISABLE_EXPAND: yes
  • > 自Activity(ActivityLifeCycle )起動
ActivityManager(120): Starting activity: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10100000 cmp=com.amle.android.mysample/.ActivityLifeCycle }


(3)Homeボタン長押し(アプリ履歴表示)

dalvikvm(120): GC_EXTERNAL_ALLOC freed 19860 objects / 988712 bytes in 104ms
StatusBar(120): DISABLE_EXPAND: yes

-> 他アプリ(smartbar/.GridFavouristApps)起動

ActivityManager(120): Starting activity: Intent { flg=0x10100000 pkg=com.aps.smartbar cmp=com.aps.smartbar/.GridFavouristApps }
ActivityLifeCycle(10487): onUserInteraction
ActivityLifeCycle(10487): onUserLeaveHint
ActivityLifeCycle(10487): onPause
AudioFlinger(90): write blocked for 163 msecs, 2069 delayed writes, thread 0xdae0
ActivityManager(120): Displayed activity com.aps.smartbar/.GridFavouristApps: 421 ms (total 421 ms)


(4)検索ボタン長押し(他アプリ(smartbar/.GridFavouristApps)起動)

ActivityLifeCycle(10257): onUserInteraction
ActivityLifeCycle(10257): onUserInteraction
ActivityManager(120): Starting activity: Intent { act=android.intent.action.SEARCH_LONG_PRESS flg=0x10000000 cmp=com.aps.smartbar/.SearchButtonClickedReceiver }
ActivityLifeCycle(10257): onUserInteraction
ActivityLifeCycle(10257): onUserLeaveHint
ActivityLifeCycle(10257): onPause
ActivityLifeCycle(10257): onUserInteraction
InputManagerService(120): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@445bd798
ActivityLifeCycle(10257): onUserInteraction
ActivityManager(120): Starting activity: Intent { flg=0x10000000 pkg=com.aps.smartbar cmp=com.aps.smartbar/.GridFavouristApps }


(5)ステータスバーを開く

(反応なし)
  • >他アプリ(MobileRTM/.Activities.RTMReminderActivity)起動
ActivityManager(120): Starting activity: Intent { flg=0x10000000 cmp=com.rememberthemilk.MobileRTM/.Activities.RTMReminderActivity bnds=[0,568][480,664] (has extras) }
ActivityLifeCycle(10487): onUserInteraction
ActivityLifeCycle(10487): onUserLeaveHint
ActivityLifeCycle(10487): onPause
AudioFlinger(90): write blocked for 174 msecs, 2072 delayed writes, thread 0xdae0
ActivityManager(120): Displayed activity com.rememberthemilk.MobileRTM/.Activities.RTMReminderActivity: 186 ms (total 186 ms)


(6)自Activityの表示中に外部のアラームアプリを使ってAlarmManager経由で他アプリ(myalarm/.TimeupActivity)を起動させる。

ActivityManager(52): Starting activity: Intent { act=android.intent.action.MAIN flg=0x10000000 cmp=com.amle.android.myalarm/.TimeupActivity (has extras) }
ActivityLifeCycle(1183): onUserInteraction
ActivityLifeCycle(1183): onUserLeaveHint
ActivityLifeCycle(1183): onPause
AlarmService(353): onStart


(7)自Activityで別スレッドを作り、10秒後にブラウザを起動。

ActivityManager(52): Starting activity: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.android.browser/.BrowserActivity }
ActivityLifeCycle(1146): onUserInteraction
ActivityLifeCycle(1146): onUserLeaveHint
ActivityLifeCycle(1146): onPause


(8)自Activityで別スレッドを作り、10秒後に自Activityをfinish()する。

ActivityLifeCycle(1240): onPause


(9)自Activityで別スレッドを作り、10秒後に自アプリの別activity(MySample)を起動する。

dalvikvm(204): GC freed 117 objects / 5256 bytes in 66ms
ActivityManager(52): Starting activity: Intent { cmp=com.amle.android.mysample/.MySample }
ActivityLifeCycle(626): onUserInteraction
ActivityLifeCycle(626): onUserLeaveHint
ActivityLifeCycle(626): onPause


(10)自Activityで別スレッドを作り、10秒後に自アプリの別activity(MySample)を起動し、その後自分をfinish()する。

dalvikvm(544): GC freed 2963 objects / 314176 bytes in 87ms
ActivityManager(52): Starting activity: Intent { cmp=com.amle.android.mysample/.MySample }
ActivityLifeCycle(662): onUserInteraction
ActivityLifeCycle(662): onUserLeaveHint
ActivityLifeCycle(662): onPause

いろいろなパターンを試してみたが、まとめると

  • 他のActivityを起動させるとonUserLeaveHint()が呼ばれる。(Homeキー、検索キー長押し、バックグラウンドスレッドからのstartActivity()やAlarmManagerからのブロードキャストなど手段を問わない。)
  • 自アプリ内の他のactivityを起動させても呼ばれる。
  • 自Activityを起動させても呼ばれない。
  • 自Activityをfinish()させても呼ばれない。(ただし他のActivityを起動した後にfinish()してもonUserLeaveHint()は呼ばれる。)
  • ステータスバーの開閉、標準のアプリ履歴の表示では呼ばれない。(そこから別のActivityを起動すると呼ばれる。)

…となる。


実際のところonUserLeaveHint()は、他activityが起動される時には、一部の例外(着呼のときには呼ばれない)を除いてほとんど常に呼ばれると思ったほうが良さそうだ。


結論:
onUserLeaveHint()は、他のactivityが起動された(startActivity()された)ことを検知するためのメソッドであると考えられる。
最初はonPause()との使い分けがわからなかったが、「電話の着信では呼ばれない」、「自activityをfinish()しても呼ばれない」という2点でonPause()と異なる。


結局のところHomeボタンの検知に使えるわけではなさそうだが2重起動の検知には使えそうだ。
つまり、onUserLeaveHint()を挟むことなく連続でonResume()が呼び出されたら、それは連続起動(ランチャーActivityの表示中にさらにランチャーActivityが呼び出された)である。


着呼の時にはonUserLeaveHint()が呼び出されないが、この時はActivityが終了するようなので連続起動扱いにはならない。
つまり、着呼の後には常にランチャーが表示されることになる。
この動作が正しいかどうかはアプリの仕様次第だろう。


参考:
Google グループ
Activity  |  Android Developers