赞
踩
第8章 深入理解NFC
本章所涉及的源代码文件名及位置
·ForegroundDispatch.java
development/ samples/ ApiDemos/ src/ com/ example/ android/ apis/ nfc/ ForegroundDispatch.java
·Beam.java
development/ samples/ AndroidBeamDemo/ src/ com/ example/ android/ beam/ Beam.java
·NfcService.java packages/ apps/ Nfc/ src/ com/ android/ nfc/ NfcService.java
·P2pLinkManager.java
packages/ apps/ Nfc/ src/ com/ android/ nfc/ P2pLinkManager.java
·NativeNfcManager.java
packages/ apps/ Nfc/ nxp/ src/ com/ android/ nfc/ dhimpl/ NativeNfcManager.java
·P2pEventManager.java
packages/ apps/ Nfc/ src/ com/ android/ nfc/ P2pEventManager.java
·SnepServer.java packages/ apps/ Nfc/ src/ com/ android/ nfc/ SnepServer.java
·NfcDispatcher.java
packages/ apps/ Nfc/ src/ com/ android/ nfc/ NfcDispatcher.java
·SendUi.java packages/ apps/ Nfc/ src/ com/ android/ nfc/ SendUi.java
·SnepClient.java packages/ apps/ Nfc/ src/ com/ android/ nfc/ SnepClient.java
8.1 概述
NFC(Near Field Communication,近场通信)也叫做近距离无线通信技术。该技
术最早由Philips和Sony两家公司于2002年年末联合推出。2004年,Nokia、Philips、
Sony等公司还共同组建了一个名为NFC Forum的非盈利性组织来推广和发展NFC技术。
NFC Forum的职责和W i-Fi Alliance类似,它不但负责制定NFC相关的技术标准,同时还
通过NFC认证测试 ① 来保证各厂家的NFC产品符合NFC规范。
从原理上说,NFC和W i-Fi类似,二者都利用无线射频技术来实现设备之间的通信。不
过,和W i-Fi相比,NFC的工作频率为13.56MHz,有效距离为4cm左右,目前所支持的数
据传输速率有106kbps、212kbps和424kbps三种。
提示 通过NFC无线射频参数的介绍可知,NFC所针对的应用场景和W i-Fi明显不
同。以NFC有效距离为4cm为例,这么短的有效距离本身就要求交互双方必须有某种程度的
相互信任。否则,一个用户不会随便让另外一个用户的设备这么靠近自己的设备。NFC还有
其他非常多的广泛的应用场景,感兴趣的读者请阅读参考资料[1]。
NFC技术从创建到现在已超过10年,在技术层面上已相当完善。但NFC至今未能像
W i-Fi一样被普及,其中一个重要原因就是大众消费者没有一个合适的载体来使用它。显
然,随着越来越多携带NFC功能的Android智能终端的出现,NFC这种有价无市的状况有
望很快得以改善。
提示 很多专家预测2014年或2015年是NFC技术推广和普及的元年。但奇怪的是
iPhone却迟迟没有支持NFC,这不免给它的前景蒙上了一层阴影。不过,最近有消息称苹
果秘密申请了一项和NFC相关的专利。
本章将从以下几个方面来介绍NFC以及它在Android平台中的实现。
·首先介绍NFC基础知识,这是本章的核心内容。相对W i-Fi而言,本章介绍的NFC
理论知识相对比较简单,相信读者能轻松掌握。
·然后介绍Android平台中NFC实现,这部分内容包括NFC客户端示例以及NFC系统
模块。
·最后探讨目前一些开源NFC相关模块的实现情况。
先来看NFC基础知识。
① 关于NFC认证测试,请参考http:/ / www.nfc-forum.org/ certification/ certification-
testing/ 。
8.2 NFC基础知识
简而言之,NFC是从多种不同技术基础上综合发展而来,图8-1展示了NFC技术的演化
历程。
8.2.1 NFC概述
通过图8-1所示的NFC技术演化历程可知,NFC融合了三条主要的技术发展路线 [2] 。
图8-1 NFC技术演化历程
·RFID技术路线,即无线射频识别技术(图左边)。该技术路线发源于条形码
(Barcodes),然后发展出了RFID,最终出现了NFC中的两个重要组件NFC Tag(标
签)和NFC Reader。NFC Tag的作用和Barcodes类似,它是一种用于存储数据的被动式
(Passive)RFID Tag,其最重要的特征就是NFC Tag自身不包含电源组件,所以它工作
时必须依靠其他设备(比如NFC Reader)通过电磁感应的方式向其输送电能。和NFC Tag
相对应的组件是NFC Reader,它首先通过电磁感应向NFC Tag输送电能使其工作,然后根
据相关的无线射频通信协议来存取NFC Tag上的数据。
·磁条卡(Magnetic Strip Cards)技术路线(图右边)。该路线最终演化了NFC使
用的Proximity Coupling Smart Card技术(有效距离为10cm,对应的规范为ISO/ IEC
14443。注意,图中的Close Coupling Smart Card的有效距离为1cm,对应的规范为
ISO/ IEC 10536。Vicinity Coupling Smart Card的有效距离为1m,对应的规范为
ISO/ IEC 15693)。粗略来看Smart Card和RFID Tag类似,例如二者都只存储一些数
据,而且自身都没有电源组件,但Smart Card在安全性上的要求远比RFID Tag严格。另
外,Smart Card上还能运行一些小的嵌入式系统(如Java Card OS)或者应用程序
(Applets)以完成更为复杂的工作。
·移动终端线路,演化了携带NFC功能的终端设备(图中间)。随着移动终端越来越智
能,NFC和这些设备也融合得更加紧密,使得NFC的应用场景得到了较大的拓展。本书第6
章在介绍W i-Fi Simple Configuration时(6.1节)曾介绍过一个例子,即智能手机可通过
NFC来和AP交换安全配置信息。一个与之类似的例子是NFC Connection Handover技
术,它描述了两台智能终端如何通过NFC相关协议来选择合适的数据传输方式(例如
Bluetooth或W i-Fi,受限于传输速率以及有效距离,NFC本身不适合大数据量传输)。
了解NFC技术的演化历程之后,我们来看看NFC现在的样子。图8-2所示为NFC技术框
架 [3] 。
图8-2 NFC技术框架
由图8-2可知,从用户角度(即图中的Applications层之上)来看,NFC有三种运行模
式(operation mode)。
·Reader/ W rite模式:简称R/ W ,和NFC Tag/ NFC Reader相关。
·Peer-to-Peer模式:简称P2P,它支持两个NFC设备交互。
·NFC Card Emulation模式:简称CE,它能把携带NFC功能的设备模拟成Smart
Card,这样就能实现诸如手机支付、门禁卡之类的功能。
Application之下的三个箭头描述了三种运行模式所使用的协议栈。这部分内容将留待
下文分析。
NFC使用的是无线射频技术。在RF层,与之相关的规范是ISO 18092(NFC
Interface and Protocol I,简称NFCIP-1,该规范定义了NFC RF层的工作流程)和ISO
14443 Type A、Type B,以及FeliCa。
ISO 14443全称为非接触式IC卡标准,它从RF层面定义了如何与不同的非接触式IC卡
(其实物可以是NFC Tag、RFID Tag、Smart Cards)交互。ISO 14443定义了Type A
和Type B两种非接触式IC卡。
·Type A最早由Philips公司制订(其生产的芯片商标名为MIFARE,现在由从
Philips独立出来的NXP公司拥有,目前世界上70% 左右的非接触式IC卡都使用了MIFARE
芯片,例如北京市的公交卡)。
·Type B(主要用在法国市场)由其他公司制订,二者最终都成为ISO标准。
·FeliCa(也称为Type F)由Sony开发,它最终没有成为ISO标准,而成为日本工业
标准JIS X6319-4,所以FeliCa主要用于日本市场。
Type A、B和F主要区别在于RF层的信号调制解调方法、传输速率及数据编码方式上。
关于ISO 14443和FeliCa之间的区别,请读者阅读参考资料[4]。
RF层之上是Mode Switch,用于确定对端NFC Device的类型并选择合适的RF层协议
与之通信。
提示 由于NFC是从多种技术综合发展而来,所以读者在学习NFC时将会碰到很多规
范,如上文所提到的ISO 18092以及ISO 14443、FeliCa等。除了ISO等标准组织制定的规
范外,NFC Forum也制定了一系列的标准和规范。由于篇幅问题,本章仅介绍NFC
Forum定义的一些规范。对ISO相关规范感兴趣的读者可在本章基础之上自行阅读。
图8-3所示为与NFC技术框架相对应的NFC Forum所定义的规范框架 [3] 。
图8-3 NFC Forum规范框架
由图8-3所示的NFC Forum规范框架可知,NFC Forum本身只定义了P2P模式和R/ W
模式相关的规范,规范的细节将留待下文详细介绍。CE模式比较复杂,下文也会讨论和它
相关的一些知识。
在RF层,NFC Forum定义了三个主要规范。
·Analog Specifications:该规范描述了NFC设备RF层的电气特性。
·Digital Protocal Specification:该规范在ISO 18092、ISO 14443及JIS X6319-
4之上定义了NFC设备之间的数字通信协议,它使得基于不同底层协议例如Type A或Type
F的NFC设备之间或者NFC设备与其他使用ISO 18092等规范的设备之间能够交互。
·NFC Activities Specification:该规范为各运行模式对应的协议栈提供支持,例如
P2P模式下两个NFC设备如何建立链接,R/ W 模式下NFC Device如何操作NFC Tag。
图8-3最上层的Reference Applications表示NFC Forum在应用层面所定义的一些规
范。目前有两个规范。
·Connection Handover:两个NFC设备通过它来协商用蓝牙或W i-Fi来开展后续的
数据传输工作。
·Personal Health Device Communication:该规范定义了如何利用NFC技术在个
人健康设备之间交换数据信息。
另外,除了图8-3所示的规范外,NFC还制定了一个NCI(NFC Controller
Interface)规范,该规范制定了一套交互接口,使得主机设备(Device Host,以手机为
例,NFC芯片被集成到某个手机中,那么手机就是Device Host)能够使用这套接口来和
NFC芯片交互。
下面,先讨论NFC三种运行模式,而NCI相关知识将留待本节最后介绍。
提示 关于NFC Forum制定的各种规范及简要说明见参考资料[5]。
8.2.2 NFC R/ W运行模式
以支持NFC功能的智能终端为例,NFC R/ W 运行模式所包含的组件如图8-4 [6] 所示。
图8-4 R/ W 运行模式组件
图8-4展示了一个包含NFC芯片的智能终端与NFC Tag交互所涉及的组件。
·左边的智能终端扮演NFC Reader角色。位于其内部的NFC芯片包含NFC
Controller(NFC控制器,它可与Device Host或Secure Element安全单元交互)、
Antenna(天线)和Contactless Front-End(CLF,非接触式前端,负责射频信号的调
制解调等工作)三个部分。注意,图中所示的SW P等内容将在8.2.4节介绍。
·在R/ W 模式中,交互操作的发起方只能是NFC Reader,因此它也称为Initiator或
Active Device。
·右边的NFC Tag,由于需要NFC Reader通过电磁感应为其提供电能,所以在R/ W 模
式中,NFC Tag只能作为交互操作的Target(也称为Passive Device)。
NFC Forum定义了四种类型的Tag,分别为Type 1、Type 2、Type 3和Type 4。这
四种类型NFC Tag的区别在于存储空间大小、数据传输率以及底层使用的协议。表8-1列举
了它们的不同点。
NFC Forum定义了两个通用的数据结构用于NFC Device之间(包括R/ W 模式中的
NFC Reader和NFC Tag)传递数据。这两个通用数据结构分别是NFC Data Exchange
Format(NDEF)以及NFC Record。
我们先来看NFC四种不同类型的Tag有何区别,如表8-1 [7] 所示。
注意 这里需要特别指出的是:虽然NFC Froum只有四种类型的Tag,但由于NFC本
身源自RFID技术,二者在一些底层协议上也相互兼容,所以很多RFID Tag也能被NFC
Reader识别和操作。为了书写方便,除非特别说明,本章所指的NFC Tag也包括那些和
NFC相关规范兼容的RFID Tag。
虽然NFC Tag有四种不同类型(由上文可知,实际上能被NFC Reader读写的RFID
Tag还远不止四种),但为了保证最大兼容性,NFC Forum建议NFC设备之间尽量使用通
用数据结构NDEF和NFC Record来交换信息。
NFC R/ W 模式涉及的规范比较多,包括:
·NFC Reader如何与不同类型的Tag交互,这部分内容涉及非常底层的一些协议。
·NDEF和一些常用数据类型定义。
出于篇幅和实用性考虑,本书仅介绍NDEF和相关的数据类型,感兴趣的读者可自行研
究NFC Reader和Tag之间的交互协议。
1.NDEF和NFC Record
(1)NDEF和NFC Record [8][9] 之间的关系
根据NFC Forum的定义,R/ W 模式下,NFC设备之间每一次交互的数据都会封装在一
个NDEF Message中,而一个NDEF Message可以包含多个NFC Record,真正的数据则
封装在NFC Record中。图8-5展示了NDEF Message和NFC Record之间的关系。
图8-5 NDEF Message和NFC Record的关系
由图8-5可知,一个NDEF Message可包含一个或多个NFC Record。在一个NDEF
Message中,第一个NFC Record需设置其MB位(Message Begin)为1,表示它是该消
息中第一个NFC Record,最后一个NFC Record需设置ME位(Message End)位为1,表
示它是此消息中最后一个NFC Record。
图8-6 NFC Record组织结构
NFC Record本身的组织结构如图8-6所示。NFC Record分为NFC Record
Header(头部信息)和Payload(数据载荷)两大部分。
Record Header中最重要的是其第一字节。该字节有6个标志信息,分别如下。
·MB(Message Begin标志)
·ME(Message End标志)
·CF(Chunk Flag标志,表示该Record是否为分片Record)
·SR(Short Record标志。如果该标志被设置,则图中的4个Payload Length字段仅
需一个,这表明Payload数据长度将限制在255字节以内)
·IL(ID_LENGTH标志,它用于指明Header中是否包含ID Length和ID这两个字
段)
·TNF(Type Name Format标志,用于指明Payload的类型,NFC Forum定义了一
些常用的Payload类型,详情见下文分析)
其他字节如下。
·Type Length指明Record Header中Type字段的长度。
·Payload Length 3~Payload Length 0这4个字段共同指明Payload字段的长度。
如果SR标志被设置,则Record Header仅包含一个Payload length字段。
·ID Length指明ID字段的长度。如图所示IL标志未设置,则ID Length和ID字段都
不存在。
·Type字段表明Payload的类型,NFC Forum定义了诸如URI、MIME等类型的
Type,其目的是方便不同的应用来处理不同Type的数据,例如URI类型的数据就交给浏览
器来处理。
·ID需要配合URI类型的Payload一起使用,它使得一个NFC Record能通过ID来指向
另外一个NFC Record。
NFC Record中,常令初学者感到困惑的是TNF字段,其作用是什么?来看下文。
(2)TNF和RTD
TNF用于描述一个NFC Record中数据(Payload)的类型,为了方便应用程序能正确
解析NFC Record中的数据,NFC Forum规定了一些常用的数据类型,如表8-2所示。
目前NFC支持七种数据类型。
·Empty:表示该Record中没有数据,即相当于一个空的NFC Record。
·NFC Forum W ell-Known Type:由NFC Forum定义的一些较为常用的数据类
型,包括URI、TEXT等,其格式遵循NFC Forum RTD(Record Type Definition)规
范。下文将详细介绍它。
·MIME:它是Multipurpose Internet Mail Extensions的缩写,遵循RFC2046规
范。例如,当TNF取值为MIME时,其Type字段取值可为"text/ plain"或"image/ png"等。
·Absolute URI:绝对URI,遵循RFC 3986规范。例如某文件的绝对URI
为"http:/ / android.com/ robots.txt",而其相对URI则为"robots.txt"。
·NFC Forum External Type:也由NFC Forum的RTD规范定义,下文将介绍它。
·Unknown:代表Payload中的数据类型未知,它和MIME类型"application/ octet-
stream"有些类似,这种类型的数据由相应的应用程序来解析。
·Unchanged:这种类型的数据用于NFC Record分片。例如一个大的数据需要通过多
个NFC Record来承载,除第一个NFC Record分片外,该数据对应的其他NFC Record分
片都必须设置TNF为Unchanged。关于这部分内容,读者可参考NDEF规范的2.3.3
节"Record Chunks"。
在TNF七大类型中,NFC Forum通过RTD规范定义了其中的W KT(W ell-Known
Type)和External Type两种类型。虽然RTD规范全长只有20来页,但阅读起来比较枯
燥,在此,笔者总结其核心内容。
简单点说,W KT就是NFC Forum自己定义的一些常用数据类型,目前常用类型如下。
·URI Record Type:用于存储URI数据,对应Type字段取值为"U"。
·Text Record Type:用于存储文本数据,对应Type字段取值为"T"。
·Signature Record Type:用于存储数字签名数据,对应Type字段取值为"Sig"。
·Smart Poster Record Type:智能海报,用于存储与该海报相关的一些资讯信息,
如图片、相关介绍等,对应Type字段取值为"Sp"。
·Generic Control Record Type:用于传递控制信息,对应Type字段取值为"Gc"。
·External Type:为第三方组织定义的类型,目前NFC Forum没有定义相关的数据
类型。
提示 NFC Forum目前定义的所有W KT类型列表可参考http:/ / www.nfc-
forum.org/ specs/ nfc_forum_assigned_numbers_register。
掌握了上述理论知识后,下面将通过两个实例来看看NFC Record各个字段到底该如何
设置。
2.NFC Record实例 [10][11]
本节这两个实例分别来自URI Record Type规范和TEXT Record Type规范。先来看
URI Record Type实例。
(1)URI Record Type实例
URI Record Type属于NFC Forum W ell-known Type的一种,其对应的Type字段
取值为"U"。对于这种类型的NFC Record,其Payload组织结构如表8-3所示。
在URI Record Payload中,第一个字节指明URI的ID码,表8-4为NFC Forum定义的
几种ID码。
了解上述信息后,我们来看"http:/ / www.nfc.com"这样的信息该如何封装为一个NDEF
消息,图8-7所示为NDEF消息各字段的取值情况。
图8-7 URI Record实例
由于该NDEF消息只包含一个NFC Record,所以这个唯一的NFC Record将设置MB和
ME标志位为1。另外,由于数据量小于255字节,所以SR标志位为1。最后,该Record携带
的数据属于URI类型,它为W ell-Known Type的一种,所以TNF取值为0x01。
Type Length字段取值为0x01,对应的Type字段取值为"U",代表URI Record
Type。
根据本节对URI Record的介绍,这种类型Record的Payload包含ID Code和data两个
部分。ID Code取值为0x01占据1字节(代表"http:/ / www"),而data为"nfc.com"占据7字
节,所以整个Payload长度为8字节,故Payload length字段取值为0x08。
当应用程序获取Payload信息后,将根据ID Code和Data的取值最终计算出对应的URI
为"http:/ / www.nfc.com"。
相信本节所述的URI Record实例能帮助读者更加直观得了解NDEF和NRC Record,
下面再来看一个实例。
(2)Text Record Type实例
Text Record Type和URI Record Type类似,其Payload组织结构如表8-5所示。
图8-8 TEXT Record实例
图8-8所示为携带"Hello W orld"字符串信息的NDEF消息各字段的取值情况。
实例比较简单,请读者根据本节对TEXT Record知识的介绍来自行解释图8-8各个字
段的取值。
至此,NFC R/ W 运行模式介绍完毕。在R/ W 模式下,对应用程序而言最重要的工作就
是解析NDEF消息。NFC Forum定义了七种数据类型,其中内容比较丰富的属于NFC
Forum W ell Known Type。本节介绍了W KT中最简单的URI Record和TEXT Record。
读者可在本节基础上自行研究其他几种数据类型。
8.2.3 NFC P2P运行模式 [12]
在前面介绍的R/ W 模式中,NFC Device只能单向和NFC Tag交互,即只能NFC
Device单方对NFC Tag发起操作,而NFC所基于的无线射频技术实际上可以支持NFC
Device之间互相传递数据。为了满足NFC Device之间双向交互的需求,NFC Forum定义
了P2P(Peer-to-Peer)运行模式。
图8-9展示了IEEE 802参考模型、OSI参考模型及NFC P2P的协议栈参考模型。由此
图可知,NFC P2P协议栈最高层为LLC(Logical Link Control,逻辑链路控制层)。这
一层使用的协议称为LLCP(LLC Protocol)。
在OSI参考模型中,LLC比较偏底层,其更多考虑的是物理地址寻址、链路管理,以及
数据传输方面的事情(参考3.3.1节关于OSI/ RM的介绍)。所以,NFC也在LLC层之上添
加了一些对使用者更为方便和友好的协议。图8-10所示为NFC P2P协议栈的全貌。
图8-9 NFC P2P协议栈
图8-10 NFC P2P协议栈参考模型全貌
·SNEP(Simple NDEF Exchange Protocol)紧接LLC层。该协议使得两个NFC
Device之间能直接交换NDEF消息。
·通过Protocol Bindings,NFC可支持其他高层次并且用途更加广泛的协议。根据参
考资料[3]所示的内容,NFC可支持IP和OBEX(Object Exchange,对象交换)协议,但
经过调查发现NFC Forum官网目前只有LLCP-OBEX-Binding协议的草案,而LLCP和IP
协议如何绑定还在研究当中。
·Other Protocols中目前比较常用的是CHP(Connection Handover Protocol)。
目前,Android 4.2中的NFC P2P模块支持SNEP和CHP。本章将重点分析SNEP,而
CHP则请读者学完本章后再自行研究。下面先介绍LLCP,然后再介绍SNEP。
1.LLCP介绍
NFC LLCP比较简单,对应的规范全长也只有40来页。关于LLCP,从以下两个方面来
介绍。
·LLCP的数据封包格式。对学习通信协议来说,掌握数据包格式非常重要。
·NFC LLCP对上层提供无链接(Connectionless)和面向链接(Connection-
oriented)的两种数据传输服务。其中,无链接的数据传输服务和UDP类似,上层的收发
双方无须事先建立逻辑链接关系即可收发数据。面向链接的数据传输服务和TCP类似,收发
双方发送数据前,需要在LLC层先建立逻辑链接关系(即类似TCP协议中的connect和
accept)。同时,LLC层还会处理数据包丢失、重传以及接收确认等方面的事情。目前
SNEP和CHP均使用了LLC提供的面向链接的数据传输服务,故我们将重点介绍它。
(1)LLCP数据包格式
NFC LLC层数据封包格式如图8-11所示。
图8-11 NFC LLCP数据包格式
由图8-11可知,LLCP数据包前3字节为LLCP Header。LLCP Header之后就是
Payload,其长度由PTYPE来决定。
·DSAP和SSAP分别代表Destination和Source Service Access Point(目标和源服
务接入点)。DSAP和SSAP的作用类似于TCP/ UDP中的端口号。注意,使用NFC LLCP
时,DSAP和SSAP可唯一确定通信双方。读者可能有疑问,使用TCP/ UDP时,除了指明端
口号外,还需要指明对端设备的IP地址,但NFC LLCP数据包中却没有这样的信息。另
外,和图3-24所示的LLC数据封装格式比起来,NFC LLCP数据包也没有MAC地址这样的
字段。也就是说,LLCP只需要通信双方所使用的端口号即可,而无需MAC或IP地址这样
的信息,这是因为NFC近距离作用的特点使得通信双方从进入有效距离内开始就已彼此确
定,故无需再通过MAC地址指明谁是接收设备,谁是发送设备。而当上层通过LLC发送数
据或者LLC向上层传递接收到的数据时则需要通过类似端口这样的SSAP和DSAP来进一步
确定发送模块和接收模块到底是谁。
·PTYPE字段指明LLCP包的类型。NFC LLCP定义了多种不同类型的包,下文将结
合面向链接的数据传输服务来学习相关的LLCP包。
·Sequence字段指明LLCP包的序号,它可分为Send端和Receiver端。由于有一些类
型的LLCP包无需Sequence字段,所以Sequence字段长度有可能为0。例如,无链接的数
据包就不需要Sequence字段,而面向链接的数据包需要该字段来处理数据接收确认或丢失
重传等方面的事情。
由上述介绍可知,DSAP和SSAP类似TCP/ UDP的端口号,决定了收发模块到底是谁。
表8-6所示为NFC中SAP取值情况。
以SNEP的使用为例:
·位于NFC Device A的服务端模块在SSAP为0x04的端口上进行监听。
·位于NFC Device B的客户端模块选择一个合适的SSAP,设置DSAP为0x04。然后
该客户端模块发送数据包,LLC负责将数据包打包传递给NFC Device B(假设这两个设备
都在彼此的有效距离内)。
·NFC Device A的LLC层接收到数据包后发现DSAP为0x04,而其上刚好有一个服务
模块工作在0x04端口,故LLC层将把数据包传递给这个在0x04端口上监听的服务模块。
下面将通过分析面向链接数据传输服务的工作流程来进一步研究LLCP。
(2)面向链接数据传输服务
假设Device A和Device B打开了NFC功能。当二者进入有效距离后,它们的LLC模块
将进入Link Activation(链路激活)阶段,在此阶段中,A和B的交互过程如图8-12所
示。
图8-12 Link Activation工作流程
·进入Link Activation时,Device A和Device B将分别扮演Initiator和Target角
色,参考资料[13]可用于确定谁来扮演Initiator或Target。
·Initiator发送PAX数据包给Target。PAX全称为Parameter Exchange,它用于在
两个设备间交换彼此的LLC层配置信息(如协议版本等,详情见下文)。
·Target收到Initiator的PAX包后需要相应处理,例如判断协议版本是否匹配等。
Target处理完后,它需要发送自己的LLC层配置信息给Initiator。
·Initiator检查Target的LLC层配置参数,如果一切正常,双方Logical Link成功建
立,随后可进入正常工作阶段。
根据上述内容,双方需要通过PAX交换LLC层的配置信息。PAX属于LLCP数据包的一
种,其格式如图8-13所示。
图8-13 PAX数据包
LLC层的配置信息保存在图8-13中的参数列表中,正常情况下PAX携带的参数信息及
作用如表8-7所示。(注意,并非所有参数都会包含在图8-13的参数列表中。)
以上知识有一些细节需要读者注意。
·当Device A和Device B进入有效距离后,Link Activation将被触发,而Device A
和B分开后,Link Deactivation将被触发。从使用角度来看,Link
Activation/ Deactivation可能会频繁被触发。
·W KS用于告知本机设备哪些W ell-Known服务端口上有模块在监听。这表明在Link
Activation被触发前,使用者就必须在感兴趣的W KS端口上进行监听。这和笔者之前所认
为的设备先进入Link Activation,然后再监听W KS端口不同。以后分析Android平台中
SNEP的代码时读者将看到相关的处理。
Link被激活后,Device A和Device B将先建立面向链接的关系,然后再开展数据交
互,这一流程如图8-14所示。
图8-14 面向链接工作流程
假设Device A扮演Client角色,Device B扮演Server角色。Client先通过CONNECT
包向Server发起链接请求。CONNECT包对应的格式如图8-15所示。
图8-15 CONNECT包格式
CONNECT包需要携带一些参数信息,常见的参数如表8-8所示。
当服务器端成功处理CONNECT包后,它将回复CC(Connection Complete)包给
客户端。如此,Client和Server就建立了链接关系。CC包内容非常简单,请读者自行研究
参考资料[12]。此后,Client和Server就可通过Information(规范中简称为I)包和
RR(Receive Ready)包来传输数据,其中:
·I包用于承载具体的数据。
·RR包用来确认接收方确实收到了数据。
I和RR包比较简单,这部分内容也请读者自行研究参考资料[12]。
总体而言,LLCP比较简单,不过直接使用LLCP还是稍显复杂。所以,在LLCP基础
上,NFC Forum定义了SNEP协议用于在两个NFC Device之间传输NDEF消息。下面来学
习SNEP。
2.SNEP介绍 [14]
SNEP(Simple NDEF Exchange Protocol)支持在两个NFC Device之间交换
NDEF消息。SNEP是一种基于面向链接的数据传输协议,作为W ell-Known Service的一
种,其服务端口号为0x04,服务名为"urn:nfc:sn:snep"。
SNEP属于Request/ Response方式,其工作过程如图8-16所示。
图8-16 SNEP工作方式
SNEP的工作流程非常简单,主要包括两个步骤。
1)SNEP客户端发送SENP Request消息给服务端进行处理。
2)SNEP服务端回复SNEP Response消息给客户端以告知处理结果。
SNEP Request消息和Response消息的格式如图8-17所示。
图8-17 SNEP Request/ Response消息格式
图8-17中,SNEP Request/ Response消息开头都是1字节的Version字段,Version字
段之后分别是Request字段和Response字段。Request字段表示请求类型,Response字段
表示处理结果。
表8-9所示为SNEP当前支持的Request类型。
以SNEP Put请求消息为例,其对应的格式如图8-18所示。
图8-18 SNEP Put消息格式
SNEP协议本身非常简单,此处不详细介绍,本章下文将结合代码来介绍Android中
SNEP的实现。
至此,我们对NFC LLCP进行了相关介绍。这部分难度不大,读者需要重点掌握的部分
包括LLCP协议本身,尤其是其数据封包格式、各种参数信息、常见SAP等。在LLCP基础
上,读者可学习SNEP这种比较常用的协议。另外,读者还可在本节基础上自行学习
Connection Handover,它是另外一种基于LLCP面向链接数据传输服务的协议。
提示 以后在分析Android NFC模块代码时也会碰到Connection Handover,请读者
自己来分析。
下面来看NFC运行模式中最后一种,即NFC CE(Card Emulation)模式。
8.2.4 NFC CE运行模式 [15][16]
NFC CE运行模式使得携带NFC芯片的设备能充当智能卡(例如信用卡)使用。该运行
模式所支持的应用场景极具吸引力,例如用支持该功能的Android智能手机来完成购票、支
付,甚至充当门禁卡,汽车钥匙、公交卡等。
图8-19为CE运行模式示意图。
图8-19 CE运行模式
由图8-19可知,SE和NFC芯片(主要是指NFC Controller,简称NFCC)通过
SW P(Single W ire Protocol)或者S 2 C(SignalIn/ SignalOut Connection
Interface,也叫NFC W ired Interface,简称NFC-W I)来交互。一般来说,SE上面运行
了一些特殊的应用程序,NFC负责将数据通过SW P或S 2 C传递给SE中的应用来处理。
NFCC通过HCI协议和NFC Mobile交互,而SE也可通过ISO 7816协议和NFC
Mobile交互。
在CE模式中,NFC Mobile被NFC Reader识别成一个智能卡。NFC Reader通过相关
规范发送数据或控制命令给NFC Mobile中的NFCC。
当NFCC收到数据或控制命令后,将交给相关的应用程序来处理。由于CE相关的应用
场景针对支付、门禁等这类对安全性要求非常高的情况,以Android手机NFC支付为例,一
个完整的支付应用程序包括一个为用户提供操作界面的APK以及一些运行在安全性有绝对保
障的SE中的应用程序。
总之,SE在CE模式中扮演了非常重要的角色,目前SE和NFC的组合有三种方式,如
图8-20所示。这三种组合方式从上到下分别如下。
·SE为一个嵌入式安全芯片,该芯片在手机出厂前就已经安装在其内部,而且无法被
替换。该芯片上运行着一个小系统能够处理支付或安全方面的工作。目前,这种形式的SE
还没有标准规范,可参考的模型有NXP公司的pn65芯片模块示意(如图8-21 [17] 所示)。
图8-20 SE和NFC的组合方式
·SE为一个支付型SD卡,这种卡实际上是在SD卡上嵌入了安全模块,相关应用可在这
种卡上运行。该种组合方式所对应的方案也称为NFC-SD方案,这方面的国际标准有ISO
7816。中国的银联曾经主推过NFC-SD卡支付解决方案。
·SE为UICC,也就是常说的手机SIM卡,这种组合方式对应的方案也称为NFC-SIM
方案,目前由运营商主推。前面提到的北京市利用NFC手机充当一卡通所使用的方案就是
NFC-SIM,它需要使用者先到移动运营商那换一个特殊的SIM卡。
图8-21中,NXP公司pn65 NFC芯片自身就包含一个Secure Element,即图中的
SmartMX模块,该模块中运行着一个名为Java Card OS的操作系统。在Java Card OS
上,用户可以安装和运行一些应用程序(称为Applets)。除了SmartMX内置的SE
外,pn65也支持使用外部的SE,即图8-21中的UICC。
图8-21 NXP pn65芯片模块
提示 从参考资料[18]和[19]来看,目前国际上大多使用NFC-SIM方案,而中国的运
营商和银联也将联合推广它,其对应的商品名叫“闪付”。
SE和NFC控制器连接所使用的S2C和SW P协议中,NFC-SIM方案将采用SW P,其对
应的规范是ETSI TS 102613。NFC和UICC使用SW P的连接如图8-22所示。
图8-22 CLF-UICC连线
CLF(NFC Contactless Front-End缩写)和UICC通过三条线相连。Gnd接地,Vcc
提供电源。SW IO为CLF和UICC的数据连接线,数据传输率在212kbps~1.6Mbps之间,
每次传输的数据包小于30字节。
注意,图中UICC的电源由CLF来提供,而非直接由手机电源来提供。这种设计方案使
得手机在电池耗尽的情况下,也可通过外部电磁感应(由NFC Reader或其他NFC设备)来
给CLF和UICC供电,从而确保支付请求不受手机本身的电源影响。
提示 关于SW P的细节,读者可参考ETSI TS 102613。
NFC Forum没有和CE相关的规范,所以读者先了解本节所述的知识,后文在NFC CE
示例中将进一步介绍与之相关的内容。
至此,NFC Device最后一种运行模式就介绍完毕,下面来介绍NFC理论知识的最后一
部分,即NCI。
8.2.5 NCI原理 [20]
NCI(NFC Controller Interface)是NFC Forum于2012年制定的一个规范,其主要
关注点为DH(Device Host,主机设备)如何控制并与NFCC(NFC Controller)交互。
图8-23所示为NFCC、NCI和DH三者之间的关系。
图8-23 NFCC、NCI和DH三者之间的关系
在图8-23中,NFCC和DH通过物理连线相连,物理连线对应为Transport Layer(传
输层)。目前,NFCC和DH在传输层这一块支持SPI、I 2 C、UART和USB等。
在图右边的DH中,所有和NFC相关的应用程序都可被视为DH-NFCEE(EE是
Execution Environment的缩写)。图左边有一个NFCEE模块,该模块也可运行着一些和
NFC相关的程序或系统(以图8-21为例,它的SmartMX Secure Element就是此处所说的
EE)。NFCEE模块可直接集成在NFCC中,也可作为单独的芯片模块通过物理连线与
NFCC相连。
NCI负责处理DH和NFCC之间的交互。NCI包含多个模块,详情见下文。图8-24所示
为NCI的模块结构。
图8-24 NCI模块结构
·NCI Core模块负责DH和NFCC之间交互的基本功能,包括控制消息(Control
Message)和数据消息(Data Message)的传递、DH初始化、重置和配置NFCC等。
·Transport Mapping用于在NFC Core和传输层之间转换数据格式,例如将NCI
Core使用的控制消息和数据消息转换成对应传输层使用的数据格式。
·NCI Module包含多个功能模块,例如RF Discovery模块用于搜索周围的其他NFC
Device、RF Interface用于和对端的NFC Device交互。
使用NCI的NFC Device中,DH和NCI的工作原理如图8-25所示。
图8-25 NCI工作原理
图8-25中:
·DH通过NCI规范定义的Control Message来控制NFCC。目前规范定义的Control
Message包括Commands(请求命令,包括初始化NFCC、重置NFCC、设置NFCC配置参
数等)、Responses(回复)和Notifications(通知)。这些Message都封装在NCI
Control Packages中。其中,Commands只能由DH发送给NFCC。
·DH通过RF Interface和对端NFC设备(图中的Remote NFC Endpoint)交互,也
可通过NFCEE Interface和本设备的NFCEE交互。交互数据包括Control Message和
Data Message。
NCI规范一共有140多页,是NFC Forum众多规范中比较复杂的一个。根据笔者的理
解,NCI的一个很重要的作用就是统一Android中NFC HAL层的实现,即通过一套标准的
方法来实现对NFCC的控制以及数据交互。不过,由于NCI规范推出的时间比较晚(该协议
最终版的时间为2012年11月6日),所以占据最大市场份额的NXP公司在其Android平台的
NFC HAL层中还没有使用NCI。
提示 8.4节将专门讨论Android平台中NFC HAL层的实现状况。从Android 4.2的代
码来看,NXP公司使用了自己的一套NFC HAL层实现方式,而博通公司的NFC HAL层的
实现参考了NCI规范。但实际上这两家公司NFC HAL层的代码处处透露着它们与特定芯片
的紧密关系,这使不了解芯片细节的读者很难真正看懂NFC HAL层的代码。随着NFC的重
要性和普及程度日益加大,开发者已经在Linux Kernel 3.8 [21] 中增加了一个名为NFC的子
系统,它使得以后的NFC HAL层只需通过netlink消息就可和位于Kernel空间的NFC驱动
交互。因目前NFC HAL层这些被不同芯片所“绑架”的代码就可从用户空间移除,而那些
和芯片相关的代码就可通过NFC驱动的形式运行在Kernel之内。
8.2.6 NFC相关规范
至此,我们对NFC理论知识进行了一番介绍,读者应重点关注NFC的三种运行模式以
及相关的数据类型定义、协议栈和工作方式。另外,对目前NFC HAL层实现感兴趣的读者
不妨仔细研究NCI规范。
NFC涉及的规范非常多,表8-10总结了相关的规范 [22] 。
8.3 Android中的NFC
Android平台中,NFC系统模块运行在一个名为"com.android.nfc"的应用进程中,该
应用程序的代码位于packages/ apps/ Nfc下。由于目前NFC HAL层的实现还没有统一接
口,所以该应用程序对应的组织结构如图8-26所示。
图8-26 Android平台中Nfc模块结构
如果使用NXP公司pn系列的NFC芯片,则Nfc模块结构如左图所示,即最终的APK文
件名为Nfc.apk,它通过packages/ apps/ Nfc/ nxp目录下dhimpl模块与libnfc_jni以及
libnfc这两个动态库交互。libnfc的代码位于external/ libnfc-nxp目录下,由NXP公司提
供以用于操作NXP公司的NFC芯片。
如果使用博通公司2079x系列的NFC芯片,则Nfc模块结构如右图所示,即最终的APK
文件名为NfcNci.apk,它通过packages/ apps/ Nfc/ nci目录下的dhimpl模块与
libnfc_nci_jni以及libnfc_nci这两个动态库交互。libnfc_nci的代码位于external/ libnfc-
nci目录下,由博通公司提供以用于操作博通公司的NFC芯片。
提示 图8-26所述的Nfc模块结构对应的Android系统版本为4.2,而Android 4.1只支
持NXP公司的芯片。
如果看过libnfc_jni或libnfc_nci_jni的代码,会发现它们分别使用了NXP和博通公司
封装得用于和各自NFC芯片交互的API,代码可读性非常差。这种情况出现的原因正是前文
所说当前Linux Kernel中还没有一种统一的方法让用户空间的进程和NFC驱动交互。当
然,此问题有望通过完善NFC Subsystem和对应的netlink消息机制得以解决。
基于上述原因,本书不打算介绍任何与特定芯片平台结合过于紧密的模块。所以,本章
分析重点将以图8-26中Nfc.apk为主,它包含了Android平台中NFC的一些核心知识。读者
在掌握的基础上,可尝试结合pn544芯片的数据手册来自行分析dhimpl、libnfc-jni和
libnfc。
下面将开始NFC代码分析之旅,包括两条分析路线。
·先分析NFC相关的应用程序,从客户端角度介绍如何使用Android系统提供的NFC
服务。
·然后介绍Nfc.apk,展示NFC系统模块的核心内容。
8.3.1 NFC应用示例
Android平台中,NFC应用的类型和NFC三种运行模式有关,我们先来看一个使用
NFC R/ W 模式读取NFC Tag的示例。
1.NFC R/ W 模式示例
根据前文对NFC基础知识的介绍可知,和R/ W 模式相关的应用场景就是使用者利用
NFC手机(充当NFC Reader的角色)来读取目标NFC Tag中的信息。Android平台为
NFC R/ W 模式设计了“Tag分发系统”(Tag Dispatch System)的机制,描述了NFC系
统模块如何向应用进程分发与目标NFC Tag相关的Intent(该Intent中包含了Tag中的数
据或是一个代表目标NFC Tag的Tag对象)。
(1)NFC Tag分发系统
Tag分发系统的工作机制如图8-27所示。
图8-27 Tag分发系统的工作机制
Tag分发系统的工作机制如下。
1)当本机扫描到一个NFC Tag后,NFC系统模块将首先尝试直接读取该Tag中的数
据。
2)如果这些数据封装在NDEF消息中并且能映射成Android系统直接支持的数据类型
(目前仅支持MIME和URI这两大类数据类型,详情见表8-11),则NFC系统模块将发送
一个ACTION_NDEF_DISCOVERED的Intent给那些注册了对
ACTION_NDEF_DISCOVERED通知感兴趣的Activity。如果找到目标Activity,则将此
Intent(携带Tag中的NDEF消息和一个代表该NFC Tag的Tag对象)派发给它。如果NFC
系统模块没有找到目标Activity,则将尝试发送一个ACTION_TECH_DISCOVERED的
Intent(包含一个代表目标NFC Tag的Tag对象)。
3)如果NFC Tag中的数据不能转换成系统直接支持的类型,或者NFC Tag中的数据没
有使用NDEF消息格式或者没有目标Activity对ACTION_NDEF_DISCOVERED通知感兴
趣,则NFC系统模块将发送一个ACTION_TECH_DISCOVERED的Intent(包含一个Tag
对象)。如果找到对该Intent感兴趣的Activity,则此Intent将派发给它。
4)如果没有Activity对ACTION_TECH_DISCOVERED感兴趣,则NFC系统模块将
最后尝试发送一个ACTION_TAG_DICOVERED的Intent(包含一个Tag对象)。如果有
对ACTION_TAG_DISCOVERED感兴趣的Activity,则此Intent将派发给它。
上述的Tag分发系统看起来很复杂,实际上其核心内容可概况成三个步骤。
步骤1 如果目标NFC Tag包含了系统支持的NDEF消息,则NFC系统模块将直接把这
个NDEF消息分发给感兴趣的Activity。如果有目标Activity,则直接分发给它,否则转步
骤2。分支转换的判断标准是NFC Tag是否包含了系统支持的NDEF消息以及同时是否有目
标Activity注册了ACTION_NDEF_DISCOVERED通知。
步骤2 如果目标NFC Tag包含了系统不支持的NDEF消息或者步骤1中没有目标
Activity,则NFC系统模块将尝试分发一个ACTION_TECH_DISCOVERED通知。NFC
系统模块在分发此通知时,将首先分析目标NFC Tag所支持的Tag Technology(它代表目
标NFC Tag所使用的技术,详情见下文分析),然后寻找注册了支持这些Tag Technology
的目标Activity并将Intent分发给它。如果没有合适的目标Activity,则转入步骤3。
步骤3 NFC系统模块将分发ACTION_TAG_DISCOVERED通知给注册了对该通知感
兴趣的目标Activity。
除了Tag分发系统外,Android系统还有一个“前台分发系统”(Foreground
Dispatch System)。其规则和Tag分发系统类似,二者区别主要集中在选择目标Activity
上。
·Tag分发系统中,Activity在其AndroidManifest.xml中设置Intent分发条件,即
设置对应的IntentFilter。在这种分发系统中,不考虑目标Activity是否在前台还是后台。
只要找到目标Activity,NFC系统就会启动它。
·前台分发系统中,当前活跃(即所谓的前台)的Activity在其启动过程中设置Intent
分发条件。如果NFC Tag满足前台Activity设置的分发条件,NFC系统模块首先会把
Intent分发给前台这个Activity。当该Activity退到后台时,它需要取消前台分发功能,即
它不再是目标Activity。
简而言之,前台分发系统只检查当前显示的Activity是否满足分发条件,而Tag分发系
统则会搜索系统内所有满足条件的Activity。
(2)Tag分发通知
下面我们分别来看看这三个不同作用的Tag分发通知。
1)ACTION_NDEF_DISCOVERED:由上文可知,NFC系统模块首先尝试将NFC
Tag中的数据映射成系统直接支持的数据格式。表8-11列举了Android系统直接支持的NFC
Forum数据格式。
由表8-11可知,如果NFC Tag中数据格式能映射成功,则NFC系统模块将发送一个
ACTION_NDEF_DISCOVERED Intent给目前Activity,而该Intent将包含此NFC Tag
中的NDEF消息。
除了NFC Forum定义的数据类型外,Android还新增了一个名为AAR(Android
Application Record)的数据类型,它其实是在一个NDEF的消息中封装了某个应用的
package名。对AAR来说,分发系统的工作流程如下。
·分发系统首先尝试使用IntentFilter来寻找目标Activity。如果和IntentFilter匹配
的Activity同时和AAR匹配(即二者的package名一样),就启动该Activity。
·如果Activity跟AAR不匹配,或者是有多个Activity能够处理该Intent,或者是没
有能够处理该Intent的Activity,NFC系统模块将启动由AAR指定的应用程序。
·如果系统中没有安装该AAR对应的应用程序,NFC系统模块将从Google Play下载
该应用程序。
AAR的好处是能让某个公司部署的NFC标签只能由该公司开发的客户端(通过在
NDEF中设置AAR)来处理。后文代码分析时候读者还将看到上述AAR的工作流程。
2)ACTION_TECH_DISCOVERED:如果系统不能映射NFC Tag中的数据,我们该
如何处理呢?
提示 ACTION_TECH_DISCOVERED触发的另一个原因是没有Activity对
ACTION_NDEF_DISCOVERED感兴趣。
该问题的直观答案就是应用程序自己去读取并解析Tag中的数据。不过,由于NFC Tag
的类型有四种之多,甚至同一个厂商还生产了基于不同底层协议的NFC Tag,导致
Android系统无法提供一种通用的接口来操作所有种类的NFC Tag。为了解决此问
题,Android提供了一个名为"android.nfc.tech"的Java包来帮助应用程序操作对应的NFC
Tag。表8-12为android.nfc.tech包中的几个重要成员类。
NFC系统模块将在ACTION_TECH_DISCOVERED Intent中携带一个Tag对象,应
用程序可调用该Tag对象的getTechList来获取该Tag所使用的Technology。注意,一个
Tag可能同时支持表8-12中多种Technology。例如图8-28所示为笔者测试北京市公交卡时
所得到的Tag Technology信息。
图8-28 北京市公交卡Tag Technology示例
由图8-28可知,北京市公交卡同时支持MifareClassic、NfcA和NdefFormatable这
三种类型的Tag Technology。应用程序接着可根据目标NFC Tag所支持的Tag
Technology来创建表8-12中的对象来和NFC Tag交互。
特别注意 严格来说,表8-12中所列的类不仅仅是用来读写对应类型的NFC Tag,它
还支持一些控制操作以至于能在NFC Tag上实现一些特定的协议。以北京市公交卡为例,
其内部肯定有一个相关的协议使得应用程序可通过这些协议来完成公交卡充值,付费等操
作。“小木公交”软件即可读取多个城市公交卡的信息,读者不妨下载试试。
3)ACTION_TAG_DISCOVERED:如果目标NFC Tag不属于表8-12中的一种,则
NFC系统模块将发送ACTION_TAG_DISCOVERED Intent并携带一个Tag对象传递给感
兴趣的Activity。Activity将根据Tag的ID(调用Tag的getId函数)或该Tag使用的技术
(调用Tag的getTechList)来创建合适的处理对象。
下面通过一个示例来了解上述三种通知的用法。
(3)示例分析
本节将通过一个前台分发示例来看看应用程序如何处理上述三种Intent。
[–>ForegroundDispatch.java::onCreate]
public class ForegroundDispatch extends Activity {// ForegroundDispatch是一个Activity
…// 定义一些成员变量
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
// NFC客户端必须调用下面这个函数以获得一个NfcAdapter对象,该对象用于和NFC系统模块交互
mAdapter = NfcAdapter.getDefaultAdapter(this);
// 构造一个PendingIntent供NFC系统模块派发
mPendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
// 监听ACTION_NDEF_DISOVERED通知,并且设置MIME类型为“/”
// 对任何MIME类型的NDEF消息都感兴趣
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
ndef.addDataType(“/”);
// 我们同时还监听ACTION_TECH_DISSCOVERED和ACTION_TAG_DISCOVERED通知
mFilters = new IntentFilter[] {
ndef,
new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED),
new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED),
};
// 对ACTION_TECH_DISCOVERED通知来说,还需要注册对哪些Tag Technology感兴趣
mTechLists = new String[][] {
new String[] { NfcF.class.getName() },// 假设本例支持NfcF
new String[]{MifareClassic.class.getName()}};// 假设本例支持MifareClassic
}
}
在上述onCreate函数中,同时监听了三种Tag Intent通知,最终效果如下。
·如果目标Tag中包含MIME类型的NDEF消息,则Tag分发系统将给我们传递一个
ACTION_NDEF_DISCOVERED Intent。
·如果目标Tag使用的Tag Technolog为NfcF或MifareClass,则Tag分发系统将给我
们传递一个ACTION_TECH_DISCOVERED Intent。
·最后,Tag分发系统将不满足上述条件的其他所有Tag通过
ACTION_TAG_DISCOVERED Intent传递给我们。
接下来,由于本例使用了NFC的前台分发系统,故需要将onCreate中设置的配置信息
传递给NFC系统模块,相关代码如下所示。
[–>ForegroundDispatch.java::onResume]
public void onResume() {
super.onResume();
// 调用NfcAdapter的enableForegroundDispatch函数启动前台分发系统
// 同时需要将分发条件传递给NFC系统模块
mAdapter.enableForegroundDispatch(this, mPendingIntent, mFilters,mTechLists);
}
当NFC系统模块扫描到一个NFC Tag时,前台分发系统通过将触发
ForegroundDispatch这个Activity的onNewIntent函数,该函数的代码如下所示。
[–>ForegroundDispatch.java::onNewIntent]
public void onNewIntent(Intent intent) {
String action = intent.getAction();
// 处理ACTION_NDEF_DISCOVERED消息
if(action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED)){
NdefMessage[] ndefMsgs = null;
// 获取该Intent中的NdefMessage数组。绝大部分情况下该数组的长度为1
NdefMessage[] ndefMsgs = (NdefMessage[])intent.
getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
} // 处理ACTION_TECH_DISCOVERED通知
else if (action.equals(NfcAdapter.ACTION_TECH_DISCOVERED)){
// 获取该Intent中的Tag对象
Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
String[] techList = detectedTag. getTechList();// 获取该Tag使用的Technology
for(String tech: techList){
if(tech.equals(NfcF.class.getName())){// 假设该Tag支持NfcF
// 创建NfcF对象和该Tag交互
NfcF nfcF = NfcF.get(detectedTag);
nfcF.connect();// 向目标Tag发起I/O操作前需要先连接上它
…// 调用NfcF类的其他函数,例如transceive向NFC Tag发送命令
nfcF.close();// 关闭连接
}…
}
} // 处理ACTION_TAG_DISCOVERED通知
else if(action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)){
Tag tag = (Tag)intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
String[] arry = tag.getTechList();
…// 根据Tag使用的Technology来构造相应的处理对象
}
}
当ForegroundActivity退出时,需要在onPause函数中停止使用前台分发系统,相关
代码如下所示。
public void onPause() {
super.onPause();
mAdapter.disableForegroundDispatch(this);// 停止前台分发系统
}
通过上述介绍可知,在R/ W 模式中:
·如果应用程序仅用于读取NFC Tag中所包含的数据,则应尽量通过注册
ACTION_NDEF_DISCOVERED通知来获取自己感兴趣的数据。
·如果应用程序希望能和NFC Tag交互以实现自己的一套协议或者希望能直接读写
NFC Tag,则可通过注册ACTION_TECH_DISCOVERED通知来获得代表NFC Tag的
Tag对象。应用程序接着要根据该Tag使用的Technology来构造对应的TagTechnology对
象来操作此NFC Tag。
·如果应用程序处理的NFC Tag不满足表8-12中的一种,则需要监听
ACTION_TAG_DISCOVERED通知,然后再构造自己的TagTechnology对象来操作此
NFC Tag。
提示 关于Android中NFC的分发系统,请读者阅读Android SDK关于NFC的介绍,
相关资料位于http:/ / developer.android.com/ guide/ topics/ connectivity/ nfc/ nfc.html。
2.NFC P2P模式示例
Android平台中的NFC P2P模式使用了前文介绍的SNEP协议。在SNEP协议基础
上,Android设计了"Android Beam"技术架构,该架构使得NFC客户端程序能非常容易得
在两个NFC设备间传递NDEF消息。
提示 除了SNEP外,Android还定义了一个与之类似的NPP(Ndef Push
Protocol),该协议对应的服务端SAP为0x10,服务名为"com.android.npp"。Android
Beam中,系统首先使用SNEP进行传输。如果一些老旧的设备不支持SNEP,则系统将使用
NPP。
和R/ W 模式一样,Android Beam的使用也需要绑定到一个Activity中,下面我们直接
通过一个例子来看看如何使用Android Beam。
(1)发送端处理
[–>Beam.java::onCreate]
public class Beam extends Activity implements // Beam是一个Activity
CreateNdefMessageCallback,// 用于从源Activity中得到需要传递的NDEF消息
OnNdefPushCompleteCallback // 用于通知NDEF消息传送完毕
{
NfcAdapter mNfcAdapter;
TextView mInfoText;
private static final int MESSAGE_SENT = 1;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mInfoText = (TextView) findViewById(R.id.textView);
// 得到一个NfcAdapter对象,用于和NFC系统模块交互
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
/*
下面这两个函数调用非常重要。
setNdefPushMessageCallback:设置一个回调对象。如果该回调对象不为空,则NFC系统模块将
为Beam这个Activity启用Android Beam。该回调对象的作用是:当NFC系统模块通过SNEP协议
发现另外一个NFC设备时,系统会弹出如图8-29所示的“数据发送通知框”。如果用户选择本机发送数据,
则NFC系统模块将通过这个回调对象获取需要发送的数据。
setOnNdefPushCompleteCallback:设置一个数据发送完毕通知回调对象,当NFC系统模块发送完
本Activity所设置的NDEF消息时,该回调对象对应的函数将被调用。
/
mNfcAdapter.setNdefPushMessageCallback(this, this);
mNfcAdapter.setOnNdefPushCompleteCallback(this, this);
}
如图8-29所示,左图为数据发送通知框。在Android平台中,两个互相靠近的NFC设
备都会弹出类似左图这样的数据发送通知框。至于最终是谁来发送数据则需要用户点击触摸
屏来决定。当对端设备收到示例Beam所发送的NDEF消息后,对端设备的NFC系统模块将
解析该NDEF消息然后通过Tag分发系统或前台分发系统找到目标Activity。图8-29右图即
为对端设备收到本例中Beam发送的数据后的处理结果。
图8-29 Beam示例截图
下面来看createNdefMessage函数,它实现了CreateNdefMessageCallback接口
类。当用户在图8-29左图中点击触摸屏后,NFC系统模块将通过该函数获取应用程序需要
发送的NDEF消息,其代码如下所示。
[–>Beam.java::createNdefMessage]
public NdefMessage createNdefMessage(NfcEvent event) {
Time time = new Time();
time.setToNow();
// 设置一些信息
String text = (“Beam me up!\n\n” +“Beam Time: " + time.format(”%H:%M:%S"));
// 构造一个MIME Type的NDEF消息
NdefMessage msg = new NdefMessage(NdefRecord.createMime(
“application/com.example.android.beam”, text.getBytes()));
return msg;
}
当本机NFC系统模块成功发送了NDEF消息后,onNdefPushComplete将被调用以通
知数据发送的情况。在Beam示例中,该函数的代码如下所示。
[–>Beam.java::onNdefPushComplete]
public void onNdefPushComplete(NfcEvent arg0) {
// 发送一个MESSAGE_SENT消息。注意,onNdefPushComplete运行在Binder线程
mHandler.obtainMessage(MESSAGE_SENT).sendToTarget();
}
(2)接收端处理
当对端NFC设备接收到此NDEF消息时,将通过Tag分发系统来处理它。Beam示例在
其AndroidManifest.xml设置了如图8-30所示的IntentFilter。
图8-30 Beam设置的IntentFilter
根据前文对Tag分发系统的介绍,对端设备的Beam将被启动。启动过程中几个重要函
数的代码如下所示。
[–>Beam.java::onNewIntent/ onResume/ processIntent]
public void onNewIntent(Intent intent) {
setIntent(intent);// Beam使用了SINGLE_TOP启动模式。setIntent用于保存Intent
}
// Beam启动时,onResume将被调用
public void onResume() {
super.onResume();
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
processIntent(getIntent());// getIntent获取setIntent设置的那个Intent对象
}
}
// 处理ACTION_NDEF_DISCOVERED通知
void processIntent(Intent intent) {
// 取出对端Beam发送的NDEF消息
Parcelable[] rawMsgs =
intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
NdefMessage msg = (NdefMessage) rawMsgs[0];
// 设置Text控件,最终结果如图8-29右图所示
mInfoText.setText(new String(msg.getRecords()[0].getPayload()));
}
至此,通过一个示例展示了NFC客户端程序如何使用Android Beam技术。Android
Beam的本质是利用NFC P2P模式的SNEP协议在两个NFC设备间传递NDEF消息。除了
NfcAdapter的setOnNdefPushCompleteCallback函数外,NfcAdapter还有其他方式能
发送NDEF消息。关于这部分内容,请读者务必阅读SDK中的介绍
(http:/ / developer.android.com/ guide/ topics/ connectivity/ nfc/ nfc.html# p2p)。
另外,Android Beam除了能发送NDEF消息外,它还支持发送URI Scheme
为"file"或"content"类型的数据,也就是文件或数据库中的内容。这些数据的量可能比较
大,所以Android Beam将使用Handover并选择蓝牙来传输它们。
3.NFC CE模式示例
在Android平台中,NFC CE的使用比较特殊,主要体现在两点。
·Android SDK没有直接提供Card Emulation相关的API,但Android系统内部提供
了一个名为"com.android.nfc_extras.jar"的Java动态库。在这个动态库中,Android封装
了和CE相关的API。应用程序需要主动加载这个nfc_extras库才能使用CE模式。
·由于CE通常用于支付等方面的工作,所以Android系统在nfc_extras动态库的使用
上有着非常严格的权限管理。
下面介绍相关知识。
(1)nfc_extras和nfcee_access.xml
根据上文所述,应用程序如何才能使用CE模式呢?我们先来看看如何在应用程序中使
用nfc_extras动态库。图8-31所示的AndroidManifest.xml指明了必要的做法。
图8-31 AndroidManifest.xml设置
使用NFC CE的应用必须通过标签申
明"android.permission.NFC"权限。同时还需通过申明使用动态
库"com.android.nfc_extras"。这样,当应用程序运行时,系统会为它加载
com.android.nfc_extras.jar包。该包对应的文件位于/ system/ framework目录下。
接着,客户端在需要使用nfc_extras API的Java类文件中通过import语句导入相关的
类,如图8-32所示。
图8-32 nfc_extras动态库相关类
nfc_extras主要包含三个类,其用法将留待下节的示例代码中再来介绍。
客户端导入相关类后,下一步要解决的问题就是编译。由于Android SDK没有提供这
些类,故需要手动解决编译问题。目前有两种方法解决。
一种方法是为应用程序编写Android.mk,然后添加以下内容。
LOCAL_JAVA_LIBRARIES:=com.android.nfc_extras
该方法要求在Android源码下编译此应用程序。
另外一种方式是在Eclipse中为应用程序手动添加一个编译路径,如图8-33所示。
图8-33 Eclipse设置编译路径
图8-33中,笔者在测试示例中添加了nfc_extras动态库(即classes-full-
debug.jar,它是Android系统编译nfc_extras时生成的中间JAR文件包)。
注意 无论哪种方法,都需要有Android系统的源码。
通过上述步骤,应用程序可编译成功,但它此时依然没有权限操作NFC CE。这是因为
在Android系统中,除了"android.permission.NFC"权限外,NFC系统模块针对NFC CE
这种重要运行模式还需要检查另外一个权限,即客户端程序的签名信息。只有拥有系统指定
签名的应用程序才能使用NFC CE模式。
Android系统所指定的签名信息都保存在/ etc/ nfcee_access.xml文件中,图8-34所示
为Galaxy Note 2中该文件的内容。
图8-34 nfcee_access.xml示例
图8-34中所示nfcee_access.xml包含三个签名(由signer标签指定,图中由黑框标
示)。
·第一个签名为Google W allet相关应用拥有。
·第二个签名为Samsung W allet相关应用拥有。
·第三个签名为笔者测试时用的签名信息。
签名信息检查的工作流程如下。
1)客户端程序开展CE相关操作前,必须先获得一个NfcAdapterExtras对象。
2)在获取该对象时,NFC系统模块先检查应用程序是否拥
有"android.permission.NFC"权限,接着检查该应用程序的签名信息。只有调用程序的签
名信息在nfcee_access.xml有记录,该应用才能得到一个NfcAdapterExtras对象。
提示 显然,nfcee_access.xml要么由手机厂商在出厂前设置,要么在root的手机上
修改。
了解上述知识后,通过一个示例来介绍nfc_extras相关的API及使用方法。
(2)示例分析
本节使用的示例通过修改AndroidBeamDemo而来。
[–>示例]
class NfcEEActivity extends Activity{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {// 先从PackageManager那获取自己的签名信息
PackageManager pm = getPackageManager();
PackageInfo info = pm.getPackageInfo(“com.example.android.beam”,
PackageManager.GET_SIGNATURES);
// 将签名信息打印出来,开发者需要将此签名信息保存到/etc/nfcee_access.xml中
Log.e(“NfcEE”, "signature = " + info.signatures[0].toCharsString());
mContext = this;
// 调用NfcAdatperExtras.get函数获取一个NfcAdapterExtras对象
mAdapterExtras = NfcAdapterExtras.get(NfcAdapter.getDefaultAdapter(
mContext));
/
获取与NFC芯片中Execution Environment模块交互的对象。注意:此处的EE一般情况下就是指
Secure Element。以图8-21 NXP pn65芯片模块图为例,SE可以是其内部的SmartMX模块,
也可以是外部的UICC。对UICC来说,不是所有手机都支持将UICC连接到NFC芯片。除此之外,
通过NFC操作UICC还需要相关驱动的支持。
mEe的类型为NfcExecutionEnvironment,通过它可以和SE交互。
/
mEe = mAdapterExtras.getEmbeddedExecutionEnvironment();
}…
// 创建一个新的线程,相关测试工作放在此线程中进行
Thread testThread = new Thread(){
public void run() {
/
使用CE模式前需要先设置Route,系统目前有ROUTE_ON_WHEN_SCREEN_ON(屏幕打开
时启用CE)和ROUTE_OFF(关闭CE)这两种选项。
/
mAdapterExtras.setCardEmulationRoute(
new CardEmulationRoute(CardEmulationRoute.ROUTE_ON_WHEN_SCREEN_ON, mEe));
// 创建和SE交互的通道。该通道创建后,NFC其他功能将被禁止(如R/W或P2P)
mEe.open();
/
发送命令给EE去执行。SELECT_CARD_MANAGER_COMMAND存储了相关的命令信息。
注意:EE命令的格式遵循ISO 7816-4规范。不同应用需要具体芯片的情况使用对应的命令,这部分是
CE模式的难点。
/
byte[] out = mEe.transceive(SELECT_CARD_MANAGER_COMMAND);
…
mEe.close();// 关闭与SE交互的通道
// 关闭Card Emulation功能
mAdapterExtras.setCardEmulationRoute(
new CardEmulationRoute(CardEmulationRoute.ROUTE_OFF, null));
}
};
testThread.start(); // 启动工作线程
}
…// 其他代码
}
从上述示例代码来看,nfc_extras的API似乎比较简单。但对一个实际应用程序而言,
其最大难度却在于处理相关命令上。由于不同NFC芯片以及所使用的SE不同,其定义的命
令也不尽相同。关于SE命令的格式,读者可参考ISO 7816-4规范。
至此,我们对NFC CE进行了一些简单介绍,并围绕nfc_extras动态库的使用进行了相
关讨论。根据笔者的研究,CE模式的内容远比R/ W 及P2P模式复杂。为此,强烈建议读者
继续阅读参考资料[23]、[24]和[25]。
8.3.2 NFC系统模块
本节开始时介绍,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系统模块核心成员。
(1)NfcService核心成员
图8-35所示为NfcAdapter、TagService等相关成员的类信息。
图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所示。
图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所示。
图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界面时,数据将被发送出去。
(2)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;
}
}
(3)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处理流程分析
(1)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做进一步处理。
提示 findAndReadNdef的实现和具体的NFC芯片有关,而NXP公司的实现函数在
NativeNfcTag类中,内容比较复杂,感兴趣的读者可以阅读。
(2)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;
}
(3)NFC Tag处理流程总结
NFC Tag的处理流程还算简单,下面总结其中涉及的重要函数调用,如图8-38所示。
图8-38 NFC Tag处理流程
NFC Tag的处理流程比较简单,其中有的代码逻辑比较复杂,这部分内容主要集中在
NativeNfcTag的findAndReadNdef函数中。它和具体NFC芯片厂商的实现有关,故把它
留给感兴趣的读者自己来研究。
下面我们将研究Android Beam的工作流程。
3.Android Beam工作流程分析
当本机检测到某个NFC设备进入有效距离并且能处理LLCP协议后,将通过
notifyLlcpLinkActivation通知我们。本节就从这个函数开始分析。
(1)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所
示。
图8-39 notifyLlcpLinkActivation流程
(2)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<Void, Void, Void> {
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数据发送流程。
图8-40 Beam数据发送流程
接着来看Beam数据接收流程。
(3)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接收端的处理流程。
图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 W allet。
提示 NFC CE模式其实内容相当复杂,涉及很多规范。笔者会在博客上继续介绍NFC
CE相关的知识,敬请读者关注。
8.4 NFC HAL层讨论
Android在Hardward目录下为NFC定义了一个nfc.h头文件用于支持NFC HAL操作,
但读者如果看过libnfc或libnfc-nci代码会发现,libnfc和libnfc-nci没有太多使用nfc.h定
义的接口,而是大量引用各自公司定义的一套API。这种做法无可厚非,但它使得其他更上
层的模块很难做到与底层平台或硬件解耦合。相信图8-26已经让读者直观感受到到这种做
法恶果了。
注意 与NFC这种状况形成鲜明对比,本书前面浓墨重彩介绍的W i-Fi模块,借助
nl80211机制或历史更悠久的wireless extension API解决了上层模块与底层平台或硬件的
解耦合问题。
表8-13列举了当前知名的几个NFC HAL层实现。
笔者研究了表8-13中的除nfcpy之外的几个NFC HAL层模块代码,感觉和
wpa_supplicant比起来还是有一定差距。不过,根据参考资料[21]和[26]的介绍,未来
Linux系统中,NFC整个软件架构将变成如图8-42所示。
图8-42 NFC软件架构展望
图8-42中,用户空间运行一个名为neard的NFC Daemon进程,它通过AF_NFC
socket以及Generic Netlink机制和内核空间的NFC子系统通信。neard通过不同的插件来
支持NFC的协议,例如Handover、NPP、SNEP等。
内核空间中,NFC子系统包括Control Command Handler、LLCP Handler、Raw
Data Handler以及Core等核心模块。
不同NFC芯片厂商只要实现相关的NFC驱动即可。至于Core模块如何与NFC驱动交
互,则可使用基于NFC Forum定义的NCI规范(抽象为NFC NCI Layer)、HCI规范(抽
象为NFC HCI Layer),或者直接操作NFC Driver。
提示 在此,笔者希望NFC软件架构尽快完善,同时也希望国内的公司能积极参与到这
一过程中来以提高我们的话语权。
8.5 本章总结和参考资料说明
8.5.1 本章总结
本章对NFC进行了详细介绍,主要内容如下。
·NFC理论知识,这部分内容主要围绕NFC三种运行模式进行了相关讨论。
·Android系统中NFC模块的结构,通过三个示例程序向读者展示了Android中NFC
API的一些知识。
·对NFC系统模块NfcService及其他重要成员进行了介绍,详细分析了NFC Tag分发
以及Android Beam的实现细节。
最后,希望读者在本章的基础上,完成下列任务。
·学习NFC Forum中的其他规范。
·结合NFC Forum中的Connection Handover规范,分析Android中NFC
Handover的实现代码。
8.5.2 参考资料说明
1.概述
[1] 《Near Field Communication From Theory to Practice》第1章和第2章
说明:这本书是笔者目前所阅读的关于NFC最为详尽的资料,建议初学者仔细阅读,尤
其是前三章。
2.NFC概述
[2] 《Near Field Communication From Theory to Practice》图2-1(略有修
改)
[3] NFC Technology Overview
说明:下载地址为http:/ / www.nfc-
forum.org/ resources/ presentations/ NFCForum_Technical_W IMA09.pdf。该资料为
NFC Forum官方提供,介绍了NFC技术。
[4] NFC vs ISO 14443 vs FeliCa
说明:该文档介绍了NFC、ISO 14443和FeliCa之间的区别,文档下载地址为
http:/ / developer.nokia.com/ Community/ Blogs/ resources/ 300066/ Philips-NFC-vs-
ISO14443-vs-Felica-SLIDES.pdf。
[5] http:/ / www.nfc-forum.org/ specs/ spec_list/
说明:该网页介绍了当前NFC Forum官方各个技术文档的主要内容,建议读者下载
NFC Forum技术文档前先阅读此网页。
3.NFC R/ W 运行模式
[6] 《Near Field Communication From Theory to Practice》3.5
节"Reader/ W riter Operating Mode Essentials"
说明:该节对NFC R/ W 运行模式进行了相关介绍。
[7] http:/ / www.nfc-
forum.org/ resources/ white_papers/ NXP_BV_Type_Tags_W hite_Paper-Apr_09.pdf
说明:该文档可在NFC Forum官网上下载,属于NXP公司的一篇介绍NFC Tag Type
的白皮书,通俗易懂,建议不熟悉的读者仔细研究。
4.NDEFP和NFC Record
[8] NFC Data Exchange Format Technical Specification
[9] NFC Record Type Definition Technical Specification
[10] URI Record Type Definition Technical Specification
[11] Text Record Type Definition Technical Specification
说明:NFC Forum官方文档,难度都比较小。
5.NFC P2P运行模式
[12] Logical Link Control Protocol Technical Specification
说明:LLCP的官方协议,建议读者先阅读本章相关章节后再去看它。
[13] NFC Digital Protocol Technical Specification
说明:阅读此规范前,最好看看ISO 18092(http:/ / www.docin.com/ p-
586980527.html)。
[14] Simple NDEF Exchange Protocol Technical Specification
说明:SNEP官方协议,非常简单。
6.NFCCE运行模式
[15] 《Near Field Communication From Theory to Practice》3.7节
[16] 《Near Field Communication From Theory to Practice》3.3节
说明:详细介绍了NFC Enabled Phone和Card Emulation Mode,读者可在此基础
上去理解。
[17] http:/ / www.nfc.cc/ technology/ nxp-nfc-chips/
说明:NXP公司pn65 NFC系列芯片模块图。
[18] http:/ / www.chinaz.com/ biz/ 2011/ 0827/ 207232.shtml
[19] http:/ / kan.weibo.com/ con/ 3616344461572955
说明:中国市场上运营商和银联这两大利益集团联合推广NFC-SIM卡方案。
7.NCI介绍
[20] NFC Controller Interface(NCI)Specification
说明:NCI官方文档,长达140多页。不过读者无须了解其细节,只要掌握NCI架构及
相关模块的功能即可。
[21] https:/ / github.com/ charsyam/ linux-kernel-
3.8/ blob/ master/ Documentation/ networking/ nfc.txt
说明:Linux Kernel 3.8中关于NFC Subsystem的介绍。
8.NFC规范列表
[22] 《Professional NFC Application Development for Android》表1-2
说明:此书与参考资料[1]由同一团队编写,对Android上如何开发NFC应用进行了详
细介绍。
9.NFC CE示例
[23] http:/ / stackoverflow.com/ questions/ 15065172/ nfcee-execution-
environment-hardware-or-library-module
说明:这个资料介绍了Android中如何操作NFC EE,读者不妨看看。
[24] http:/ / nelenkov.blogspot.jp/ 2012/ 08/ accessing-embedded-secure-
element-in.html
[25] http:/ / nelenkov.blogspot.de/ 2012/ 08/ android-secure-element-
execution.html
说明:以上两个资料非常详尽地介绍了Android SE方面的知识,文章质量非常高。
10.NFC HAL层探讨
[26]
http:/ / elinux.org/ images/ d/ d1/ Near_Field_Communication_with_Linux.pdf
说明:内容和参考资料[21]类似。关于NFC认证测试,请参考http:/ / www.nfc-
forum.org/ certification/ certification-testing/ 。
注意 图8-26对应的Android版本为4.2。根据审稿专家的意见,NFC Tag
Technologies分为supported和optional supported两种。optional supported表示某些
Tag Technology在某些平台上不受支持。笔者此处采用的是Android SDK关于Tag
Technology的解释,详情见网址
http:/ / developer.android.com/ reference/ android/ nfc/ tech/ package-summary.html。
2013年5月起,北京可用支持NFC功能的手机当公交卡乘坐地铁和公交,该措施无疑为
NFC的推广起到了积极作用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。