本篇博客是为了解决以下的问题:
- NFC是什么
- NFC的原理
- NFC工作模式和实用场景
- NFC相比于蓝牙的优势
- Android读取和写入NDEF格式的NFC芯片
- Android读取和写入非NDEF格式的NFC芯片
NFC是什么
NFC是Near Field Communication的简称,意思为近距离无线通讯技术,简单的说是能够在短距离之内与兼容设备实现数据交换的技术。
NFC的原理
NFC是使用非接触式射频技术实现的数据交换技术,在13.56MHz频率20厘米距离内进行传输,其传输速度有106 Kbit/秒、212 Kbit/秒或者424 Kbit/秒三种。
NFC工作模式和实用场景
NFC工作模式分为三种:
- 读卡器模式,指含有NFC芯片的标签,可以被支持NFC的手机或者专门读卡器设备读取或者写入数据。一般可以用于巡更,用支持NFC的手机到定点区读取NFC芯片的数据然后传输到后台表示已巡视了定点区。
- 仿真卡模式,指含有IC卡的NFC芯片,可以使用支持NFC的手机或者NFC射频器读取出NFC芯片中IC卡的信息。常见的由交通卡,信用卡。
- 点对点模式,指两台NFC设备之间互相都可以进行数据 交换。
NFC相比于蓝牙的优势
蓝牙4.0相比于以前版本,功耗低,传输速度快,可以在10m之内使用。
NFC技术优势是创建连接快,传输速度也不慢,存储芯片中的数据可以加密安全性较高,在20cm之内使用。
Android读取和写入NDEF格式的NFC芯片
首先说明NFC芯片可以根据厂家定制,一次性写入然后只能读,简单的可读可写。目前Android SDK API主要支持NFC论坛标准(Forum Standard),这种标准被称为NDEF。
1.支持NFC功能,添加权限
<uses-permission android:name="android.permission.NFC" />
<!-- 要求当前设备必须要有NFC芯片 -->
<uses-feature android:name="android.hardware.nfc" android:required="true" />
2.当前Activity的清单文件中的设置
<activity
android:name="com.example.NFCActivity"
android:launchMode="singleTask" >
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
3.当前Activity声明周期的判断
private NfcAdapter mNfcAdapter;
private PendingIntent mPendingIntent;
@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();
//后面两个null,null设置处理优于所有其他NFC的处理
if (mNfcAdapter != null)
mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null);
}
/**
* 暂停Activity,界面获取焦点,按钮可以点击
*/
@Override
public void onPause() {
super.onPause();
//恢复默认状态
if (mNfcAdapter != null)
mNfcAdapter.disableForegroundDispatch(this);
}
4.读取NFC芯片中内容
@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();
}
}
5.写入NFC芯片
@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;
}
核心的代码已经贴出,查看详情的代码,请点击这里,已经简化成工具类,可直接拷贝使用。
Android读取和写入非NDEF格式的NFC芯片
对于非NDEF格式数据的NFC芯片,其中的数据格式是自定义的,但是存储到芯片中都是以字节码的形式。想深入学习的朋友可以,参考Github源码,编译生成的APK能够读取部分地区交通卡信息。
感谢以下知识的分享:
Android 高级开发——NFC标签开发深度解析
NFC百度百科