安卓后台常驻读取NFC
正常的我们在安卓上使用NFC是调用nfcAdapter.enableForegroundDispatch然后还在这个界面的时候放上nfc卡后系统就会将nfc的信息通过pendingIntent发送出来,现在有一个这样的需求,需要常驻后台发送信息,也就是即使退出应用,这个nfc消息也能被应用接收到。
分析源码不能接收到的原因是,在enableForegroundDispatch后会注册一个名为mForegroundDispatchListener这个的回调,如果被调用了则会调用disableForegroundDispatchInternal这个方法继续调用setForegroundDispatch置空回调导致应用无法接受到nfc消息
OnActivityPausedListener mForegroundDispatchListener = new OnActivityPausedListener() {@Overridepublic void onPaused(Activity activity) {disableForegroundDispatchInternal(activity, true);}};void disableForegroundDispatchInternal(Activity activity, boolean force) {try {sService.setForegroundDispatch(null, null, null);if (!force && !activity.isResumed()) {throw new IllegalStateException("You must disable foreground dispatching " +"while your activity is still resumed");}} catch (RemoteException e) {attemptDeadServiceRecovery(e);}}
这个回调是通过以下的方式注册上去的
ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,mForegroundDispatchListener);
这个回调 会接监听应用的状态,如果这个应用是退出界面的状态,那么这个就会被这个接收到,然后设置了一个标志位,所以根据这个流程,我们只需要让应用的生命周期无法在paused的时候回调这个方法即可,所以我们提前反注册这个回调,就会破坏这个流程,让系统认为这个应用一直在前台回调nfc卡的信息,
那么我们就使用以下方法,大概流程是通过反射把mForegroundDispatchListener对象取出来,然后继续反射ActivityThread.currentActivityThread().unregisterOnActivityPausedListener这个方法反注册这个回调,以下是代码
nfcAdapter = NfcAdapter.getDefaultAdapter(this);PendingIntent pendingIntent = PendingIntent.getService(this,0,new Intent(this,NfcService.class),0);nfcAdapter.enableForegroundDispatch(this,pendingIntent,null,null);Class<?> cNfcAdapter = nfcAdapter.getClass();Field[] fields = cNfcAdapter.getDeclaredFields();Field fForegroundDispatchListener = null;for (Field field : fields) {Log.i("nfcinvoke","field name " + field.getName());if(field.getName().equalsIgnoreCase("mForegroundDispatchListener")){fForegroundDispatchListener = field;}}fForegroundDispatchListener.setAccessible(true);try {Class<?> cActivityThread = Class.forName("android.app.ActivityThread");Method mCurrentActivityThread = cActivityThread.getMethod("currentActivityThread");mCurrentActivityThread.setAccessible(true);Object activityThread = mCurrentActivityThread.invoke(null);Method[] methods = activityThread.getClass().getMethods();Method mUnregisterOnActivityPausedListener = null;for (Method method : methods) {Log.i("wubin","method name 1111 " + method.getName());if(method.getName().equalsIgnoreCase("unregisterOnActivityPausedListener")){mUnregisterOnActivityPausedListener = method;mUnregisterOnActivityPausedListener.setAccessible(true);}}mUnregisterOnActivityPausedListener.invoke(activityThread,this,fForegroundDispatchListener.get(nfcAdapter));} catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}
需要注意的是,以上反射需要systemuid+系统签名的方式,如果不是的话,反射的时候这两个方法是找不到的。
如果是有系统源码的话,那就在这个nfcadapter里面做一些手脚,比如在反注册前读取一下某个标志位决定是否真的反注册。