前言:上一节讲了Android中NFC的一些概念和基本知识,以及NFC传输数据的格式,最后讲了举了一个给NFC标签中写入微信包名,然后通过用标签靠近手机自动打开微信的功能,本节将继续强化NFC的概念以及更深层次的理解,最后附上上一节的链接:https://blog.csdn.net/helloworld19870427/article/details/86020306
一、NFC基础
1、从NFC标签中读取NDEF数据是用标签调度系统来处理的,它会分析被发现的NFC标签,对数据进行适当的分类,并启动对该类数据感兴趣的应用程序。想要处理被扫描到NFC标签的应用程序会声明一个Intent过滤器,并请求处理数据。
Android Beam功能允许设备把一个NDEF消息推送到物理/硬件上相互监听的另一个设备上。这种交互提供了比其他无线技术(如蓝牙)更容易的发送数据的方法。因为NFC不需要手动的设备发现或配对要求,两个设备在接近到一定范围时会自动的连接。Android Beam通过一组NFC API来使用,以便应用程序能够在设备之间来传输信息。例如,通信录、浏览器以及YouTube等应用程序都使用Android Beam来跟其他设备共享通信录、网页和视频,如果数据过大,Android Beam底层会通过蓝牙来传输。
2、NFC标签调度系统(The Tag Dispatch System)
通常是在设备的设置菜单中NFC开关被关闭,否则Android设备会在非锁屏的状态下搜索NFC,当Android设备发现NFC标签时,期望的行为是用最合适的Activity来处理该Intent,而不是询问用户使用什么应用程序。因为设备只能在很短的范围内扫描到NFC标签,强制的让用户手动的选择一个Activity,会导致设备离开NFC标签,从而中断该连接,你应该开发你自己的Activity来处理你所关心的NFC标签,从而阻止选择器的操作。为了帮助你达到这个目标,Android提供了特殊的标签调度系统,来分析扫描到的NFC标签,通过解析数据,在被扫描到的数据中尝试找到感兴趣的应用程序,具体做法如下:
(1)解析NFC标签并搞清楚标签中标识数据负载的MIME类型或URI
(2)把MIME类型或URI以及数据负载封装到一个Intent中
(3)基于Intent来启动Activity
NDEF数据格式:NFC Data Exchange Format,NFC数据交换格式,NFC组织约定的NFC tag中的数据格式,NDEF是轻量级的紧凑的二进制格式,可带有URL、vCard和NFC滇南桂的各种数据类型。
NDEF数据被封装在一个NdefMessage中,该消息包含了一条或者多条NdefRecord,Android也支持其他不包含NDEF数据类型的标签,你能够使用android.nfc.tech包中的类来工作,要使用其他类型标签来工作涉及到编写自己的跟该标签通信的协议栈,因此我们还是使用NDEF比较好,不要给自己增加额外的难度。
3、NDEF格式标签的读写:
我们可以通过Tag对象的getTechList()获取到标签的技术类型,只有支持NDEF格式的标签才可以进行NDEF格式的读写操作。
读写NDEF格式标签主要有两个类:
NdefMessage:描述NDEF格式的信息,实际上我们写入NFC标签的就是NdefMessage对象。
NdefRecord:描述NDEF信息的一个信息段,一个NdefMessage可能包含一个或者多个NdefRecord。
获取Ndef对象: Ndef ndef = Ndef.get(mTag);
创建NdefRecord,Android为我们提供了创建NdefRecord的方法,是我们可以轻松创建一个NdefRecord对象
- NdefRecord.createApplicationRecord(String packageName) // 写入一个包名
- NdefRecord.createUri(Uri uri) // 写入Uri对象
- NdefRecord.createUri(String uriString) // 写入uriString
- NdefRecord.createTextRecord(String languageCode, String text) // 写入文本
NdefRecord.createTextRecord(String languageCode, String text)最小兼容sdk版本是21,也就是Android 5.0操作系统,不过都支持NFC了应该不用去考虑安卓5.0以下系统了吧。
当Android设备扫描到包含NDEF格式数据的NFC标签时,它会解析该消息,并尝试搞清楚数据的MIME类型或URI标识。首先系统会读取消息(NdefMessage)中的第一条NdefRecord,来判断如何解释整个NDEF消息(一个NdefMessage消息能够有多条NdefRecord)。,在格式良好的NdefMessage消息中,第一条NdefRecord包含以下字段信息:
(1)创建文本NdefRecord
/**
* 创建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;
}
(2)创建NdefMessage,并写入Ndef标签
//往Ndef标签中写数据
private void writeNdef(){
if (mTag==null){
Toast.makeText(this,"不能识别的标签类型!",Toast.LENGTH_SHORT);
finish();
return;
}
Ndef ndef=Ndef.get(mTag);//获取ndef对象
if (!ndef.isWritable()){
Toast.makeText(this,"该标签不能写入数据!",Toast.LENGTH_SHORT);
return;
}
NdefRecord ndefRecord=createTextRecord(writeEdt.getText().toString());//创建一个NdefRecord对象
NdefMessage ndefMessage=new NdefMessage(new NdefRecord[]{ndefRecord});//根据NdefRecord数组,创建一个NdefMessage对象
int size=ndefMessage.getByteArrayLength();
if (ndef.getMaxSize()<size){
Toast.makeText(this,"标签容量不足!",Toast.LENGTH_SHORT);
return;
}
try {
ndef.connect();//连接
ndef.writeNdefMessage(ndefMessage);//写数据
Toast.makeText(this,"数据写入成功!",Toast.LENGTH_SHORT);
} catch (IOException e) {
e.printStackTrace();
} catch (FormatException e) {
e.printStackTrace();
}finally {
try {
ndef.close();//关闭连接
} catch (IOException e) {
e.printStackTrace();
}
}
}
(3)读NDEF文本数据
//读取Ndef标签中数据
private void readNdef(){
if (mTag==null){
Toast.makeText(this,"不能识别的标签类型!",Toast.LENGTH_SHORT);
finish();
return;
}
Ndef ndef=Ndef.get(mTag);//获取ndef对象
try {
ndef.connect();//连接
NdefMessage ndefMessage=ndef.getNdefMessage();//获取NdefMessage对象
if (ndefMessage!=null) readEdt.setText(parseTextRecord(ndefMessage.getRecords()[0]));
Toast.makeText(this,"数据读取成功!",Toast.LENGTH_SHORT);
} catch (IOException e) {
e.printStackTrace();
} catch (FormatException e) {
e.printStackTrace();
}finally {
try {
ndef.close();//关闭链接
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 解析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();
}
}