概述
Android开发----NFC标签读写
前言
最近因为项目需要,特意学习了NFC的Android开发。加上之前并没有系统地学习过Android开发知识,起手比较困难,搞了半天才算一知半解。怎么办呢?不知道大家在平时学习,尤其是需要撰写论文或者报告的时候,有没有过这个感受。我们一开始收集和整理资料,往往是“雾里看花”,被一些“高级”的专业术语、概念铺陈搞得头晕脑胀。而一些仅有的博文也都是转来转去,其质量也良莠不齐,很多都解释得云里雾里。可是,在真正落笔撰文之时,很多之前不解的概念却渐渐清晰起来。究其原因,是因为文字需要逻辑性,而将知识转化为文字强迫我们在心中对已有的原材料进行梳理和归纳,同时还需要进行探索性地实践得出预期结论。一旦步入正轨,便可按图索骥。以前做调研,习惯于将搜索得到的文章用印象笔记剪辑保存(免费广告,强力推荐),却从未将自己的理解书以文字,虽然当时理解,由于理解不深,过段时间便忘。为了改变这种状况,还是向学霸大神们学习,做到笔耕不辍。一是建立自己的知识库体系,促进消化吸收;二是跟众多和我一样在成长路上的朋友们分享交流学习的心得,举一反三。
下面开始正题:
阅读之前
你需要有一点Android的开发基础知识,不要连AndroidManifest.xml文件干什么用的都不知道,那样的话看起来会很吃力哦。
NFC简介
NFC技术由非接触式射频识别(RFID)演变而来,由飞利浦半导体(现恩智浦半导体公司)、诺基亚和索尼共同研制开发,其基础是RFID及互连技术。近场通信(Near Field Communication,NFC)是一种短距高频的无线电技术,在单一芯片上结合感应式读卡器、感应式卡片和点对点的功能,能在13.56MHz频率运行于4厘米距离内,与兼容设备进行识别和数据交换。其传输速度有106 Kbit/秒、212 Kbit/秒或者424 Kbit/秒三种。目前近场通信已通过成为ISO/IEC IS 18092国际标准、ECMA-340标准与ETSI TS 102 190标准。现今这项技术在日韩被广泛应用。手机用户凭着配置了支付功能的手机就可以行遍全国:他们的手机可以用作机场登机验证、大厦的门禁钥匙、交通一卡通、信用卡、支付卡等等。
NFC标签的复杂度不一。简单的标签仅能够提供读写语义,有时编程域是一次性的,写完卡片就变成只读。更复杂一点的tag能够提供数学运算,拥有加密硬件保护区块的访问。最最复杂的tag包含操作环境,允许tag上执行的代码进行交互。我们还能够以各种格式来向tag中写入存储数据,但很多Android的API框架都是基于NFC论坛制定的NDEF标准。
广告时间,下图是我司的NFC标签产品,可以贴在任意表面平整的物体上。
目前众多手机厂商均有自带NFC内置芯片的手机型号,如小米3,三星的GalaxyNote 3,Nexus5,微软的Lumia 1020等等。苹果公司新推产品iPhone 6、iPhone 6 Plus甚至Apple Watch中均配备NFC芯片,可惜这几款产品的NFC功能被锁定,只能在移动支付Apple Pay中得到应用。但管中窥豹,可见一斑,NFC能够为人们的生活带来诸多便捷,将成为未来智能手机的必备配置。
NDEF
必须要先着重讲一下NDEF,因为NFC开发过程中基本上就是和该数据类型打交道。信息的传输总是离不开各种各样的协议,如用于Internet通信的HTTP协议、TCP/IP协议,蓝牙通信协议,WIFI的IEEE 802.11协议等等。协议中往往规定了通信的状态转换、时序以及数据交换格式。NFC的数据交换格式NFC Data Exchange Format(NDEF)是NFC组织约定的NFC tag中的数据格式。NDEF是轻量级的紧凑的二进制格式,可带有URL、vCard和NFC定义的各种数据类型。NDEF信息(NdefMessage)由各种数据记录(NdefRecord)组成,而各个记录由报头(Header)和有效载荷(Payload)组成,其中NDEF记录的数据类型和大小由记录载荷的报头注明,这里的报头包含3部分,分别为标识符(Identifier),数据长度(Length)和类型符(Type)。
关于NDEF规范的官方文档,可以到 万能的百度文库进行下载。(官方网站居然收费100美元,简直无情)
NFC开发(以下大部分翻译自android开发官网说明)
根据手机设备所扮演的具体角色不同,NFC操作主要有三种模式。
读/写器模式
NFC设备充当读写器,对被动式NFC标签进行读或者写。
P2P模式
NFC设备与其他NFC设备进行数据交换。例如使用NFC来分享图片、名片、音乐等。
仿真卡模式
NFC设备充当NFC卡,外部读卡器能够访问内部数据。例如可以充当公交卡、银行卡。
本文着重讲第一种模式。
开发平台
Eclipse + ADT + Android SDK + Galaxy Nexus
介绍Android开发平台如何搭建的博客有很多,这里就不再赘述了。
苹果是明确表示近期不会开放NFC的API给开发者了,而微软的WP系统我(yong)不(de)太(ren)了(tai)解(shao)。感谢Google,目前我们唯一能够玩玩的也就只有Android系统的NFC开发了。本文选用的测试机为三星的Galaxy Nexus,算是Google的亲儿子哈哈。
Tag分发系统
NDEF的读操作是通过Tag分发系统处理的,该系统分析被发现的NFC标签,对数据进行大致分类,然后启动某个对该数据感兴趣的APP。想要处理被扫描NFC标签的APP能通过声明一个intent过滤器来请求处理数据。
在显示屏被解锁时,Android设备总是在尝试寻找NFC标签,除非在设置中禁用NFC。当Android设备发现一个Tag标签,我们希望能够有由最合适的Activity去处理该intent,而不需要询问用户该用哪个APP。因为在扫描NFC标签时,手机往往与标签离得很近,如果需要用户决定用何种App,很有可能会迫使他们将设备从标签上拿开,从而会中断连接。好的做法是你所涉及的Activity应当只处理感兴趣的NFC标签,从而防止Activity选择界面出现。
为了达到该目的,你可以这样做:
1. 解析NFC标签并得到MIME类型或者标识tag中数据载荷的URI。
2. 将MIME类型或者URI和有效载荷封装进intent。
3. 启动一个基于该intent的activity。
NFC标签映射到MIME类型和URI
在你开始着手写NFCAPP时,一件重要的事就是要搞清楚NFC标签的不同类型,tag分发系统如何解析NFC标签,以及tag分发系统在检测到NDEF信息后所做的特殊操作。NFC标签涵盖很多技术,数据写入的方式也大相径庭。Android能够完美地支持NFC论坛制定的NDEF标准,所以请放心大胆地使用NDEF。
我们之前也介绍了NDEF的相关知识,下面将详细介绍Android如何处理NDEF格式的标签的。当Android设备扫描到包含有NDEF格式数据的标签时,它会先尝试解析该信息并找出数据的MIME类型或者URI标识。系统会先读取NdefMessage中的第一个NdefRecord,从而决定改如何解释整个NdefMessage。在一个风格良好的NdefMessage中,第一个NdefRecord包含如下4个域。
3-bitTNF(Type Name Format)
指明该如何解释variablelength type域,合法值见Table 1.
Variablelength type
描述记录的类型。如果使用TNF_WELL_KNOWN,则用该域来描述RecordType Definition(RTD)。合法的RTD值见Table 2.
Variablelength ID
record的UID。这个域不常用,但是如果你需要一个区分record的标记,就建一个ID给它。
Variablelength payload
读写的真实数据。不要以为第一个NdefRecord中就包含了所有你要的数据,还要把NdefMessage之后的NdefRecord考虑在内。
Table1. Supported TNFs and their mappings
Type Name Format (TNF) | Mapping |
TNF_ABSOLUTE_URI | URI based on the type field. |
TNF_EMPTY | Falls back to ACTION_TECH_DISCOVERED. |
TNF_EXTERNAL_TYPE | URI based on the URN in the type field. The URN is encoded into the NDEF type field in a shortened form:<domain_name>:<service_name>. Android maps this to a URI in the form: |
TNF_MIME_MEDIA | MIME type based on the type field. |
TNF_UNCHANGED | Invalid in the first record, so falls back toACTION_TECH_DISCOVERED. |
TNF_UNKNOWN | Falls back to ACTION_TECH_DISCOVERED. |
TNF_WELL_KNOWN | MIME type or URI depending on the Record Type Definition (RTD), which you set in the type field. See Table 2. for more information on available RTDs and their mappings. |
Table2. Supported RTDs for TNF_WELL_KNOWN and theirmappings
Record Type Definition (RTD) | Mapping |
RTD_ALTERNATIVE_CARRIER | Falls back to ACTION_TECH_DISCOVERED. |
RTD_HANDOVER_CARRIER | Falls back to ACTION_TECH_DISCOVERED. |
RTD_HANDOVER_REQUEST | Falls back to ACTION_TECH_DISCOVERED. |
RTD_HANDOVER_SELECT | Falls back to ACTION_TECH_DISCOVERED. |
RTD_SMART_POSTER | URI based on parsing the payload. |
RTD_TEXT | MIME type of |
RTD_URI | URI based on payload. |
tag分发系统尝试使用TNF和type域将某种MIME类型或者URI映射到NDEF信息。如果成功的话,它将带有实际载荷的信息封装在一个ACTION_NDEF_DICOVERED的intent中。但是,有时tag分发系统也会出现无法通过第一条NdefRecord来识别数据类型的情况:当NDEF数据无法被映射为某个MIME或者URI,或者当NFC标签并不包含以NDEF开头的数据。在这些情况下,Tag对象会含有tag实现技术信息,并且载荷会被封装进ACTION_TECH_DISCOVERED的intent中。
NFC标签分发到APP
当tag分发系统完成NFC标签信息封装并生成一个intent后,将该intent发送给那些经过过滤后能够接受该intent的APP。如果有多于一个APP能够处理该intent,那么用户也就必须通过Activity选择器来选择所需的Activity。tag分发系统定义了三种intent,根据优先级从高至低的次序罗列如下:
1. ACTION_NDEF_DISCOVERED
当扫描到的tag中包含有NDEF载荷且为已知类型,该intent将用来启动Activity。该intent的优先级最高,tag分发系统总是先于其他intent用该intent来启动Activity。
2. ACTION_TECH_DISCOVERED
如果manifest中没有注册处理ACTION_NDEF_DISCOVERED类型的intent,该intent将被用以启动Activity。如果tag中没有包含可以映射到MIME或者URI类型的数据,或者虽然没有包含NDEF数据,但是已知的tag技术,则该intent也会被直接启用。
3. ACTION_TAG_DISCOVERED
如果以上两个intent都没人疼,那么该intent就会启动。
下图是分发流程
AndroidManifest.xml
索取NFC权限
Ÿ 为了能够使用Android手机的NFC功能,需要在Manifest文件中添加相应的权限:
<uses-permission android:name="android.permission.NFC"/>
Ÿ 指定Android SDK的最小版本。自API 10之后,NFC的功能更加完善,因此这里我们将最小版本要求定位10.
<uses-sdk android:minSdkVersion="10"/>
Ÿ 利用uses-feature来使得你的APP在Android市场上只对拥有NFC的设备可见。当然,如果你的NFC对于你的APP并不是必须的,你也可以直接忽略该配置,而在运行时使用getDefaultAdapter()来检查设备是否有NFC硬件。
<uses-feature android:name="android.hardware.nfc"android:required="true"/>
过滤NFC Intent
我在自己调试的过程中,发现接触标签时自己设计的Activity完全没有反应,后来才搞明白原来是Intent Filter没有写对。
为了能够启动自己设计的App,你可以在Manifest文件中筛选1个、2个或3个NFC intents。但是,为了能够获得最大程度地控制,你应当时时考虑使用ACTION_NDEF_DISCOVERED。ACTION_TECH_DISCOVERED以及ACTION_TAG_DISCOVERED都是在没有发现NDEF载荷时ACTION_NDEF_DISCOVERED的次优项。况且,ACTION_TAG_DISCOVERED太笼统了,一旦设备中安装了其他能够筛选ACTION_NDEF_DISCOVERED或ACTION_TECH_DISCOVERED的App,你的应用将很有可能启动不了,除非你能够确保没有安装其他能够处理以上两种intents的应用。如果你对tag的类型十分清楚,请尽可能地将你的筛选器设置为ACTION_NDEF_DISCOVERED.
ACTION_NDEF_DISCOVERED
下面的xml段描述了能够筛选MIME类型为text/plain的ACTION_NDEF_DISCOVEREDintent筛选器。
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
下面的xml段描述了能够筛选URI为http://developer.android.com/index.html的ACTION_NDEF_DISCOVEREDintent筛选器。
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http"
android:host="developer.android.com"
android:pathPrefix="/index.html"/>
</intent-filter>
ACTION_TECH_DISCOVERED
如果你的activity筛选器需要筛选ACTION_TECH_DISCOVERED,那么你必须创建一个XML资源文件来指明你的activity到底支持哪些NFC技术。如果tech-list(通过getTechList()获取)是tag支持的技术的一个子集的话,就认为是匹配的。例如,如果某个tag支持MifareClassic、NdefFormatable和NfcA,那么为了匹配上,tech-list中就必须指定所有3种、或2种、或1种技术(且不包含其他技术)。
下面的XML段定义了所有技术。你可以根据需要将其中的某些项移除。将该文件保存到<project-root>/res/xml文件夹中,取个你中意的名字。
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.NfcB</tech>
<tech>android.nfc.tech.NfcF</tech>
<tech>android.nfc.tech.NfcV</tech>
<tech>android.nfc.tech.Ndef</tech>
<tech>android.nfc.tech.NdefFormatable</tech>
<tech>android.nfc.tech.MifareClassic</tech>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>
你也可以指定多个tech-list集。每个集合都是独立的,当你的activity和其中的某一个匹配的时候,就认为是匹配的。下面的XML段表明activity能够支持NfcA+Ndef科技或者NfcB+Ndef科技。
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
</resources>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.NfcB</tech>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
</resources>
什么?你不知道你所用的tag是何种类型的技术?你可以用getTechList()函数来获取:
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
tag = (Tag) intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag !=null) {
String[] techList = tag.getTechList();
for (String tech : techList) {
System.out.println(tech);
}
}
}
}
经检测,我用的tag技术为android.nfc.tech.NfcA + android.nfc.tech.Ndef
之后在你的AndroidManifest.xml文件里添加<meta-data>属性来关联你刚刚添加的XML文件。
<activity>
...
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED"/>
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter"/>
...
</activity>
ACTION_TAG_DISCOVERED
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED"/>
</intent-filter>
从tag intent中获取信息
如果一个activity因为NFC intent而启动,那么我们就可以从该intent中获取intent的信息。Intents可以根据所扫描的tag获取如下附加信息。
Ÿ EXTRA_TAG(必需):表示Tag的对象。
Ÿ EXTRA_NDEF_MESSAGES(可选):从tag中解析得到的NDEF数组。对于ACTION_NDEF_DISCOVERED而言是必需的。
Ÿ EXTRA_ID(可选):tag的底层ID
下面的代码检查ACTION_NDEF_DISCOVEREDintent并且将其中的附加信息载荷提取出来。(原文是在onResume中实现的,我觉得不妥。毕竟onResume指的是Activity由后台转到前台时触发的,跟扫描tag的动作无关。我这里改成onNewIntent,这样每当检测到一个tag intent时就会触发该过程,也就相当于每次扫描tag时都会触发一次下面的步骤。
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
NdefMessage[] msgs = {};
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawMsgs !=null){
msgs = new NdefMessage[rawMsgs.length];
for (inti = 0;i < rawMsgs.length;i++) {
msgs[i] = (NdefMessage)rawMsgs[i];
}
} else {
Log.i("MyNFCApp","No raw message detected");
}
}
for (NdefMessage msg: msgs) {
for (NdefRecordrecord:msg.getRecords()) {
if (record !=null) {
Log.i("MyNFCApp",newString(record.getPayload()));
}else{
Log.i("MyNFCApp","No record detected");
}
}
}
}
当然你也可以这么写:先把tag的对象提取出来,再用Ndef.get(tag)来获取Ndef对象,最后用getCachedNdefMessage()来获取NdefMessage。
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
NdefMessage msg = null;
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
tag = (Tag) intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag !=null){
Ndef ndef = Ndef.get(tag);
msg = ndef.getCachedNdefMessage();
} else {
Log.i("MyNFCApp","No raw message detected");
}
}
for (NdefRecord record: msg.getRecords()) {
if (record !=null) {
Log.i("MyNFCApp",newString(record.getPayload()));
} else {
Log.i("MyNFCApp","No record detected");
}
}
}
向tag中写入信息
首先我们得搞清楚写入数据的格式,这样我们才能向tag中正确传输数据。从Android 4.0(API level 14)开始,你可以用createUri()方法来自动构建Uri记录;从Android4.1 (API Level 16)开始,你可以用createExternal()和createMime()来帮你构建MIME类型和外部类型的NDEF记录。记住尽可能地使用这些方法以减少出错概率。
我们还将讨论如何创建record的相应intent筛选器。所有之后介绍的NDEF记录例子都应当保存在发送给tag的NdefMessage的第一个NdefRecord里。
TNF_ABSOLUTE_URI
在电脑术语中,统一资源标识符(Uniform Resource Identifier,或URI)是一个用于标识某一互联网资源名称的字符串。 该种标识允许用户对网络中(一般指万维网)的资源通过特定的协议进行交互操作。URI由包括确定语法和相关协议的方案所定义。Web上可用的每种资源 -HTML文档、图像、视频片段、程序等 - 由一个URI进行定位。URI由协议规范、主机名、标识符以及相对路径构成。形如:file://a:1234/b/c/d.txt或者http://b.c/d/e.f /。那么如何创建这种类型的NdefRecord呢?见下面的代码:
NdefRecord uriRecord = new NdefRecord(
NdefRecord.TNF_ABSOLUTE_URI,
"http://developer.android.com/index.html".getBytes(Charset.forName("US-ASCII")),
new byte[0],newbyte[0]);
NdefRecord的构造函数为
NdefRecord(shorttnf, byte[] type, byte[] id, byte[] payload);
在上面的例子中,第一个record只包含TNF信息,不包含payload,所以payload给个00。
相应地,intentfilter写成
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http"
android:host="developer.android.com"
android:pathPrefix="/index.html"/>
</intent-filter>
TNF_MIME_MEDIA
MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
NdefRecord mimeRecord = new NdefRecord(
NdefRecord.TNF_MIME_MEDIA,
"application/vnd.com.example.android.beam".getBytes(Charset.forName("US-ASCII")),
new byte[0],"Beam me up, Android!".getBytes(Charset.forName("US-ASCII")));
相应地,intent filter写成:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="application/vnd.com.example.android.beam"/>
</intent-filter>
TNF_WELL_KNOWNwith RTD_TEXT
public NdefRecord createTextRecord(Stringpayload,Locale locale,boolean encodeInUtf8){
byte[]langBytes= locale.getLanguage().getBytes(Charset.forName("US-ASCII"));
Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8"):Charset.forName("UTF-16");
byte[]textBytes= payload.getBytes(utfEncoding);
int utfBit = encodeInUtf8?0 : (1<<7);
char status =(char)(utfBit+ langBytes.length);
byte[] data=new byte[1+langBytes.length+ textBytes.length];
data[0]=(byte) status;
System.arraycopy(langBytes,0, data,1,langBytes.length);
System.arraycopy(textBytes,0, data,1+ langBytes.length, textBytes.length);
NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,NdefRecord.RTD_TEXT,newbyte[0], data);
return record;
}
相应地,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>
TNF_WELL_KNOWNwith RTD_URI
三种构建方式:
1. 直接采用createUri(String)方法
NdefRecord rtdUriRecord1 = NdefRecord.createUri("http://example.com");
2. 直接采用createUri(Uri)方法
Uri uri =newUri("http://example.com");
NdefRecord rtdUriRecord2 = NdefRecord.createUri(uri);
3. 手动创建NdefRecord
byte[]uriField="example.com".getBytes(Charset.forName("US-ASCII"));
byte[]payload=new byte[uriField.length+1]; //add 1 for the URI Prefix
byte payload[0]=0x01; //prefixes http://www. to the URI
System.arraycopy(uriField,0, payload,1,uriField.length); //appendsURI to payload
NdefRecord rtdUriRecord = new NdefRecord(
NdefRecord.TNF_WELL_KNOWN,NdefRecord.RTD_URI,newbyte[0],payload);
相应地,intent filter写成:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http"
android:host="example.com"
android:pathPrefix=""/>
</intent-filter>
TNF_EXTERNAL_TYPE
1. 采用createExternal()方法
byte[]payload;//assign to your data
String domain ="com.example";//usually your app's package name
String type ="externalType";
NdefRecord extRecord = NdefRecord.createExternal(domain, type,payload);
2. 手动创建NdefRecord
byte[]payload;
...
NdefRecord extRecord = new NdefRecord(
NdefRecord.TNF_EXTERNAL_TYPE,"com.example:externalType",newbyte[0], payload);
相应地,intent filter写成:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="vnd.android.nfc"
android:host="ext"
android:pathPrefix="/com.example:externalType"/>
</intent-filter>
使用TNF_EXTERNAL_TYPE来支持更通用的NFC部署环境,更好支持Android和非Android的设备。
完整测试代码
说了那么多,最后我们写一个最简单的测试代码,用来向NFC Tag中写入一首唐诗。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.chivalryw.android.testnfcapp1"
android:versionCode="1"
android:versionName="1.0">
<uses-permissionandroid:name="android.permission.NFC"/>
<uses-sdkandroid:minSdkVersion="14"android:targetSdkVersion="14"/>
<uses-feature
android:name="android.hardware.nfc"
android:required="true"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
<activity
android:name=".TestNfcApp2"
android:label="@string/app_name"
android:launchMode="singleTask"
>
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"></action>
<categoryandroid:name="android.intent.category.LAUNCHER"></category>
</intent-filter>
<intent-filter>
<actionandroid:name="android.nfc.action.NDEF_DISCOVERED"/>
<categoryandroid:name="android.intent.category.DEFAULT"/>
<dataandroid:mimeType="text/plain"/>
</intent-filter>
</activity>
</application>
</manifest>
Layout: main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/write_tag"/>
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/read_tag"/>
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="0.50"
android:text=""/>
</LinearLayout>
TestNfcActivity.java
packagecom.chivalryw.android.testnfcapp1;
importjava.nio.charset.Charset;
import java.util.Locale;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
importandroid.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
importandroid.view.View.OnClickListener;
import android.widget.Button;
importandroid.widget.TextView;
public class TestNfcApp2 extends Activity{
private Button writeButton;
private Button readButton;
private TextView textView;
private Tag tag;
private OnClickListenerwriteListener =new OnClickListener(){
@Override
public void onClick(View v) {
writeTag();
}
};
private OnClickListenerreadListener =new OnClickListener(){
@Override
public void onClick(View v) {
readTag();
}
};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
readButton = (Button) findViewById(R.id.button1);
writeButton = (Button) findViewById(R.id.button2);
textView = (TextView) findViewById(R.id.textView1);
writeButton.setOnClickListener(writeListener);
readButton.setOnClickListener(readListener);
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
public void readTag() {
NdefMessage msg = null;
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())){
tag = (Tag)getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag !=null){
Ndef ndef = Ndef.get(tag);
msg = ndef.getCachedNdefMessage();
for (NdefRecordrecord:msg.getRecords()) {
if (record !=null) {
Stringpayload= newString(record.getPayload());
StringactualData= payload.substring(3);
textView.setText(actualData);
textView.setTextColor(Color.WHITE);
} else {
Log.i("MyNFCApp","No record detected");
}
}
} else {
Log.i("MyNFCApp","No tag detected");
}
}
}
public void writeTag() {
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())){
tag = (Tag)getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag !=null){
Ndef ndef = Ndef.get(tag);
try {
ndef.connect();
String payload = "春眠不觉晓,处处闻啼鸟。夜来风雨声,花落知多少。";
NdefRecord record = createTextRecord(payload, Locale.getDefault(),true);
NdefMessage msg = new NdefMessage(new NdefRecord[] {record});
int maxSize = ndef.getMaxSize();
if (msg.toByteArray().length >maxSize) {
Log.i("MyNFCApp","要写入的数据长度已经超过限定值");
return;
} else {
ndef.writeNdefMessage(msg);
}
} catch (Exceptione) {
e.printStackTrace();
} finally {
try {
ndef.close();
}catch(Exceptione) {
e.printStackTrace();
}
}
}
}
}
public NdefRecord createTextRecord(Stringpayload, Localelocale,booleanencodeInUtf8) {
byte[] langBytes = locale.getLanguage().getBytes(Charset.forName("US-ASCII"));
Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8"): Charset.forName("UTF-16");
byte[] textBytes = payload.getBytes(utfEncoding);
int utfBit = encodeInUtf8? 0 : (1 << 7);
char status = (char)(utfBit+langBytes.length);
byte[] data = newbyte[1+langBytes.length +textBytes.length];
data[0] = (byte)status;
System.arraycopy(langBytes, 0,data, 1,langBytes.length);
System.arraycopy(textBytes, 0,data, 1 +langBytes.length,textBytes.length);
NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,NdefRecord.RTD_TEXT,newbyte[0],data);
return record;
}
}
真机测试
接触tag,按Write将古诗写入,按Read将在TextView中显示刚写入的古诗内容,如下图。
======================================
谢谢阅读!转载请注明出处。
最后
以上就是时尚外套为你收集整理的Android开发——NFC标签读写Android开发----NFC标签读写的全部内容,希望文章能够帮你解决Android开发——NFC标签读写Android开发----NFC标签读写所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复