ハードキーフックの方法

フックしたいんだ

今作っているアプリでBACKキーのフックをしたかったので、以下のようなコードを書きました。

@Override
public boolean onKeyUp(int keyCode, KeyEvent event){
 if(keyCode == KeyEvent.KEYCODE_BACK) {
   foo();
   return true;
 }
 return false;
}

しかしこれでは思ったように機能してくれません。
先にKeyDownで本来のBACKキーの機能が働き、Activityが閉じてしまいます。
onKeyDownをオーバーライドすればうまくいきそうなので、書き直します。

@Override
public boolean onKeyDown(int keyCode, KeyEvent event){
 if(keyCode == KeyEvent.KEYCODE_BACK) {
   foo();
   return true;
 }
 return false;
}

これでBACKキーのフックができました。
HT-03Aで動かしてみても、正常に動作することが確認できます。

しかし

この実装をしたときに、何気なくツイートしたところ、
@adamrockerさんにdispatchKeyEventの方がいいよとアドバイスされました。
たしかにHT-03A(1.6)ではonKeyDownにBACKキーがアサインされていたけど、
他の端末ではその保障はありません。
実は他の端末ではonKeyUpにアサインされていて、残念なことになることもありえます。
dispatchKeyEventならKeyDownのときも、KeyUpのときも呼ばれるので、安心ですね。

せっかくなんで

すでにバージョンによって動きが違う部分があるのかどうか、調べてみました。
ソース読みます。

Activityクラスのソースコード
Activity.java - core/java/android/app - Code Search:
http://www.google.co.jp/codesearch/p?hl=ja#uX1GffpyOZk/core/java/android/app/Activity.java

BACKキーが押されたときに最終的に呼ばれるメソッドは
このonBackPressed()というメソッドです。
1884行目付近

public void onBackPressed() {
 finish();
}

finish()を呼んでいるので、Activityは終了します。
シンプルでいいですね。


まずはonKeyDownメソッドを見てみます。
1175行目付近

public boolean onKeyDown(int keyCode, KeyEvent event)  {
 if (keyCode == KeyEvent.KEYCODE_BACK) {
   if (getApplicationInfo().targetSdkVersion
       >= Build.VERSION_CODES.ECLAIR) {
     event.startTracking();
   } else {
     onBackPressed();
   }
   return true;
 }
 //...
}

Build.VERSION_CODES.ECLAIRなんてステキなコードが書いてありますね。
Eclair移行だとonKeyDownではonBackPressed()が呼ばれず、eventのstartTrackingが呼ばれます。
KeyEvent#startTracking()はTRACKINGというフラグを立てる処理だけしています。
Eclair以前のバージョンであれば、onKeyDown()でBACKキーが働くみたいですね。

続いてonKeyUpメソッドを見てみます。
1858行目付近

public boolean onKeyUp(int keyCode, KeyEvent event) {
 if (getApplicationInfo().targetSdkVersion
     >= Build.VERSION_CODES.ECLAIR) {
   if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
       && !event.isCanceled()) {
     onBackPressed();
     return true;
   }
 }
 return false;
}

Eclair移行は、onKeyDownで立てられたTRACKINGフラグをみて、onBackPressed()を呼んでます。
すでにハードキーの動きが違う部分があるんですね。
TRACKINGフラグがあるので、onKeyDownでフックしたままでも大丈夫そうですが
dispatchKeyEventでやっといた方が安心ですね。

まとめ

dispatchKeyEventが安心