NFC Basics(nfc基础)
这个文档描述了Android实现基本的nfc任务,解释了怎么以NDEF消息的方式发送和接受nfc数据,也描述了Android framework对这些功能的支持,对更多的更强大的讨论比如非NDEF格式的nfc数据传递可以参考nfc加强。
以NDEF格式交互nfc数据主要有两个使用案例需要描述:
读取来自一个nfc标签的NDEF数据
通过Androidbeam功能将NDEF格式信息从一个设备发送到另一个设备。
读取来自nfc标签的ndef数据是通过tag dispatch system(标签匹配系统,下面有介绍)实现,标签匹配系统主要是分析发现的nfc标签,解析nfc数据的类型,和根据nfc数据的类型启动一个合适的app来处理获取到的nfc数据。如果你的app想要处理nfc扫描到的nfc数据需要在nfc中声明一个intentfilter(declare an intent filter,下面也有介绍)来请求处理这个数据。
Android beam功能允许设备通过物理触碰的方式(nfc交互)将ndef数据从一个设备传递到另一个设备,这种交互提供了一种简易的方式来发送数据,而不是像其他的无线交互方式(蓝牙)那样传递数据前还需要检索和配对。nfc方式是两个设备靠近在一起的时候会自动的建立连接。Androidbeam功能也是依赖nfc点对点模式实现的,所以任何应用程序都可以在两个设备间(都支持nfc交互)传递数据,比如通讯录,浏览器,YouTube都可以使用Android beam实现名片,网页,和视频传递到另一个设备。
The Tag Dispatch System(标签匹配系统)
如果你的安卓设备的设置中没有关闭nfc,在你的屏幕没有锁定的时候就会寻找nfc标签,当设备发现一个nfc标签的时候,期望的是有最适合的activity来处理这个标签数据有关的intent,而不是还要再去询问用户哪一个app可以处理这个nfc标签数据。由于设别发现nfc标签的距离是很短的,他就像是用户手动选择一个activity将nfc数据从nfc标签中搬离到设备中然后再断开连接。还有如果你开发一个actiivty能够唯一的处理nfc标签那么你的actiivty关心的是扫描到处理的nfc标签后如何防止activity选择器的出现(一句话的意思就是设备扫描到nfc标签数据以后希望自动的寻找到合适的activity来处理这些数据)
为了实现这个目标,Android提供了特别的tag 匹配系统来分析扫描到的nfc标签,解析他们然后尽可能的定位到一个和其最相关的activity来处理扫描到的数据。为此做了下面的工作:
解析nfc标签和分析出nfc标签内部的有效nfc数据的mime类型(uri,nfc数据有可能只是一个链接也有可能是其他的数据,一段文本或者一个小的图片,所以有可能是mime type,也有可能是uri)
封装mini类型和uri到一个intent,这两部的操作在nfc标签如何映射到mime类型和uri实现(下面有介绍)
基于这个intent开启一个恰当的activity去处理,详情在nfc标签如何匹配application(下面有介绍)
nfc标签如何映射mime类型和uri
在开始写你的nfc程序以前,很重要的是理解nfc标签的不同类型,标签匹配系统怎样解析nfc标签,和标签匹配系统发现了NDEF消息以后做了那些特殊的工作,NFC标签是一个很广泛的技术允许数据以不同的方式写入标签,但是Android支持最好的还是NFC Forum定义的NDEF标准协议
NDEF数据被封装成包含一条或者多条记录(NdefRecord)的消息(NdefMessage)。每一条NDEF记录都是依照规范(协议)把你想要创建的数据完全格式化的,当然,Android也支持不包含ndef数据的其他标签类型,Android对这种标签的实现是通过 android.nfc.tech包下的类实现的。关于这个技术想要了解更多可以参考文章nfc加强。和其他类型的标签通信需要你自己去定义私有的协议栈,所以Android还是推荐你使用Ndef格式去开发和处理标签,这种格式是开发环境和Android设备都有更好的支持(android对ndef消息和ndef记录都提供了类的支持,对其属性进行了封装,而对于其他的类型标签都只是提供了底层的实现,数据格式等都没有提供实现)。
注意:为了下载完整的NDEF规范,去这个网址下载和参考创建普通NDEF记录的案例(下面有介绍)来了解如何构建NDEF记录。
通过上面你对nfc标签有了一定的了解,下面的片段描述了Android处理格式化NDEF标签的更多详情,当Android设备扫描到一个包含NDEF数据的nfc标签的时候,他解析他的消息并尽可能的识别他的mime类型(uri),为了做到这一点,系统先读取NdefMessage中的第一条记录NdefRecord(一条消息有可能包含多条记录),在消息的第一条记录中封装了如何解析这条消息。在规范化的NdefMessage中第一条NdefRecord包含下面的数据:
附加初步认识
分开了解ndefrecord之前最好对ndefrecord的字段有个综合的认识:NdefRecord包含有四个字段:3-bit Tnf,type,id,payload。tnf指明字段属性type是代表了什么,type指定后面的payload代表什么,即tnf指定type代表uri或者mime type的时候,payload可以带用户指定的数据(此时是有效载荷数据),tnf指定uri或者mime type的识别依赖type的时候,type指定payload的数据是uri还是mime type。带着这个初步认识去具体的理解下面的数据:
3-bit TNF(Type Name Format)指定type字段的格式
这个字段属性指明了type字段的格式类型,有效的格式类型如下表所示:
Variable length Type(type 字段)
描述了记录的type,如果tnf指定的类型是TNF_WELL_KNOWN,那么这个字段指定特别的Record Type Definition(RTD:记录类型定义),有效的rtd值如下定义:
Variable length ID
为记录指定id,通常没有必要,用空字节代替,特殊情况下可以使用。
Variable length payload
你想读或者写的真实的数据,rtd设置为uri或者poster的时候这个字段包含uri(特殊情况),还有就是一个ndef消息可能包含多个ndef记录,在使用的过程中要遍历所有ndef记录的payload,不仅仅是第一条记录的payload。
标签匹配系统尽可能的通过tnf和type两个字段映射到ndef消息的mimetype或者uri,如果成功,他将解析到的信息和payload数据一块封装到ACTION_NDEF_DISCOVERED intent中去,通过这个intent匹配到能够处理这个tag数据的最合适的activity。但是标签匹配系统基于第一条记录不能够映射出mime type或者uri的时候(通常是数据格式不是NDEF格式,或者格式是NDEF格式但是第一条记录没有配置mimetype或者uri),基于标签技术的tag对象和payload数据将被封装到ACTION_TECH_DISCOVERED intent中去(简单概括:符合ndef格式的封装成ndef action的intent去匹配activity,如果不符合ndef的封装成tech action的intent去匹配activity)。
上面两个表格描述了tnf和type两个字段如何映射mime type或者uri的,同时也说明了tnf类型和type类型指定为非ndef数据格式,此时数据就要交给上面提到的ACTION_TECH_DISCOVERED intent去处理。
比如:标签匹配系统封装了一个TNF_ABSOLUTE_URI的记录,那么type字段表示的是uri,标签匹配系统会将uri和payload数据封装到ACTION_NDEF_DISCOVERED intent中去,换句话说,如果封装一个TNF_UNKNOWN的记录,不是ndef格式的数据,匹配系统将会以tag对象的方式将数据封装到ACTION_TECH_DISCOVERED intent中去。
nfc标签如何匹配到application中去
标签匹配系统创建了封装有nfc标签mimetype(uri)和有效数据的intent后,通过系统intent的过滤将intent发送给最符合的activity去处理。如果有多个activity可以处理这个nfc标签intent,那么会弹出一个activity选择器供用户选择一个activity去处理。标签匹配系统提供了三个intent来处理nfc标签,下面由高到低的优先级分开来描述这三个intent:
ACTION_NDEF_DISCOVERED :如果nfc标签包含NDEF格式数据,这个intent用来启动对应mimetype或者uri的actiivty,这是最高级别的intent。
ACTION_TECH_DISCOVERED:如果没有注册ndef格式数据的activity注册或者nfctag数据不是ndef格式,或者是ndef格式但是识别不出mimetype(uri)的时候会直接使用这个intent启动activity来处理。优先级别仅次于上面的intent。
ACTION_TAG_DISCOVERED: 这个intent是上面两个都没有注册的时候才会被触发使用。
标签匹配系统匹配application的基本方式是:
- 解析nfc标签后,尽可能的尝试启动高级别的intent,ndef或者tech intent
- 没有ndef intent或者没有相应的activity注册的时候,启动下一级的intent(tech或者tag)
- 如果都没有intent处理,那就什么都不做。
匹配系统匹配的流程图是:
在Android manifest文件中配置请求NFC访问
访问设备的nfc硬件和处理nfc intent前,需要在项目的manifest文件中声明下面几个条目:
声明访问nfc硬件的权限
添加最小sdk的支持
api9仅仅支持tag匹配ACTION_TAG_DISCOVERED和仅仅给了通过参数 EXTRA_NDEF_MESSAGES访问NDEF消息。并没有其他的标签属性和IO外设的访问。
api10包含了NDEF推送一样的综合性的读和写,所以在项目的开发中为了更好的支持nfc的开发将项目支持的最小版本设置最小设置为10。
api14提供了通过Android beam轻松的将数据从一个设备推送到另一个设被还有提供了更多的简单创建ndefrecord的api,涉及到nfc功能的项目开发最好设置最小的sdk支持是14.
uses-feature元素的声明方便play对安装项目设备进行筛选
如果你的项目使用了nfc,但是nfc并不是你的项目主体,你可以在manifest文件中省略掉这一步,而是使用在代码里面判断当前设备是否支持nfc硬件的方式处理,判断方式是获取nfcadapter判断是否为null来决定,如果为null不支持nfc硬件,如果不为null则支持nfc硬件。
NFC IntentFilter 的写法
当你想要处理的nfc标签被扫描到的时候启动你的app,那么需要在你的manifest文件中注册接受nfc intent的一种,两种甚至三种intentfilter声明。文档上说了一堆,不在逐一翻译了,简单说的意思就是自己的项目对于nfc的设计和开发最好都是依照ndef协议,还有就是在读和写的时候都是有目的性的开发,不需要声明三级intent,声明ndef intent去处理,然后最多在声明一个tech intent做备胎就好了。
ACTION_NDEF_DISCOVERED
下面实例显示了处理text/plain:
下面的实例显示了处理uri:http://developer.android.com/index.html.
ACTION_TECH_DISCOVERED
如果你的activity过滤tech这个intent action,你必须在
例如:如果扫描到的tag支持MifareClassic, NdefFormatable, 和 NfcA。你的activity被匹配的顺序是按照tech-list文件中声明的符合这个tag支持的技术数量,比如tech-list中声明符合tag支持的类型3项的顺序优于tech-list符合tag支持的类型1项的。
下面的案例定义所有的tag类型,在使用的过程中可以移除不使用的类型,这个类型文件放在路径
你也可以指定多个tech-list集合,每一个tech-list集合都是独立。nd your activity is considered a match if any single tech-list set is a subset of the technologies that are returned by getTechList(). This provides AND and OR semantics for matching technologies.
The following example matches tags that can support the NfcA and Ndef technologies or can support the NfcB and Ndef technologies:(不理解,不好妄自翻译:)
在你的manifest文件中,使用元素meta-data特别的指出上面创建的xml文件在activity的声明元素中,即:
想了解更多的信息可以参考文章nfc加强
ACTION_TAG_DISCOVERED
从intent中获取到nfc信息
nfc intent启动activity,在activity中可以从这个intent中获取到封装到里面的tag信息(uri,payload数据),下面三个常量是系统提供的用于从intent中获取nfc数据的:
EXTRA_TAG(required):获取tag对象的常量
EXTRA_NDEF_MESSAGE(optional):解析tag标签后一个ndef数据的数组,在ACTION_NDEF_DISCOVERED的intent中获取到一组ndefmessage。
EXTRA_ID:用来获取记录id的常量值。
在intent获取tag数据前需要先对intent的action进行判断校验,看看是否已经进行tag扫描,进行tag扫描然后再获取数据:即:
获取tag数据如下:
创建普通的NDEF类型记录
这个片段描述了在发送nfc标签和通过Android beam发送数据的时候如何创建普通的NDEF类型记录。在Android4.0(api14)开始,createExternal()和createMime()被用来创建external和mime类型的记录。
这个片段同时也描述了相应的记录的intentfilter的写法,下面给出的案例的记录的写法都是ndef消息的第一条记录,因为第一条记录包含tag的mime类型(uri)。
TNF_ABSOLUTE_URI
注意:系统推荐使用RTD_URI的方式代替TNF_ABSOLUTE_URI去写uri类型的记录,因为前面的方式更有效率。
uri记录代码如下:
对应intentfilter的代码如下:
TNF_MIME_MEDIA
使用createMime()api实现代码如下:
使用NdefRecord手动创建代码如下:
对应的intentfilter的代码如下:
TNF_WELL_KNOWN with RTD_TEXT
TNF_WELL_KNOWN with RTD_TEXT实现记录的代码如下:
对应intentfilter的代码如下:
TNF_WELL_KNOWN with RTD_URI
使用createUri(String) api创建代码如下:
使用create(Uri)api创建代码如下:
使用NdefRecord手动创建的代码如下:
对应intentfilter的代码如下:
TNF_EXTERNAL_TYPE
使用createExternal() api 创建代码如下
使用NDefRecord手动创建代码如下:
对应intentfilter的代码如下:
使用TNF_EXTERNAL_TYPE 对于一般的nfc标签开发来说在Android设备和非Android设备上有着更好的支持。
注意:针对TNF_EXTERNAL_TYPE的URNS有着固定的格式: urn:nfc:ext:example.com:externalType。但是nfc forum 组织宣称urn:nfc:ext:在跨平台中都是固定的,在开发过程中可以将其省略,直接写example.com和externalType就行,在Android的匹配的时候,系统会自动的加上前缀(和在layout文件中写组件名字的机制一样)。
Android Application Record(android应用程序记录)
在Android4.0(api14)开始,Android application record(Android应用程序记录,aar,下面也都是以aar简写)使得nfc标签扫描到后能够更精确的启动你的app和activity。aar把项目的包名嵌入到了NDEF记录中去,可以添加aar记录到NDEF消息的任何一个NDEF记录中去,因为标签匹配系统会遍历所有的NDEF消息的所有NDEF记录来寻找aar,如果找到aar,她会按照aar里面嵌入的包名信息去查找对应的app来处理nfc标签。如果app没有安装在当前设备上,会自动启动google play去下载对应的app来处理。
个人理解:给nfc标签的记录加上处理nfc标签的包名指定,通过包名和记录中的mimetype(uri)一起精确的找到处理nfc标签的activity来处理nfc标签,aar的一个特性是当前设备没有指定包名的app可以去play下载指定的app来处理。
对于已经开发的特殊nfc标签存在多个app和activity可以处理的时候,aar是很有用的,通过aar标签匹配系统可以具体到指定的app(甚至可以在设备没有安装的时候自动去play下载安装处理nfc标签)。aar只是在application级别支持,通过指定项目的包名进行约束,并不是activity级别通过intent filter进行约束。aar记录对外也没有提供获取包名的方法,外面也用不着,指定aar记录完全是nfc设备的标签匹配系统用来做匹配用的。
如果nfc标签包含aar,标签匹配系统按照下面的方式去匹配:
如果有一个activity匹配上面介绍的封装有nfc标签mime type(uri)的intent也匹配aar的包名,那么直接启动这个activity。
如果activity匹配intent而不匹配aar,或者多个activity匹配intent或者没有activity匹配intent的时候,标签匹配系统直接通过aar启动指定的app来处理,由此可见aar的优先级高于intentfilter的优先级。
如果匹配aar的app在设备上没有安装系统会去play寻找下载安装来处理。
注意:可以通过 前端匹配系统来重写aars和intent匹配系统来实现foreground activity优先处理发现的nfc标签。(通过重写,如果处理这个标签的activity在前端运行时,即时还有其他的设置的arr的app或者处理这个标签的activity,当前前端的activity都能够拦截此时的标签intent并对数据进行处理)
如果你只是想过滤识别不包含aar的nfc标签,那么还是像往常那样声明intentfilter就好了。仅仅使用intentfilter对于不包含aar的nfc标签是很有用的。需要记住的是aar在Android4.0或者更高版本的设备才被支持。
在开发nfc标签的时候需要注意的事情:
可以使用aar和mime type(uri)组合的方式指定nfc标签支持的设备(nfc怎么被匹配和处理)
还有就是的考虑怎样写nfc标签使得其支持更多的设备(Android设备还有其他非Android设备,兼容问题)
为了上面的兼容,通过定义相对特殊的mime type(uri)来区分匹配更方便一些(因为包名(aar)的机制只是适用于Android设备,对于其他的设备并不适用,比如ios设备就不识别aar,所以nfc标签如果跨平台推荐只是使用mimetype(uri)进行匹配)
Android提供了简单的api来创建aar,NdefRecord中的方法createApplicationRecord())可以创建一个aar记录,然后将其添加到Ndef消息中去即可,需要注意的是ndef消息的第一条ndef记录是用来识别mime type(uri)的,不要用arr来代替ndef的第一条ndefrecord。即:
Beaming NDEF Messages to Other Devices(发送ndef消息到其他设备)
Android beam允许两个支持nfc的Android设备之间点对点的交换数据,此时,发送数据到另一个设备的app必须在前端和接收数据的设备不能够锁定,当两个设备紧密的靠在一起的时候,发送数据的设备会显示一个Touch To Beam(触摸发送)的ui,通过这个Ui用户可以决定是否发送数据到接收数据设备(在nexus6p上实测结果是第一次nfc接触出现一个触摸发送的ui,用户点击后出现两个设备再次nfc靠近的提示,第二次靠近的时候数据做了传输:通讯录传输成功并打开了接受设备的通讯录显示传过去的数据。浏览器没有成功打开接收设备的浏览器(有可能是接受设备没有安装google浏览器))。
注意:前端NDEF推送在api10开始使用,和Android beam提供的功能相似。不过在后期的版本中这些api已经被废除,但是在一些老设备上还是可以使用的。
可以使用下面两种方法的任何一种方法在app中使用Android beam:
setNdefPushMessage()):接受一个Ndef消息并设置到beam,当两个设备紧密的靠近时beam自动的发送消息。
setNdefPushMessageCallback()):接受一个包含createNdefMessage()的回调,当两个设备紧密接触的时候可以在这个回调中创建ndef消息并发送。
一个activity一次只能推送一个ndef消息,因此两种方式同时设置的时候,setNdefPushMessageCallback()比setNdefPushMessage()优先级高,以setNdefPushMessageCallback()设置的消息为准。
为了使用Android beam,可以参考下面指导:
发送数据的activity必须是在前端而且两个设备都没有锁定
发送的数据必须依照NdefMessage的格式封装数据
接受beamed数据的nfc设备必须支持 com.android.npp NDEF push protocol 或者NFC Forum's SNEP (Simple NDEF Exchange Protocol),com.android.npp协议在api9到api13的Android设备上支持,两个协议在api14及其以上都支持。
注意:如果你的activity支持Android beam并且在前端,标准的intent匹配系统关闭(在nfc加强中有更深层次的介绍)。然而你的activity允许foreground dispatching。activity仍然扫描标签和匹配在前端匹配中设置的intentfilter。
activity允许Android beam:
创建一个包含你想推送到其他设备的NdefRecords的NdefMessage。
在activity的oncreate()方法中调用带有NdefMessage的setNdefpushMessage()方法或者setNdefPushMessageCallback()。
普通情况下,如果发送的ndef消息已确定,不依赖于逻辑操作可以使用setNdefPushMessage()的方法在oncreate里面实现。当发送的消息不确定依赖于项目的上下文时候使用回调的方式实现。
下面的代码演示了回调的方式实现Android beam,具体的逻辑代码可以参考系统demo(AndroidBeamDemo,注意在github上没有可以在sdk中找到):
注意:上述代码中注释了aar(可以移除),如果你允许aar,app总是接受Android beam消息,如果app不存在,play会去下载这个app,所以在4.0版本及其以上如果使用了aar,intentfilter的匹配不是必须的了(关于aar前面有着详细的描述):
使用上面的intentfilter,com.example.android.beam包名的应用程序在下面几种情况下可以被启动:
扫描到nfc标签
使用aar的方式接收到包名com.example.android.beam的Android beam消息
接收到包含mime类型的application/vnd.com.example.android.beam的消息
尽管aars机制保证了app可以被启动或者下载,但是还是推荐intentfilter的模式,因为intentfilter模式可以让你具体到你的应用程序中的某一个activity,而不是像aar那样只是启动app的mainactivity。aar做不到activity的级别,同时有些Android设备也不支持aar模式(低于4.0的老版本设备)。