android nfc常用标签读取总结
有几天没有更新博客了,不过本篇却准备了许久,希望能带给每一位开发者最简单高效的学习方式。废话到此为止,下面开始正文。
nfc(near field communication,近场通信)是一种数据传输技术。与wi-fi、蓝牙、红外线等数据传输技术的一个主要差异就是有效距离一般不能超过4厘米。但是nfc传输速度要比红外快。目前nfc已经出现了一些应用,例如电子标签识别、刷手机、点对点付款、身份识别、信息记录等,本篇文章的目的是为大家揭开nfc标签的面纱。
下面我们先从nfc的工作模式开始阐述nfc,开发nfc必先了解nfc。
1.nfc的工作模式
nfc支持如下3种工作模式:读卡器模式(reader/writer mode)、仿真卡模式(card emulation mode)、点对点模式(p2p mode)。
下来分别看一下这三种模式:
(1)读卡器模式
数据在nfc芯片中,可以简单理解成“刷标签”。本质上就是通过支持nfc的手机或其它电子设备从带有nfc芯片的标签、贴纸、名片等媒介中读写信息。通常nfc标签是不需要外部供电的。当支持nfc的外设向nfc读写数据时,它会发送某种磁场,而这个磁场会自动的向nfc标签供电。
(2)仿真卡模式
数据在支持nfc的手机或其它电子设备中,可以简单理解成“刷手机”。本质上就是将支持nfc的手机或其它电子设备当成借记卡、公交卡、门禁卡等ic卡使用。基本原理是将相应ic卡中的信息凭证封装成数据包存储在支持nfc的外设中 。
在使用时还需要一个nfc射频器(相当于刷卡器)。将手机靠近nfc射频器,手机就会接收到nfc射频器发过来的信号,在通过一系列复杂的验证后,将ic卡的相应信息传入nfc射频器,最后这些ic卡数据会传入nfc射频器连接的电脑,并进行相应的处理(如电子转帐、开门等操作)。
(3)点对点模式
该模式与蓝牙、红外差不多,用于不同nfc设备之间进行数据交换,不过这个模式已经没有有“刷”的感觉了。其有效距离一般不能超过4厘米,但传输建立速度要比红外和蓝牙技术快很多,传输速度比红外块得多,如过双方都使用android4.2,nfc会直接利用蓝牙传输。这种技术被称为android beam。所以使用android beam传输数据的两部设备不再限于4厘米之内。
点对点模式的典型应用是两部支持nfc的手机或平板电脑实现数据的点对点传输,例如,交换图片或同步设备联系人。因此,通过nfc,多个设备如数字相机,计算机,手机之间,都可以快速连接,并交换资料或者服务。
下面看一下nfc、蓝牙和红外之间的差异:
对比项 | nfc | 蓝牙 | 红外 |
---|---|---|---|
网络类型 | 点对点 | 单点对多点 | 点对点 |
有效距离 | <=0.1m | <=10m,最新的蓝牙4.0有效距离可达100m | 一般在1m以内,热技术连接,不稳定 |
传输速度 | 最大424kbps | 最大24mbps | 慢速115.2kbps,快速4mbps |
建立时间 | <0.1s | 6s | 0.5s |
安全性 | 安全,硬件实现 | 安全,软件实现 | 不安全,使用irfm时除外 |
通信模式 | 主动-主动/被动 | 主动-主动 | 主动-主动 |
成本 | 低 | 中 | 低 |
2.android对nfc的支持
不同的nfc标签之间差异很大,有的只支持简单的读写操作,有时还会采用支持一次性写入的芯片,将nfc标签设计成只读的。当然,也存在一些复杂的nfc标签,例如,有一些nfc标签可以通过硬件加密的方式限制对某一区域的访问。还有一些标签自带操作环境,允许nfc设备与这些标签进行更复杂的交互。这些标签中的数据也会采用不同的格式。但android sdk api主要支持nfc论坛标准(forum standard),这种标准被称为ndef(nfc data exchange format,nfc数据交换格式)。
ndef格式其实就类似于硬盘的ntfs,下面我们看一下ndef数据:
(1)ndef数据的操作
android sdk api支持如下3种ndef数据的操作:
1)从nfc标签读取ndef格式的数据。
2)向nfc标签写入ndef格式的数据。
3)通过android beam技术将ndef数据发送到另一部nfc设备。
用于描述ndef格式数据的两个类:
1)ndefmessage:描述ndef格式的信息,实际上我们写入nfc标签的就是ndefmessage对象。
2)ndefrecord:描述ndef信息的一个信息段,一个ndefmessage可能包含一个或者多个ndefrecord。
ndefmessage和ndefrecord是android nfc技术的核心类,无论读写ndef格式的nfc标签,还是通过android beam技术传递ndef格式的数据,都需要这两个类。
(2)非ndef数据的操作
对于某些特殊需求,可能要存任意的数据,对于这些数据,我们就需要自定义格式。这些数据格式实际上就是普通的字节流,至于字节流中的数据代表什么,就由开发人员自己定义了。
(3)编写nfc程序的基本步骤
1)设置权限,限制android版本、安装的设备:
<uses-sdk android:minsdkversion="14"/> <uses-permission android:name="android.permission.nfc" /> <!-- 要求当前设备必须要有nfc芯片 --> <uses-feature android:name="android.hardware.nfc" android:required="true" />
2)定义可接收tag的activity
activity清单需要配置一下launchmode属性:
<activity android:name=".tagtextactivity" android:launchmode="singletop"/>
而activity中,我们也抽取了一个通用的basenfcactivity,如下(后面的activity实现都继承于basenfcactivity):
/** * 1.子类需要在oncreate方法中做activity初始化。 * 2.子类需要在onnewintent方法中进行nfc标签相关操作。 * 当launchmode设置为singletop时,第一次运行调用oncreate方法, * 第二次运行将不会创建新的activity实例,将调用onnewintent方法 * 所以我们获取intent传递过来的tag数据操作放在onnewintent方法中执行 * 如果在栈中已经有该activity的实例,就重用该实例(会调用实例的onnewintent()) * 只要nfc标签靠近就执行 */ public class basenfcactivity extends appcompatactivity { private nfcadapter mnfcadapter; private pendingintent mpendingintent; /** * 启动activity,界面可见时 */ @override protected void onstart() { super.onstart(); mnfcadapter = nfcadapter.getdefaultadapter(this); //一旦截获nfc消息,就会通过pendingintent调用窗口 mpendingintent = pendingintent.getactivity(this, 0, new intent(this, getclass()), 0); } /** * 获得焦点,按钮可以点击 */ @override public void onresume() { super.onresume(); //设置处理优于所有其他nfc的处理 if (mnfcadapter != null) mnfcadapter.enableforegrounddispatch(this, mpendingintent, null, null); } /** * 暂停activity,界面获取焦点,按钮可以点击 */ @override public void onpause() { super.onpause(); //恢复默认状态 if (mnfcadapter != null) mnfcadapter.disableforegrounddispatch(this); } }
注意:通常来说,所有处理nfc的activity都要设置launchmode属性为singletop或者singletask,保证了无论nfc标签靠近手机多少次,activity实例只有一个。
接下来看几个具体的nfc标签应用实例,通过情景学习快速掌握nfc技术:
3.两个nfc标签的简单实例
1.利用nfc标签让android自动运行程序
场景是这样的:现将应用程序的包写到nfc程序上,然后我们将nfc标签靠近android手机,手机就会自动运行包所对应的程序,这个是nfc比较基本的一个应用。下面以贴近标签自动运行android自带的“短信”为例。
向nfc标签写入数据一般分为三步:
1)获取tag对象
tag tag = intent.getparcelableextra(nfcadapter.extra_tag);
2)判断nfc标签的数据类型(通过ndef.get方法)
ndef ndef = ndef.get(tag);
3)写入数据
ndef.writendefmessage(ndefmessage);
详细实现代码如下:
public class runappactivity extends basenfcactivity{ private string mpackagename = "com.android.mms";//短信 @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); } @override public void onnewintent(intent intent) { if (mpackagename == null) return; //1.获取tag对象 tag detectedtag = intent.getparcelableextra(nfcadapter.extra_tag); writenfctag(detectedtag); } /** * 往标签写数据的方法 * * @param tag */ public void writenfctag(tag tag) { if (tag == null) { return; } ndefmessage ndefmessage = new ndefmessage(new ndefrecord[]{ndefrecord .createapplicationrecord(mpackagename)}); //转换成字节获得大小 int size = ndefmessage.tobytearray().length; try { //2.判断nfc标签的数据类型(通过ndef.get方法) ndef ndef = ndef.get(tag); //判断是否为ndef标签 if (ndef != null) { ndef.connect(); //判断是否支持可写 if (!ndef.iswritable()) { return; } //判断标签的容量是否够用 if (ndef.getmaxsize() < size) { return; } //3.写入数据 ndef.writendefmessage(ndefmessage); toast.maketext(this, "写入成功", toast.length_short).show(); } else { //当我们买回来的nfc标签是没有格式化的,或者没有分区的执行此步 //ndef格式类 ndefformatable format = ndefformatable.get(tag); //判断是否获得了ndefformatable对象,有一些标签是只读的或者不允许格式化的 if (format != null) { //连接 format.connect(); //格式化并将信息写入标签 format.format(ndefmessage); toast.maketext(this, "写入成功", toast.length_short).show(); } else { toast.maketext(this, "写入失败", toast.length_short).show(); } } } catch (exception e) { } } }
注意:设置 runappactivity 的 launchmode 属性为 singletop。
现在看一下效果图:
将nfc标签贴近手机背面,自动写入数据,此时退出所有程序,返回桌面,然后再将nfc标签贴近手机背面,将会看到自动打开了“短信”。
下来再看一个有趣的例子:
2.利用nfc标签让android自动打开网页
如何让nfc标签贴近手机,手机可以自动打开一个网页呢?
首先我们创建一个ndefrecord,android已经为我们提供好了这样的方法:
//直接接受一个uri public ndefrecord createuri(string uristring); //接受一个uri的对象 public ndefrecord createuri(uri uri);
实现代码对比“3.利用nfc标签让android自动运行程序”部分只是修改了writenfctag方法中
ndefmessage ndefmessage = new ndefmessage(new ndefrecord[]{ndefrecord .createapplicationrecord(mpackagename)});
为
ndefmessage ndefmessage = new ndefmessage(new ndefrecord[]{ndefrecord .createuri(uri.parse(http://www.baidu.com))});
其余不变。
上面这个功能还是比较有用的,例如我们往某些商品上贴上nfc标签,里面写入该商品的详细介绍网页uri,当用户贴近商品时,就会自动打开该商品的详情介绍。
通过上面这两个案例的学习相信很多人已经对nfc感起了兴趣,那么下来渗透式的分析一下ndef文本格式,看看ndef到底是个什么东西。
4.ndef文本格式深度解析
获取nfc标签中的数据要通过 ndefrecord.getpayload 方法完成。当然,在处理这些数据之前,最好判断一下ndefrecord对象中存储的是不是ndef文本格式数据。
(1)判断数据是否为ndef格式
1)tnf(类型名格式,type name format)必须是ndefrecord.tnf_well_known。
2)可变的长度类型必须是ndefrecord.rtd_text。
如果这两个标准同时满足,那么就为ndef格式。
(2)ndef文本格式规范
不管什么格式的数据本质上都是由一些字节组成的。对于ndef文本格式来说,这些数据的第1个字节描述了数据的状态,然后若干个字节描述文本的语言编码,最后剩余字节表示文本数据。这些数据格式由nfc forum的相关规范定义,可以通过 下载相关的规范。
下面这两张表是规范中 3.2节 相对重要的翻译部分:
ndef文本数据格式:
偏移量 | 长度(bytes) | 描述 |
---|---|---|
0 | 1 | 状态字节,见下表(状态字节编码格式) |
1 | n | iso/iana语言编码。例如”en-us”,”fr-ca”。编码格式是us-ascii,长度(n)由状态字节的后6位指定。 |
n+1 | m | 文本数据。编码格式是utf-8或utf-16。编码格式由状态字节的前3位指定。 |
状态字节编码格式:
字节位(0是最低位,7是最高位) | 含义 |
---|---|
7 | 0:文本编码为utf-8,1:文本编码为utf-16 |
6 | 必须设为0 |
5..0 | 语言编码的长度(占用的字节个数) |
下面我们动手实现nfc标签中的文本数据的读写操作:
1.读nfc标签文本数据
public class readtextactivity extends basenfcactivity { private textview mnfctext; private string mtagtext; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_read_text); mnfctext = (textview) findviewbyid(r.id.tv_nfctext); } @override public void onnewintent(intent intent) { //1.获取tag对象 tag detectedtag = intent.getparcelableextra(nfcadapter.extra_tag); //2.获取ndef的实例 ndef ndef = ndef.get(detectedtag); mtagtext = ndef.gettype() + "\nmaxsize:" + ndef.getmaxsize() + "bytes\n\n"; readnfctag(intent); mnfctext.settext(mtagtext); } /** * 读取nfc标签文本数据 */ private void readnfctag(intent intent) { if (nfcadapter.action_ndef_discovered.equals(intent.getaction())) { parcelable[] rawmsgs = intent.getparcelablearrayextra( nfcadapter.extra_ndef_messages); ndefmessage msgs[] = null; int contentsize = 0; if (rawmsgs != null) { msgs = new ndefmessage[rawmsgs.length]; for (int i = 0; i < rawmsgs.length; i++) { msgs[i] = (ndefmessage) rawmsgs[i]; contentsize += msgs[i].tobytearray().length; } } try { if (msgs != null) { ndefrecord record = msgs[0].getrecords()[0]; string textrecord = parsetextrecord(record); mtagtext += textrecord + "\n\ntext\n" + contentsize + " bytes"; } } catch (exception e) { } } } /** * 解析ndef文本数据,从第三个字节开始,后面的文本数据 * @param ndefrecord * @return */ public static string parsetextrecord(ndefrecord ndefrecord) { /** * 判断数据是否为ndef格式 */ //判断tnf if (ndefrecord.gettnf() != ndefrecord.tnf_well_known) { return null; } //判断可变的长度的类型 if (!arrays.equals(ndefrecord.gettype(), ndefrecord.rtd_text)) { return null; } try { //获得字节数组,然后进行分析 byte[] payload = ndefrecord.getpayload(); //下面开始ndef文本数据第一个字节,状态字节 //判断文本是基于utf-8还是utf-16的,取第一个字节"位与"上16进制的80,16进制的80也就是最高位是1, //其他位都是0,所以进行"位与"运算后就会保留最高位 string textencoding = ((payload[0] & 0x80) == 0) ? "utf-8" : "utf-16"; //3f最高两位是0,第六位是1,所以进行"位与"运算后获得第六位 int languagecodelength = payload[0] & 0x3f; //下面开始ndef文本数据第二个字节,语言编码 //获得语言编码 string languagecode = new string(payload, 1, languagecodelength, "us-ascii"); //下面开始ndef文本数据后面的字节,解析出文本 string textrecord = new string(payload, languagecodelength + 1, payload.length - languagecodelength - 1, textencoding); return textrecord; } catch (exception e) { throw new illegalargumentexception(); } } }
注意:activity清单需要配置一下launchmode属性(后面一样要注意):
<activity android:name=".readtextactivity" android:launchmode="singletop"/>
2.写nfc标签文本数据
public class writetextactivity extends basenfcactivity { private string mtext = "nfc-newtext-123"; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_write_text); } @override public void onnewintent(intent intent) { if (mtext == null) return; //获取tag对象 tag detectedtag = intent.getparcelableextra(nfcadapter.extra_tag); ndefmessage ndefmessage = new ndefmessage( new ndefrecord[] { createtextrecord(mtext) }); boolean result = writetag(ndefmessage, detectedtag); if (result){ toast.maketext(this, "写入成功", toast.length_short).show(); } else { toast.maketext(this, "写入失败", toast.length_short).show(); } } /** * 创建ndef文本数据 * @param text * @return */ public static ndefrecord createtextrecord(string text) { byte[] langbytes = locale.china.getlanguage().getbytes(charset.forname("us-ascii")); charset utfencoding = charset.forname("utf-8"); //将文本转换为utf-8格式 byte[] textbytes = text.getbytes(utfencoding); //设置状态字节编码最高位数为0 int utfbit = 0; //定义状态字节 char status = (char) (utfbit + langbytes.length); byte[] data = new byte[1 + langbytes.length + textbytes.length]; //设置第一个状态字节,先将状态码转换成字节 data[0] = (byte) status; //设置语言编码,使用数组拷贝方法,从0开始拷贝到data中,拷贝到data的1到langbytes.length的位置 system.arraycopy(langbytes, 0, data, 1, langbytes.length); //设置文本字节,使用数组拷贝方法,从0开始拷贝到data中,拷贝到data的1 + langbytes.length //到textbytes.length的位置 system.arraycopy(textbytes, 0, data, 1 + langbytes.length, textbytes.length); //通过字节传入ndefrecord对象 //ndefrecord.rtd_text:传入类型 读写 ndefrecord ndefrecord = new ndefrecord(ndefrecord.tnf_well_known, ndefrecord.rtd_text, new byte[0], data); return ndefrecord; } /** * 写数据 * @param ndefmessage 创建好的ndef文本数据 * @param tag 标签 * @return */ public static boolean writetag(ndefmessage ndefmessage, tag tag) { try { ndef ndef = ndef.get(tag); ndef.connect(); ndef.writendefmessage(ndefmessage); return true; } catch (exception e) { } return false; } }
我们将手机贴近nfc标签,当写入成功会弹出“写入成功”的吐司。下面我们再验证一下是否成功写入:
我们看到,数据已经写入成功了,说明到此我们已经成功的读写nfc标签中的文本数据了。
5.ndef uri格式深度解析
与ndef文本格式一样,存储在nfc标签中的uri也有一定的格式,
(1)uri的格式规范要比文本格式简单一些:
name | 偏移 | 大小 | 值 | 描述 |
---|---|---|---|---|
识别码 | 0 | 1byte | uri识别码 | 用于存储已知uri的前缀 |
uri字段 | 1 | n | utf-8类型字符串 | 用于存储剩余字符串 |
(2)uri的前缀如下(都是十六进制的一个数):
十进制 | 十六进制 | 协议 | 十进制 | 十六进制 | 协议 |
---|---|---|---|---|---|
0 | 0x00 | n/a | 1 | 0x01 | . |
2 | 0x02 | . | 3 | 0x03 | http:// |
4 | 0x04 | https:// | 5 | 0x05 | tel: |
6 | 0x06 | mailto: | 7 | 0x07 | |
8 | 0x08 | . | 9 | 0x09 | ftps:// |
10 | 0x0a | sftp:// | 11 | 0x0b | smb:// |
12 | 0x0c | nfs:// | 13 | 0x0d | ftp:// |
14 | 0x0e | dav:// | 15 | 0x0f | news: |
16 | 0x10 | telnet:// | 17 | 0x11 | imap: |
18 | 0x12 | rtsp:// | 19 | 0x13 | urn: |
20 | 0x14 | pop: | 21 | 0x15 | sip: |
22 | 0x16 | sips: | 23 | 0x17 | tftp: |
24 | 0x18 | btspp:// | 25 | 0x19 | btl2cap:// |
26 | 0x1a | btgoep:// | 27 | 0x1b | tcpobex:// |
28 | 0x1c | irdaobex:// | 29 | 0x1d | file:// |
30 | 0x1e | urn:epc:id: | 31 | 0x1f | urn:epc:tag: |
32 | 0x20 | urn:epc:pat: | 33 | 0x21 | urn:epc:raw: |
34 | 0x22 | urn:epc: | 35 | 0x23 | urn:nfc: |
每一个协议,都是用十六进制来存储于识别码位置(占1byte)。
是不是相对简单了些,那么下来我们来解析一个uri。
(3)预先定义已知uri前缀
这里我们定义一个uriprefix类,以便方便的获取uri前缀:
public class uriprefix { public static final map<byte, string> uri_prefix_map = new hashmap<byte, string>(); // 预先定义已知uri前缀 static { uri_prefix_map.put((byte) 0x00, ""); uri_prefix_map.put((byte) 0x01, "http://www."); uri_prefix_map.put((byte) 0x02, "https://www."); uri_prefix_map.put((byte) 0x03, "http://"); uri_prefix_map.put((byte) 0x04, "https://"); uri_prefix_map.put((byte) 0x05, "tel:"); uri_prefix_map.put((byte) 0x06, "mailto:"); uri_prefix_map.put((byte) 0x07, "ftp://anonymous:anonymous@"); uri_prefix_map.put((byte) 0x08, "ftp://ftp."); uri_prefix_map.put((byte) 0x09, "ftps://"); uri_prefix_map.put((byte) 0x0a, "sftp://"); uri_prefix_map.put((byte) 0x0b, "smb://"); uri_prefix_map.put((byte) 0x0c, "nfs://"); uri_prefix_map.put((byte) 0x0d, "ftp://"); uri_prefix_map.put((byte) 0x0e, "dav://"); uri_prefix_map.put((byte) 0x0f, "news:"); uri_prefix_map.put((byte) 0x10, "telnet://"); uri_prefix_map.put((byte) 0x11, "imap:"); uri_prefix_map.put((byte) 0x12, "rtsp://"); uri_prefix_map.put((byte) 0x13, "urn:"); uri_prefix_map.put((byte) 0x14, "pop:"); uri_prefix_map.put((byte) 0x15, "sip:"); uri_prefix_map.put((byte) 0x16, "sips:"); uri_prefix_map.put((byte) 0x17, "tftp:"); uri_prefix_map.put((byte) 0x18, "btspp://"); uri_prefix_map.put((byte) 0x19, "btl2cap://"); uri_prefix_map.put((byte) 0x1a, "btgoep://"); uri_prefix_map.put((byte) 0x1b, "tcpobex://"); uri_prefix_map.put((byte) 0x1c, "irdaobex://"); uri_prefix_map.put((byte) 0x1d, "file://"); uri_prefix_map.put((byte) 0x1e, "urn:epc:id:"); uri_prefix_map.put((byte) 0x1f, "urn:epc:tag:"); uri_prefix_map.put((byte) 0x20, "urn:epc:pat:"); uri_prefix_map.put((byte) 0x21, "urn:epc:raw:"); uri_prefix_map.put((byte) 0x22, "urn:epc:"); uri_prefix_map.put((byte) 0x23, "urn:nfc:"); } }
然后我们来看一下清单文件中activity的相关配置:
<activity android:name=".readwriteuriactivity" android:label="读写nfc标签的uri" android:launchmode="singletop" > <intent-filter> <action android:name="android.nfc.action.ndef_discovered" /> <category android:name="android.intent.category.default" /> <!-- 拦截nfc标签中存储有以下uri前缀的 --> <data android:scheme="http" /> <data android:scheme="https" /> <data android:scheme="ftp" /> </intent-filter> <intent-filter> <action android:name="android.nfc.action.ndef_discovered" /> <category android:name="android.intent.category.default" /> <!-- 定义可以拦截文本 --> <data android:mimetype="text/plain" /> </intent-filter> </activity>
好了,接下来就可以进行读写nfc标签中的uri数据了:
1.读nfc标签中的uri数据
public class readuriactivity extends basenfcactivity { private textview mnfctext; private string mtagtext; @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_read_uri); mnfctext = (textview) findviewbyid(r.id.tv_nfctext); } @override public void onnewintent(intent intent) { //获取tag对象 tag detectedtag = intent.getparcelableextra(nfcadapter.extra_tag); //获取ndef的实例 ndef ndef = ndef.get(detectedtag); mtagtext = ndef.gettype() + "\n max size:" + ndef.getmaxsize() + " bytes\n\n"; readnfctag(intent); mnfctext.settext(mtagtext); } /** * 读取nfc标签uri */ private void readnfctag(intent intent) { if (nfcadapter.action_ndef_discovered.equals(intent.getaction())) { parcelable[] rawmsgs = intent.getparcelablearrayextra( nfcadapter.extra_ndef_messages); ndefmessage ndefmessage = null; int contentsize = 0; if (rawmsgs != null) { if (rawmsgs.length > 0) { ndefmessage = (ndefmessage) rawmsgs[0]; contentsize = ndefmessage.tobytearray().length; } else { return; } } try { ndefrecord ndefrecord = ndefmessage.getrecords()[0]; log.i("java",ndefrecord.tostring()); uri uri = parse(ndefrecord); log.i("java","uri:"+uri.tostring()); mtagtext += uri.tostring() + "\n\nuri\n" + contentsize + " bytes"; } catch (exception e) { } } } /** * 解析ndefrecord中uri数据 * @param record * @return */ public static uri parse(ndefrecord record) { short tnf = record.gettnf(); if (tnf == ndefrecord.tnf_well_known) { return parsewellknown(record); } else if (tnf == ndefrecord.tnf_absolute_uri) { return parseabsolute(record); } throw new illegalargumentexception("unknown tnf " + tnf); } /** * 处理绝对的uri * 没有uri识别码,也就是没有uri前缀,存储的全部是字符串 * @param ndefrecord 描述ndef信息的一个信息段,一个ndefmessage可能包含一个或者多个ndefrecord * @return */ private static uri parseabsolute(ndefrecord ndefrecord) { //获取所有的字节数据 byte[] payload = ndefrecord.getpayload(); uri uri = uri.parse(new string(payload, charset.forname("utf-8"))); return uri; } /** * 处理已知类型的uri * @param ndefrecord * @return */ private static uri parsewellknown(ndefrecord ndefrecord) { //判断数据是否是uri类型的 if (!arrays.equals(ndefrecord.gettype(), ndefrecord.rtd_uri)) return null; //获取所有的字节数据 byte[] payload = ndefrecord.getpayload(); string prefix = uriprefix.uri_prefix_map.get(payload[0]); byte[] prefixbytes = prefix.getbytes(charset.forname("utf-8")); byte[] fulluri = new byte[prefixbytes.length + payload.length - 1]; system.arraycopy(prefixbytes, 0, fulluri, 0, prefixbytes.length); system.arraycopy(payload, 1, fulluri, prefixbytes.length, payload.length - 1); uri uri = uri.parse(new string(fulluri, charset.forname("utf-8"))); return uri; } }
2.写nfc标签中的uri数据
public class writeuriactivity extends basenfcactivity { private string muri = "http://www.baidu.com"; @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_write_uri); } public void onnewintent(intent intent) { tag detectedtag = intent.getparcelableextra(nfcadapter.extra_tag); ndefmessage ndefmessage = new ndefmessage(new ndefrecord[]{createurirecord(muri)}); boolean result = writetag(ndefmessage, detectedtag); if (result){ toast.maketext(this, "写入成功", toast.length_short).show(); } else { toast.maketext(this, "写入失败", toast.length_short).show(); } } /** * 将uri转成ndefrecord * @param uristr * @return */ public static ndefrecord createurirecord(string uristr) { byte prefix = 0; for (byte b : uriprefix.uri_prefix_map.keyset()) { string prefixstr = uriprefix.uri_prefix_map.get(b).tolowercase(); if ("".equals(prefixstr)) continue; if (uristr.tolowercase().startswith(prefixstr)) { prefix = b; uristr = uristr.substring(prefixstr.length()); break; } } byte[] data = new byte[1 + uristr.length()]; data[0] = prefix; system.arraycopy(uristr.getbytes(), 0, data, 1, uristr.length()); ndefrecord record = new ndefrecord(ndefrecord.tnf_well_known, ndefrecord.rtd_uri, new byte[0], data); return record; } /** * 写入标签 * @param message * @param tag * @return */ public static boolean writetag(ndefmessage message, tag tag) { int size = message.tobytearray().length; try { ndef ndef = ndef.get(tag); if (ndef != null) { ndef.connect(); if (!ndef.iswritable()) { return false; } if (ndef.getmaxsize() < size) { return false; } ndef.writendefmessage(message); return true; } } catch (exception e) { } return false; } }
我们将手机贴近nfc标签,写入成功后验证一下是否成功写入:
我们看到,数据已经写入成功了,说明到此我们已经成功的读写nfc标签中的uri数据了。
到这里,ndef格式就大致说完了,那么接下来看一下非ndef格式的数据。
6.非ndef格式深度解析
1.mifareultralight数据格式
将nfc标签的存储区域分为16个页,每一个页可以存储4个字节,一个可存储64个字节(512位)。页码从0开始(0至15)。前4页(0至3)存储了nfc标签相关的信息(如nfc标签的序列号、控制位等)。从第5页开始存储实际的数据(4至15页)。
使用mifareultralight.get方法获取mifareultralight对象,然后调用mifareultralight.connect方法进行连接,并使用mifareultralight.writepage方法每次写入1页(4个字节)。也可以使用mifareultralight.readpages方法每次连续读取4页。如果读取的页的序号超过15,则从头开始读。例如,从第15页(序号为14)开始读。readpages方法会读取14、15、0、1页的数据。
2.读mifareultralight格式数据
public class readmuactivity extends basenfcactivity { @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_read_mu); } @override public void onnewintent(intent intent) { tag tag = intent.getparcelableextra(nfcadapter.extra_tag); string[] techlist = tag.gettechlist(); boolean havemifareultralight = false; for (string tech : techlist) { if (tech.indexof("mifareultralight") >= 0) { havemifareultralight = true; break; } } if (!havemifareultralight) { toast.maketext(this, "不支持mifareultralight数据格式", toast.length_short).show(); return; } string data = readtag(tag); if (data != null) toast.maketext(this, data, toast.length_short).show(); } public string readtag(tag tag) { mifareultralight ultralight = mifareultralight.get(tag); try { ultralight.connect(); byte[] data = ultralight.readpages(4); return new string(data, charset.forname("gb2312")); } catch (exception e) { } finally { try { ultralight.close(); } catch (exception e) { } } return null; } }
3.写mifareultralight格式数据
public class writemuactivity extends basenfcactivity { @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_write_mu); } @override public void onnewintent(intent intent) { tag tag = intent.getparcelableextra(nfcadapter.extra_tag); string[] techlist = tag.gettechlist(); boolean havemifareultralight = false; for (string tech : techlist) { if (tech.indexof("mifareultralight") >= 0) { havemifareultralight = true; break; } } if (!havemifareultralight) { toast.maketext(this, "不支持mifareultralight数据格式", toast.length_short).show(); return; } writetag(tag); } public void writetag(tag tag) { mifareultralight ultralight = mifareultralight.get(tag); try { ultralight.connect(); //写入八个汉字,从第五页开始写,中文需要转换成gb2312格式 ultralight.writepage(4, "北京".getbytes(charset.forname("gb2312"))); ultralight.writepage(5, "上海".getbytes(charset.forname("gb2312"))); ultralight.writepage(6, "广州".getbytes(charset.forname("gb2312"))); ultralight.writepage(7, "天津".getbytes(charset.forname("gb2312"))); toast.maketext(this, "写入成功", toast.length_short).show(); } catch (exception e) { } finally { try { ultralight.close(); } catch (exception e) { } } } }
我们将手机贴近nfc标签,写入成功后验证一下是否成功写入:
我们看到,弹出了“北京上海广州天津”,说明数据已经写入成功了,说明到此我们已经成功的读写nfc非ndef格式的数据了。
源码下载:demo
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。