8.3.2 NFC系统模块

最后更新于:2022-04-02 06:05:48

本节开始时介绍,Android平台中,NFC系统模块运行在com.android.nfc进程中,该进程对应的应用程序文件名为Nfc.apk。NFC系统模块包含的组件非常多,所以通过以下几条分析路线来介绍。 * NFC系统模块的核心NfcService和一些重要成员的作用及之间的关系。 * R/W模式下NFC Tag的处理。 * Android Beam的实现。 * CE模式相关的处理。 **1、NfcService介绍** Nfc.apk源码中包含一个NfcApplication类。当该应用启动时,NfcApplication的onCreate函数将被调用。正是在这个onCreate函数中,NFC系统模块的核心成员NfcService得以创建。我们直接来看NfcService的构造函数。 **NfcService.java::NfcService** ~~~ public NfcService(Application nfcApplication) { // NFC系统模块重要成员 mNfcTagService = new TagService();// TagService用于和NFC Tag交互 // NfcAdapterService用于和Android系统中其他使用NfcService的客户端交互 mNfcAdapter = new NfcAdapterService(); // NfcAdapterExtrasService用于和Android系统中使用Card Emulation模式的客户端交互 mExtrasService = new NfcAdapterExtrasService(); sService = this; mContext = nfcApplication; // NativeNfcManager由dhimpl模块实现,用于和具体芯片厂商提供的NFC模块交互 mDeviceHost = new NativeNfcManager(mContext, this); // HandoverManager处理Connection Handover工作 HandoverManager handoverManager = new HandoverManager(mContext); // NfcDispatcher用于向客户端派发NFC Tag相关的通知 mNfcDispatcher = new NfcDispatcher(mContext, handoverManager); // P2pLinkManager用于处理LLCP相关的工作 mP2pLinkManager = new P2pLinkManager(mContext, handoverManager, mDeviceHost.getDefaultLlcpMiu(), mDeviceHost.getDefaultLlcpRwSize()); // NativeNfcSecureElement用于和SE交互,它也由dhimpl模块实现 mSecureElement = new NativeNfcSecureElement(mContext); mEeRoutingState = ROUTE_OFF; /* NfceeAccessControl用于判断哪些应用程序有权限操作NFCEE。它将读取/etc/nfcee_access.xml文件的 内容。nfcee_access.xml内容比较简单,请参考Nfc目录下的etc/sample_nfcee_access.xml来学习。 */ mNfceeAccessControl = new NfceeAccessControl(mContext); ...... // 向系统注册一个“nfc”服务。注意,SERVICE_NAME的值为“nfc”。该服务对应的对象为mNfcAdapter ServiceManager.addService(SERVICE_NAME, mNfcAdapter); /* 注册广播事件监听对象。NfcService对屏幕状态、应用程序安装和卸载等广播事件感兴趣。这部分内容请读者 自行研究。 */ ...... // EnableDisableTask为一个AsyncTask,TASK_BOOT用于NfcService其他初始化工作 new EnableDisableTask().execute(TASK_BOOT); } ~~~ 由上述代码可知,NfcService在其构造函数中,首先创建了NFC系统模块的几个核心成员。下文将详细介绍它们的作用及之间的关系。NfcService向Binder系统添加了一个名为"nfc"的服务,该服务对应的Binder对象为mNfcAdapter,类型为NfcAdapter。通过一个AysncTask(代码中的EnableDisableTask)完成NfcService其他初始化工作。 下面马上来看NFC系统模块核心成员。 **①、NfcService核心成员** 图8-35所示为NfcAdapter、TagService等相关成员的类信息。 :-: ![](http://img.blog.csdn.net/20140322214731484?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSW5ub3N0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 图8-35 NfcAdapter、TagService及相关类成员结构图 图8-35中,NfcAdapter包含一个类型为INfcAdapter的sService成员变量,该变量通过Android Binder机制来和NfcService内部类NfcAdapter的实例(即上述代码中的mAdapter)交互。NfcService内部的NfcAdapter对象即是注册到Android系统服务中的那个名为"nfc"的Binder服务端对象。 NfcAdapter还包含一个类型为INfcTag的sTagService成员变量,该变量通过Android Binder机制来和NfcService内部类TagService的实例(即上述代码中的mNfcTagService)交互。INfcTag接口封装了对Tag操作相关的函数。注意,前面所示的Nfc客户端示例中并没有直接使用INfcTag接口的地方,但表8-12所列的各种Tech类内部需要通过ITag接口来操作NFC Tag。 NfcAdapterExtras包含一个类型为INfcAdapterExtras的sService成员变量,也通过Android Binder机制来和NfcService内部类NfcAdapterService的实例(即上述代码中的mExtrasService)交互。 接着来看NfcService和NativeNfcManager,它们的类家族如图8-36所示。 :-: ![](http://img.blog.csdn.net/20140322214753375?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSW5ub3N0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 图8-36 NativeNfcManager和NfcService类家族 Android NFC系统模块通过接口类DeviceHost和其内部的接口类LlcpServerSocket、DeviceHostListener、LlcpSocket、LlcpConnectionlessSocket、NfcDepEndpoint、TagEndpoint将NFC系统模块中和NFC芯片无关的处理逻辑,以及和芯片相关的处理逻辑进行了有效解耦。图8-36中以"Native"开头的类均定义在packages/app/Nfc/nxp目录下,所以它们和NXP公司的NFC芯片相关。 DeviceHost接口中,DeviceHost类用于和底层NFC芯片交互,TagEndpoint用于和NFC Tag交互,NfcDepEndpoint用于和P2P对端设备交互,LlcpSocket和LlcpServerSocket分别用于LLCP中有链接数据传输服务的客户端和服务器端,LlcpConnectionlessSocket用于LLCP中无连接数据传输服务。另外,DeviceHostListener也非常重要,它用于NativeNfcManager往NfcService传递NFC相关的通知事件。例如其中的onRemoteEndpointDiscovered代表搜索到一个NFC Tag、onLlcpActivited代表本机和对端NFC设备进入Link Activation(链路激活)阶段。 NativeNfcManager实现了DeviceHost接口,以NXP公司的NativeNfcManager为例,它将通过libnfc_jni及libnfc和NXP公司的NFC芯片交互。 NativeNfcSecureElement用来和Secure Element交互。 接下来要出场的是HandoverManager以及P2pLinkManager,它们的家族关系如图8-37所示。 :-: ![](http://img.blog.csdn.net/20140322214814406?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSW5ub3N0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 图8-37 P2pLinkManager家族关系图 图8-37所示的P2pLinkManager家族关系非常复杂,图中的各成员说明如下。 * P2pLinkManager包含三个和传输相关的Server,分别是SnepServer、NdefPushServer以及HandoverServer。每一个Server都定义了相关的回调接口类(如SnepServer.callback),这些回调接口类的实现均由P2pLinkManager的内部类来实现。 * HandoverServer负责处理NFC Connection Handover协议,而具体的数据传输则由HandoverManager来实现。由图中HandoverMangar的mBluetoothAdapter可知,Android默认使用蓝牙来传输数据(提示:这部分内容请读者学完本章后自行研究)。 * P2pEventManager用于处理NFC P2P中和用户交互相关的逻辑。例如当搜索到一个P2P设备时,手机将会震动并发出提示音。 SendUi实现了类似图8-29左图的界面及用户触摸事件处理。在Android平台中,当两个设备LLCP链路被激活时,SendUi将显示出来。其界面组成部分包括两个部分,一个是SendUi界面显示之前手机的截屏图像,另外一个是“触摸即可发送”的文本提示信息。当用户触摸SendUi界面时,数据将被发送出去。 **②、enableInternal函数** 介绍完NfcService的几个核心成员后,马上来看NfcService构造函数中最后创建的EnableDisableTask,由于设置了参数为TASK_BOOT,故最终被执行的函数为enableInternal。 **NfcService.java::EnableDisableTask:enableInternal** ~~~ boolean enableInternal() { ...... // 启动一个WatchDog线程用来监视NFC底层操作是否超时 WatchDogThread watchDog = new WatchDogThread("enableInternal", INIT_WATCHDOG_MS); watchDog.start(); try { mRoutingWakeLock.acquire(); try { // 初始化NFC底层模块,这部分内容请读者自行阅读 if (!mDeviceHost.initialize()) {......} } finally { mRoutingWakeLock.release(); } } finally { watchDog.cancel(); } synchronized(NfcService.this) { mObjectMap.clear(); // mIsNdefPushEnabled判断是否启用NPP协议,可在Settings中设置 mP2pLinkManager.enableDisable(mIsNdefPushEnabled, true); updateState(NfcAdapter.STATE_ON); } initSoundPool();// 创建SoundPool,用于播放NFC相关事件的通知音 applyRouting(true);// 启动NFC Polling流程,一旦搜索到周围的NFC设备,相关回调将被调用 return true; } ~~~ 我们重点关注上面代码中和P2pLinkManager相关的enableDisable函数,其代码如下所示。 **P2pLinkManager.java::enableDisable** ~~~ public void enableDisable(boolean sendEnable, boolean receiveEnable) { synchronized (this) {// 假设参数sendEnable和receiveEnable为true if (!mIsReceiveEnabled && receiveEnable) { /* 启动SnepServer、NdefPushServer和HandoverServer。 下面这三个成员变量均在P2pLinkManager的构造函数中被创建,这部分内容请读者自行阅读 本章将只分析SnepServer。 */ mDefaultSnepServer.start(); mNdefPushServer.start(); mHandoverServer.start(); if (mEchoServer != null) // EchoServer用于测试,以后代码分析将忽略它 mHandler.sendEmptyMessage(MSG_START_ECHOSERVER); }...... mIsSendEnabled = sendEnable; mIsReceiveEnabled = receiveEnable; } } ~~~ **③、SnepServer的start函数** SnepServer的start函数将创建一个ServerThread线程对象,其run函数代码如下所示。 **SnepServer.java::ServerThread:run** ~~~ public void run() {// 注意:为了方便阅读,此处代码省略了synchronized和try/catch等一些代码逻辑 boolean threadRunning; threadRunning = mThreadRunning; while (threadRunning) { // 创建一个LlcpServerSocket,其中mServiceSap值为0x04,mServiceName为“urn:nfc:sn:sne” // mMiu和mRwSize为本机NFC LLCP层的MIU和RW大小。1024为内部缓冲区大小,单位为字节 mServerSocket = NfcService.getInstance().createLlcpServerSocket(mServiceSap, mServiceName, mMiu, mRwSize, 1024); LlcpServerSocket serverSocket; serverSocket = mServerSocket; // 等待客户端的链接 LlcpSocket communicationSocket = serverSocket.accept(); if (communicationSocket != null) { // 获取客户端设备的MIU int miu = communicationSocket.getRemoteMiu(); /* 判断分片大小。mFragmentLength默认为-1。MIU非常重要。例如本机的MIU为1024,而 对端设备的MIU为512,那么本机在向对端发送数据时,每次发送的数据不能超过对端 MIU即512字节。 */ int fragmentLength = (mFragmentLength == -1) ?miu : Math.min(miu, mFragmentLength); // 每一个连接成功的客户端对应一个ConnectionThread,其内容留待下文详细分析 new ConnectionThread(communicationSocket, fragmentLength).start(); } } mServerSocket.close(); } ~~~ NfcService初始化完毕后,手机中的NFC模块就进入工作状态,一旦有Tag或其他设备进入其有效距离,NFC模块即可开展相关工作。 下面先来分析NFC Tag的处理流程。 **2、NFC Tag处理流程分析** **①、notifyNdefMessageListeners流程** 当NFC设备检测到一个NFC Tag时,NativeNfcManager的notifyNdefMessageListeners函数将被调用(由libnfc_jni在JNI层调用),其代码如下所示。 **NativeNfcManager.java::notifyNdefMessageListeners** ~~~ private void notifyNdefMessageListeners(NativeNfcTag tag) { /* mListener指向NfcService,它实现了DeviceHostListener接口。 注意,notifyNdefMessageListeners的参数类型为NativeNfcTag,tag对象由jni层直接创建 并返回给Java层。 */ mListener.onRemoteEndpointDiscovered(tag); } ~~~ 上述代码中,mListener指向NfcService,它的onRemoteEndPointDiscovered函数代码如下所示。 **NfcService.java::onRemoteEndpointDiscovered** ~~~ public void onRemoteEndpointDiscovered(TagEndpoint tag) { // 注意,onRemoteEndpointDiscovered的参数类型是TagEndpoint // 由图8-36可知,NativeNfcTag实现了该接口 sendMessage(NfcService.MSG_NDEF_TAG, tag);// 发送一个MSG_NDEF_TAG消息 } ~~~ NfcService的onRemoteEndpointDiscovered将给自己发送一个MSG_NDEF_TAG消息。 NfcService内部有一个NfcServiceHandler专门用来处理这些消息。其处理函数如下所示。 **NfcService.java::NfcServiceHandler:handleMessage** ~~~ final class NfcServiceHandler extends Handler { public void handleMessage(Message msg) { switch (msg.what) { ......// 其他消息处理 case MSG_NDEF_TAG: TagEndpoint tag = (TagEndpoint) msg.obj; playSound(SOUND_START);// 播放一个通知音 /* 从该NFC Tag中读取NDEF消息,NativeNfcTag的findAndReadNdef比较复杂, 其大体工作流程是尝试用该Tag支持的Technology来读取其中的内容。 */ NdefMessage ndefMsg = tag.findAndReadNdef(); // 注意下面这段代码,无论ndefMsg是否为空,dispatchTagEndpoint都会被调用 if (ndefMsg != null) { tag.startPresenceChecking();// 检测目标Tag是否还在有效距离内 dispatchTagEndpoint(tag);// 重要函数,详情见下节 } else { if (tag.reconnect()) {// 重新链接到此NFC Tag tag.startPresenceChecking(); dispatchTagEndpoint(tag); } ...... } break; ...... } } } ~~~ 由上述代码可知,NfcService先调用TagEndpoint的findAndReadNdef函数来读取Tag中的数据,然后NfcService将调用dispatchTagEndpoint做进一步处理。 >[info] 提示 findAndReadNdef的实现和具体的NFC芯片有关,而NXP公司的实现函数在NativeNfcTag类中,内容比较复杂,感兴趣的读者可以阅读。 **②、dispatchTagEndpoint流程** 代码如下。 **NfcService.java::NfcServiceHandler.dispatchTagEndpoint** ~~~ private void dispatchTagEndpoint(TagEndpoint tagEndpoint) { // 构造一个Tag对象。前面的示例中已经见过Tag。对客户端来说,它代表目标NFC Tag Tag tag = new Tag(tagEndpoint.getUid(), tagEndpoint.getTechList(), tagEndpoint.getTechExtras(), tagEndpoint.getHandle(), mNfcTagService); registerTagObject(tagEndpoint);// 保存此tagEndpoint对象 // mNfcDispatcher的类型是NfcDispather,调用它的dispatchTag函数来分发Tag if (!mNfcDispatcher.dispatchTag(tag)) {......} } ~~~ **NfcDispatcher.java::dispatchTag** ~~~ public boolean dispatchTag(Tag tag) { NdefMessage message = null; Ndef ndef = Ndef.get(tag);// 构造一个Ndef对象,Ndef属于Tag Technology的一种 /* 从Ndef获取目标Tag中的NDEF消息。如果目标Tag中保存的是系统支持的NDEF消息,则message不为空。 特别注意:在前面代码中见到的findAndReadNdef函数内部已经根据表8-11进行了相关处理。 */ if (ndef != null) message = ndef.getCachedNdefMessage(); PendingIntent overrideIntent; IntentFilter[] overrideFilters; String[][] overrideTechLists; // ①构造一个DispatchInfo对象,该对象内部有一个用来触发Activity的Intent DispatchInfo dispatch = new DispatchInfo(mContext, tag, message); synchronized (this) { // 下面三个变量由前台分发系统相关的NfcAdapter enableForegroundDispatch函数设置 overrideFilters = mOverrideFilters; overrideIntent = mOverrideIntent; overrideTechLists = mOverrideTechLists; } // 恢复App Switch,详情可参考《深入理解Android:卷Ⅱ》6.3.3节关于resume/stopAppSwitches的介绍 resumeAppSwitches(); // 如果前台Activity启用了前台分发功能,则只需要处理前台分发相关工作即可 if (tryOverrides(dispatch, tag, message, overrideIntent, overrideFilters, overrideTechLists)) return true; // 处理Handover事件 if (mHandoverManager.tryHandover(message)) return true; // ②下面是Tag分发系统的处理,首先处理ACTION_NDEF_DISCOVERED if (tryNdef(dispatch, message)) return true; // 如果tryNdef处理失败,则接着处理ACTION_TECH_DISCOVERED if (tryTech(dispatch, tag)) return true; // 如图tryTech处理失败,则处理ACTION_TAG_DISCOVERED // 设置DispatchInfo对象的内部Intent对应的ACTION为ACTION_TAG_DISCOVERED dispatch.setTagIntent(); // 首先从PackageManagerService查询对ACTION_TAG_DICOVERED感兴趣的Activity,如果有则启动它 if (dispatch.tryStartActivity()) return true; return false; } ~~~ 上述代码中有①②两个重要函数,我们先来看第一个。 **NfcDispatcher.java::DispatchInfo构造函数** ~~~ public DispatchInfo(Context context, Tag tag, NdefMessage message) { // 这个Intent的内容将派发给NFC的客户端 intent = new Intent(); /* 不论最终Intent的Action是什么,NFC系统模块都会将tag对象和tag的ID包含在Intent中传递 给客户端。 */ intent.putExtra(NfcAdapter.EXTRA_TAG, tag); intent.putExtra(NfcAdapter.EXTRA_ID, tag.getId()); // 如果NDEF消息不为空,则把它也保存在Intent中 if (message != null) { intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[] {message}); ndefUri = message.getRecords()[0].toUri(); ndefMimeType = message.getRecords()[0].toMimeType(); } else { ndefUri = null; ndefMimeType = null; } /* rootIntent用来启动目标Activity。NfcRootActivity是Nfc.apk中定义的一个Activity。 目标Activity启动的过程如下。NFC系统模块先启动NfcRootActivity,然后再由NfcRootActivity 启动目标Activity。由于NfcRootActivity设置了启动标志(FLAG_ACTIVITY_NEW_TASK和 FLAG_ACTIVITY_CLEAR_TASK),所以目标Activity将单独运行在一个Task中。关于ActivityManager 这部内容,感兴趣的读者可阅读《深入理解Android:卷Ⅱ》第6章。 */ rootIntent = new Intent(context, NfcRootActivity.class); // 将Intent信息保存到rootIntent中。 rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intent); rootIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); this.context = context; packageManager = context.getPackageManager(); } ~~~ 接着来看第二个关键函数tryNdef,代码如下所示。 **NfcDispatcher.java::tryNdef** ~~~ boolean tryNdef(DispatchInfo dispatch, NdefMessage message) { if (message == null) return false; /* setNdefIntent: 设置Dispatcher内部intent对象的Action为ACTION_NDEF_DISCOVERED。 如果Dispatch对象的ndefUri和ndefMimeType都为null,则函数返回null。 */ Intent intent = dispatch.setNdefIntent(); if (intent == null) return false; // 如果message中包含了AAR信息,则取出它们。AAR信息就是应用程序的包名 List aarPackages = extractAarPackages(message); for (String pkg : aarPackages) { dispatch.intent.setPackage(pkg); /* tryStartActivity先检查目标Activity是否存在以及目标Activity的IntenFiltert 是否匹配intent。注意,下面这个tryStartActivity没有参数。 */ if (dispatch.tryStartActivity()) return true; } // 上面代码对目标Activity进行了精确匹配,如果没有找到,则尝试启动AAR指定的应用程序 if (aarPackages.size() > 0) { String firstPackage = aarPackages.get(0); PackageManager pm; /* 下面这段代码用于启动目标应用程序的某个Activity,由于AAR只是指定了应用程序的包名而没有指定 Activity,所以getLaunchIntentForPackage将先检查目标应用程序中是否有Activity的Category 为CATEGORY_INFO或CATEGORY_LAUNCHER,如果有则启动它。 */ UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser()); pm = mContext.createPackageContextAsUser("android", 0,currentUser).getPackageManager(); Intent appLaunchIntent = pm.getLaunchIntentForPackage(firstPackage); if (appLaunchIntent != null && dispatch.tryStartActivity(appLaunchIntent)) return true; /* 如果上述处理失败,则获得能启动应用市场去下载某个应用程序的的Intent,该Intent的 Data字段取值为“market:// details?id=应用程序包名”。 */ Intent marketIntent = getAppSearchIntent(firstPackage); if (marketIntent != null && dispatch.tryStartActivity(marketIntent)) return true; } // 处理没有AAR的NDEF消息 dispatch.intent.setPackage(null); if (dispatch.tryStartActivity()) return true; return false; } ~~~ **③、NFC Tag处理流程总结** NFC Tag的处理流程还算简单,下面总结其中涉及的重要函数调用,如图8-38所示。 :-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/09824e1d0a1f071ab1e59f4c1bdbff8f_999x692.jpg) 图8-38 NFC Tag处理流程 NFC Tag的处理流程比较简单,其中有的代码逻辑比较复杂,这部分内容主要集中在NativeNfcTag的findAndReadNdef函数中。它和具体NFC芯片厂商的实现有关,故把它留给感兴趣的读者自己来研究。 下面我们将研究Android Beam的工作流程。 **3、Android Beam工作流程分析** 当本机检测到某个NFC设备进入有效距离并且能处理LLCP协议后,将通过notifyLlcpLinkActivation通知我们。本节就从这个函数开始分析。 **①、notifyLlcpLinkActivation流程** notifyLlcpLinkActivation代码如下所示。 **NativeNfcManager.java::notifyLlcpLinkActivation** ~~~ private void notifyLlcpLinkActivation(NativeP2pDevice device) { // mListener指向NfcService // 它的onLlcpLinkActivated函数将发送一个MSG_LLCP_LINK_ACTIVATION消息 mListener.onLlcpLinkActivated(device); } ~~~ MSG_LLCP_LINK_ACTIVATION消息由NfcService的内部类NfcServiceHandler处理,它将调用llcpActivated函数,代码如下所示。 **NfcService.java::NfcServiceHandler:llcpActivated** ~~~ private boolean llcpActivated(NfcDepEndpoint device) { /* NfcDepEndpoint代表对端设备,其真实类型是NativeP2pDevice。不论对端设备是Target还是 Initiator,P2pLinkManager的onLlcpActivated函数都将被调用。 */ if (device.getMode() == NfcDepEndpoint.MODE_P2P_TARGET) { if (device.connect()) {// 如果对端是Target,则需要连接上它 if (mDeviceHost.doCheckLlcp()) { if (mDeviceHost.doActivateLlcp()) { synchronized (NfcService.this) { mObjectMap.put(device.getHandle(), device); } mP2pLinkManager.onLlcpActivated(); return true; }...... } else if (device.getMode() == NfcDepEndpoint.MODE_P2P_INITIATOR) { if (mDeviceHost.doCheckLlcp()) { if (mDeviceHost.doActivateLlcp()) { synchronized (NfcService.this) { mObjectMap.put(device.getHandle(), device); } mP2pLinkManager.onLlcpActivated(); return true; }...... } return false; } } } } ~~~ P2pLinkManager的onLlcpActivated函数代码如下所示。 **P2pLinkManager.java::onLlcpActivated** ~~~ public void onLlcpActivated() { synchronized (P2pLinkManager.this) { ..... switch (mLinkState) { case LINK_STATE_DOWN:// 如果之前没有LLCP相关的活动,则mLinkState为LINK_STATE_DOWN mLinkState = LINK_STATE_UP; mSendState = SEND_STATE_NOTHING_TO_SEND; /* mEventListener指向P2pEventManager,它的onP2pInRange函数中将播放 通知音以提醒用户,同时它还会通过SendUi截屏。请读者自行阅读该函数。 */ mEventListener.onP2pInRange(); prepareMessageToSend();// ①准备发送数据 if (mMessageToSend != null || (mUrisToSend != null && mHandoverManager.isHandoverSupported())) { mSendState = SEND_STATE_NEED_CONFIRMATION; // ②显示提示界面,读者可参考图8-29的左图 mEventListener.onP2pSendConfirmationRequested(); } break; ...... } } } ~~~ onLlcpActivated有两个关键函数,我们先来看第一个函数prepareMessageToSend,其代码如下所示。 **P2pLinkManager.java::prepareMessageToSend** ~~~ void prepareMessageToSend() { synchronized (P2pLinkManager.this) { ...... // 还记得NFC P2P模式示例程序吗?可通过setNdefPushMessageCallback设置回调函数 if (mCallbackNdef != null) { try { mMessageToSend = mCallbackNdef.createMessage();// 从回调函数那获取要发送的数据 /* getUris和NfcAdapter的setBeamPushUrisCallback函数有关,它用于发送file或 content类型的数据。由于这些数据需要借助Handover技术,请读者自己来分析。 */ mUrisToSend = mCallbackNdef.getUris(); return; } ...... } // 如果没有设置回调函数,则系统会尝试获取前台应用进程的信息 List tasks = mActivityManager.getRunningTasks(1); if (tasks.size() > 0) { // 获取前台应用进程的包名 String pkg = tasks.get(0).baseActivity.getPackageName(); /* 应用程序可以在其AndroidManifest中设置“android.nfc.disable_beam_default” 标签为false,以阻止系统通过Android Beam来传递与该应用相关的信息。 */ if (beamDefaultDisabled(pkg)) mMessageToSend = null; else { /* createDefaultNdef将创建一个NDEF消息,该消息包含两个NFC Record。 第一个NFC Record包含URI类型的数据,其内容为“http://play.google.com/store/apps /details?id=应用程序包名&feature=beam”。 第二个NFC Record为AAR类型,其内容为应用程序的包名。 */ mMessageToSend = createDefaultNdef(pkg);// 包名为pkg的应用程序 } }else mMessageToSend = null; } } ~~~ prepareMessageToSend很有意思,其主要工作如下。 * 如果设置回调对象,系统将从回调对象中获取要发送的数据。 * 如果没有回调对象,系统会获取前台应用程序的包名。如果前台应用程序禁止通过 Android Beam分享信息,则prepareMessageToSend直接返回,否则它将创建一个包含了两个NFC Record的NDEF消息。 接下来看第二个关键函数onP2pSendConfirmationRequested,其代码如下所示。 **P2pEventManager.java::onP2pSendConfirmationRequested** ~~~ public void onP2pSendConfirmationRequested() { // 对于拥有显示屏幕的Android设备来说,mSendUi不为空 if (mSendUi != null) mSendUi.showPreSend();// 显示类似图8-29左图所示的界面以提醒用户 else mCallback.onP2pSendConfirmed(); /* 对于那些没有显示屏幕的Android设备来说,直接调用onP2pSendConfirmed。mCallback指向 P2pLinkManager。待会将分析这个函数。 */ } ~~~ 在onP2pSendConfirmationRequested函数中,SendUi的showPreSend函数将绘制一个通知界面,如图8-29所示。至此,notifyLlcpLinkActivation的工作完毕。在继续分析Android Beam之前,先来总结notifyLlcpLinkActivation的工作流程,如图8-39所示。 :-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/c820becf4a17a05a238748b30efb3c92_1287x483.jpg) 图8-39 notifyLlcpLinkActivation流程 **②、Beam数据发送流程** SendUi界面将提醒用户触摸屏幕以发送数据,触摸屏幕这一动作将导致SendUi的onTouch函数被调用,其代码如下所示。 **SendUi.java::onTouch** ~~~ public boolean onTouch(View v, MotionEvent event) { ...... mCallback.onSendConfirmed();// mCallback指向P2pEventManager return true; } ~~~ **P2pEventManager.java::onSendConfirmed** ~~~ public void onSendConfirmed() { if (!mSending) { if (mSendUi != null) mSendUi.showStartSend();// 显示数据发送动画 mCallback.onP2pSendConfirmed();// mCallback指向P2pLinkManager } mSending = true; } ~~~ **P2pLinkManager.java::onP2pSendConfirmed** ~~~ public void onP2pSendConfirmed() { synchronized (this) { ...... mSendState = SEND_STATE_SENDING; if (mLinkState == LINK_STATE_UP) sendNdefMessage();// 关键函数 } } ~~~ sendNefMessage将创建一个类型为SendTask的AsyncTask实例以处理数据发送相关的工作,我们来看这个SendTask,相关代码如下所示。 **P2pLinkManager.java::SendTask:doInBackground** ~~~ final class SendTask extends AsyncTask { public Void doInBackground(Void... args) { NdefMessage m; Uri[] uris; boolean result; m = mMessageToSend; uris = mUrisToSend; // 设置要发送的数据 long time = SystemClock.elapsedRealtime(); try { int snepResult = doSnepProtocol(mHandoverManager, m, uris, mDefaultMiu, mDefaultRwSize); ...... } catch (IOException e) { // 如果使用SNEP发送失败,则将利用NPP协议再次尝试发送 if (m != null) // 请读者自行研究和NPP相关的代码 result = new NdefPushClient().push(m); ...... } time = SystemClock.elapsedRealtime() - time; if (result) onSendComplete(m, time);// 发送完毕。请读者自行阅读该函数 return null; } } ~~~ 上述代码中的doSnepProtocol函数内容如下。 **P2pLinkManager.java::SendTask:doSnepProtocol** ~~~ static int doSnepProtocol(HandoverManager handoverManager, NdefMessage msg, Uri[] uris, int miu, int rwSize) throws IOException { // 创建一个SnepClient客户端 SnepClient snepClient = new SnepClient(miu, rwSize); try { snepClient.connect(); // ①连接远端设备的SnepServer } ...... try { if (uris != null) {// 如果uris不为空,需要使用HandoverManager,这部分内容请读者自行阅读 ...... } else if (msg != null) { snepClient.put(msg);// ②利用SNEP的PUT命令发送数据 } return SNEP_SUCCESS; } catch ...... finally { snepClient.close(); } return SNEP_FAILURE; } ~~~ 重点介绍SnepClient的connect函数以及put函数。connect函数的代码如下所示。 **SnepClient.java::connect** ~~~ public void connect() throws IOException { ...... LlcpSocket socket = null; SnepMessenger messenger; // SnepMessenger用于处理数据发送和接收 try { socket = NfcService.getInstance().createLlcpSocket(0, mMiu, mRwSize, 1024); if (mPort == -1) socket.connectToService(mServiceName);// 通过服务名来连接服务端 else socket.connectToSap(mPort); // 通过SAP连接服务端 // 获取远端设备的MIU int miu = socket.getRemoteMiu(); int fragmentLength = (mFragmentLength == -1) ? miu : Math.min(miu, mFragmentLength); messenger = new SnepMessenger(true, socket, fragmentLength); } ...... ...... } ~~~ put函数代码如下所示。 **SnepClient.java::put** ~~~ public void put(NdefMessage msg) throws IOException { SnepMessenger messenger; messenger = mMessenger; synchronized (mTransmissionLock) { try { /* 获取SNEP PUT命令对应的数据包,然后发送出去。SnepMessenger的sendMessage 函数将完成具体的发送工作。 */ messenger.sendMessage(SnepMessage.getPutRequest(msg)); messenger.getMessage(); }..... } } ~~~ 图8-40总结了本节所述的Beam数据发送流程。 :-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/79044d4af1daa7f1c7f9720b4045d5aa_983x562.jpg) 图8-40 Beam数据发送流程 接着来看Beam数据接收流程。 **③、Beam数据接收流程** 8.3.2节中曾介绍,SnepServer每接收一个客户端的连接后均会创建一个ConnectionThread,它的代码如下所示。 **SnepServer.java::ConnectionThread** ~~~ private class ConnectionThread extends Thread { private final LlcpSocket mSock; private final SnepMessenger mMessager; ConnectionThread(LlcpSocket socket, int fragmentLength) { super(TAG); mSock = socket; // 也创建一个SnepMessenger用来处理具体的数据接收 mMessager = new SnepMessenger(false, socket, fragmentLength); } public void run() { ......// 省略一些try/catch逻辑 while (running) { if (!handleRequest(mMessager, mCallback)) break; ...... } mSock.close(); } } ~~~ **SnepServer.java::handleRequest** ~~~ static boolean handleRequest(SnepMessenger messenger,          Callback callback) throws IOException { SnepMessage request; request = messenger.getMessage(); ...... if (((request.getVersion() & 0xF0) >> 4) != SnepMessage.VERSION_MAJOR) { messenger.sendMessage(SnepMessage.getMessage( SnepMessage.RESPONSE_UNSUPPORTED_VERSION)); } else if (request.getField() == SnepMessage.REQUEST_GET) { // 处理GET命令,callback类型为SnepServer的内部类Callback messenger.sendMessage(callback.doGet(request.getAcceptableLength(), request.getNdefMessage())); } else if (request.getField() == SnepMessage.REQUEST_PUT) { // 处理PUT命令 messenger.sendMessage(callback.doPut(request.getNdefMessage())); } ..... return true; } ~~~ 来看SnepServer.callback的doPut函数,它由P2pLinkManager的内部类实现,代码如下所示。 **P2pLinkManager.java::SnepServer.Callback** ~~~ final SnepServer.Callback mDefaultSnepCallback = new SnepServer.Callback() { public SnepMessage doPut(NdefMessage msg) { onReceiveComplete(msg); return SnepMessage.getMessage(SnepMessage.RESPONSE_SUCCESS); } } ~~~ onReceiveComplete的代码如下所示。 **P2pLinkManager.java::onReceiveComplete** ~~~ void onReceiveComplete(NdefMessage msg) { // 发送一个MSG_RECEIVE_COMPLETE消息 mHandler.obtainMessage(MSG_RECEIVE_COMPLETE, msg).sendToTarget(); } ~~~ MSG_RECEIVE_COMPLETE消息由P2pLinkManager的handleMessge处理,相关代码逻辑如下所示。 **P2pLinkManager.java::** ~~~ public boolean handleMessage(Message msg) { switch (msg.what) { ...... case MSG_RECEIVE_COMPLETE: NdefMessage m = (NdefMessage) msg.obj; synchronized (this) { ...... mSendState = SEND_STATE_NOTHING_TO_SEND; mEventListener.onP2pReceiveComplete(true);// 取消本机显示的SendUi界面 // sendMockNdefTag将发送一个MSG_MOCK_NDEF消息给NfcService的NfcServiceHandler NfcService.getInstance().sendMockNdefTag(m); } break; ..... } } ~~~ **NfcService.java::NfcServiceHandler:handleMessage** ~~~ final class NfcServiceHandler extends Handler { public void handleMessage(Message msg) { switch (msg.what) { case MSG_MOCK_NDEF: { NdefMessage ndefMsg = (NdefMessage) msg.obj; Bundle extras = new Bundle(); extras.putParcelable(Ndef.EXTRA_NDEF_MSG, ndefMsg); extras.putInt(Ndef.EXTRA_NDEF_MAXLENGTH, 0); extras.putInt(Ndef.EXTRA_NDEF_CARDSTATE, Ndef.NDEF_MODE_READ_ONLY); extras.putInt(Ndef.EXTRA_NDEF_TYPE, Ndef.TYPE_OTHER); // 创建一个模拟Tag对象 Tag tag = Tag.createMockTag(new byte[] { 0x00 }, new int[] { TagTechnology.NDEF },new Bundle[] { extras }); // 直接通过分发系统分发这个Tag boolean delivered = mNfcDispatcher.dispatchTag(tag); ...... break; } ...... } } } ~~~ Beam接收端的处理流程比较巧妙,系统将创建一个模拟的Tag对象,然后利用分发系统来处理它。图8-41总结了Beam接收端的处理流程。 :-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/009b6118f851b65bedd3843b464e16ba_1008x590.jpg) 图8-41 Beam数据接收流程 **4、CE模式** NFC系统模块对CE模式的支持集中在以下两点。 * 通过NfcAdapterExtras为CE模式的客户端提供INfcAdapterExtras功能实现。这部分内容所涉及的调用流程比较简单,请读者自行研读。当然,我们略过了和芯片相关的部分。 * 当NFC设备进入CE模式后,和它交互的另一端是诸如NFC Reader这样的设备。此时对端发起一些操作,而NativeNfcManagement的一些回调函数将被触发。下面代码展示了和CE相关的这些回调函数。 **NativeNfcManager.java::CE相关回调函数** ~~~ // 根据NFCIP-1协议,该函数表示对端设备发送了DESELECT命令给我们。它属于deactivation阶段的命令 private void notifyTargetDeselected() {// mListern指向NfcService mListener.onCardEmulationDeselected(); } /* 用于通知SE上某个应用程序开始和对端设备进行交互。AID为Application ID的缩写,它代表SE上 的某个应用程序(称为Applet)。 */ private void notifyTransactionListeners(byte[] aid) { mListener.onCardEmulationAidSelected(aid); } // 用于通知SE模块被激活 private void notifySeFieldActivated() { mListener.onRemoteFieldActivated(); } // 用于通知SE模块被禁止 private void notifySeFieldDeactivated() { mListener.onRemoteFieldDeactivated(); } // 收到对端设备的APDU命令 private void notifySeApduReceived(byte[] apdu) { mListener.onSeApduReceived(apdu); } /* EMV是EMV标准是由国际三大银行卡组织Europay(欧陆卡,已被万事达收购)、MasterCard(万事达卡)和 Visa(维萨)共同发起制定的银行卡从磁条卡向智能IC卡转移的技术标准,是基于IC卡的金融支付标准, 目前已成为公认的全球统一标准。下面这个函数用于通知EMV Card进入Removal阶段。 该函数只适用于NXP公司的相关芯片。 */ private void notifySeEmvCardRemoval() { mListener.onSeEmvCardRemoval();// NfcService如何处理它呢 } // 用于通知MIFARE SMX Emulation被外部设备访问。该函数只适用于NXP公司的相关芯片 private void notifySeMifareAccess(byte[] block) { mListener.onSeMifareAccess(block); } ~~~ NfcService是如何处理这些回调通知的呢?以notifySeEmvCardRemoval中的onSeEmvCardRemoval为例,NfcService将发送一个MSG_SE_EMV_CARD_REMOVAL消息,而这个消息的处理函数代码如下所示。 **NfcService.java::NfcServiceHandler:handleMessage** ~~~ ...... case MSG_SE_EMV_CARD_REMOVAL:  // CE相关的消息全是类型的处理方式 Intent cardRemovalIntent = new Intent(); cardRemovalIntent.setAction(ACTION_EMV_CARD_REMOVAL); sendSeBroadcast(cardRemovalIntent); // 发送广播 ...... ~~~ 由上述代码的注释可知,NfcService对CE相关的处理非常简单,就是接收来自底层的通知事件,然后将其转化为广播事件发送给系统中感兴趣的应用程序。 **5、Android中的NFC总结** 本节介绍了Android平台中NFC系统模块NfcService及其他重要组件。从整体上来说,NfcService以及与底层芯片无关的模块难度不大,而与底层芯片相关的模块则难度较大(位于com.android.nfc.dhimpl包中)。本章没有讨论dhimpl包的具体代码,希望感兴趣的读者能结合芯片手册自行研究它们。 另外,本节还对NFC R/W模式及P2P模块的工作流程进行了相关介绍,这部分难度不大,相信读者能轻松掌握。同时,作为课后作业,请读者在本节基础上自行学习Handover相关的处理流程。 最后,本节简单介绍了NFC CE模式的处理流程,NfcService本身对CE相关的处理比较简单,它仅根据CE相关的操作向系统发送不同的广播,而这些广播则会由感兴趣的应用程序来处理,例如Google Wallet。 >[info] 提示 NFC CE模式其实内容相当复杂,涉及很多规范。笔者会在博客上继续介绍NFC CE相关的知识,敬请读者关注。
';