ForegroundDispatchとonPauseに注意
Android 2.3.3(4)、NFCのForegroundDispatchを制御するにはActivityのonResume/onPauseを基準にする必要があるということで、以下のようなスニペットがよく紹介されている。
private NfcAdapter mAdapter; private PendingIntent mPendingIntent; private IntentFilter mFilters; private String mTechLists; private TextView mText; private int mCount = 0; @Override public void onCreate(Bundle savedState) { super.onCreate(savedState); mAdapter = NfcAdapter.getDefaultAdapter(this); mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED); try { ndef.addDataType("*/*"); } catch (MalformedMimeTypeException e) { throw new RuntimeException("fail", e); } mFilters = new IntentFilter { ndef, }; mTechLists = new String { new String[] { NfcF.class.getName() } }; } @Override public void onResume() { mAdapter.enableForegroundDispatch(this, mPendingIntent, mFilters, mTechLists); super.onResume(); } @Override public void onPause() { mAdapter.disableForegroundDispatch(this); super.onPause(); }
私も実際にこの通りコーディングしたのだが、この方法はケースによっては上手くいかない。例えば次々と連続して複数のNFCタグを読み込むような処理の場合、有効にしたはずのForegroundDispatchが無効になり、Androidにプリインストールされている汎用のNFCTaggerがタグを横取りしてしまうだろう。
ここで注意しなくてはならないのはonPauseメソッドである。onPauseは通常システムが現在前面にいる以外のActivityを再開する場合に呼ばれるのだが、それだけではなく、現在のActivityが新たなインテントを処理する際にも呼ばれる場合があるため、NFCのForegroundDispatchのようにPendingIntentを使ってタグの検出を処理する場合、インテントをハンドルした時点でonPuaseが呼ばれて、結果としてForegroundDispatchが無効になってしまうのだ。
意図せずに呼ばれているonPauseメソッドのスタックトレース (NfcAdapter.ACTION_TECH_DISCOVEREDアクションのインテント受信時)
NfcFeliCaTagFragment.onPause() line: 139 FragmentManagerImpl.moveToState(Fragment, int, int, int) line: 853 FragmentManagerImpl.moveToState(int, int, int, boolean) line: 991 FragmentManagerImpl.moveToState(int, boolean) line: 974 FragmentManagerImpl.dispatchPause() line: 1654 NFCFeliCaReader(FragmentActivity).onPause() line: 375 NFCFeliCaReader(Activity).performPause() line: 3851 Instrumentation.callActivityOnPause(Activity) line: 1191 ActivityThread.performNewIntents(IBinder, List) line: 1732 ActivityThread.handleNewIntent(ActivityThread$NewIntentData) line: 1742 ActivityThread.access$2300(ActivityThread, ActivityThread$NewIntentData) line: 117 ActivityThread$NewIntentData(ActivityThread$H).handleMessage(Message) line: 978 ActivityThread$H(Handler).dispatchMessage(Message) line: 99 Looper.loop() line: 130
これでは一度だけはNFCタグを読み込めるものの、2回目以降はForegroundDispatchが無効になってしまう。
これを回避するには、ForegroundDispatchを無効にするのはを明確にすることだ。例えばActivityが完全に終了したら無効にするのであれば、onResumeメソッドはそのままでonPauseメソッドを以下のように書き換える。
@Override public void onPause() { if ( this.isFinishing() ) { mAdapter.disableForegroundDispatch(this); } super.onPause(); }
これで対象のActivityが終了するまでForegroundDispatchを有効にしたままにできる。