NFC即近场通信(Near Field Communication)的英文缩写.
读取NFC标签
当开发NFC的相关应用程序时,首先我们需要在AndroidManifest.xml清单文件中,配置相关内容。
1、硬件要求:
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
2、权限要求:
<uses-permission android:name="android.permission.NFC" />
3.sdk版本要求:
<uses-sdk android:minSdkVersion="14"/>
4.NFC内容识别原理
当一个Android设备用于扫描一个NFC标签时,其系统将使用自己的标签分派系统解码传入的有效荷载。这个标签分派系统会分析标签,将数据分类,并使用Intent启动一个应用程序接受数据。
为使应用程序接受数据NFC数据,需要踢哪加一个Activity IntentFilter来监听某个Intent动作:
a.NfcAdapter.ACTION_NDEF_DISCOVERED 优先级最高、也是最具体的NFC消息动作。使用这个动作的Intent包括MimeType和/或URI数据。最好的做法是只要有可能,就监听这个广播,因为其extra数据允许更加具体地定义要响应的标签。
b.NfcAdapter.ACTION_TECH_DISCOVERED 当NFC技术已知、但是标签不包含数据(或者包含数据不能被映射为MineType或者URI)时广播这个动作。
c.NfcAdapter.ACTION_DISCOVERED 如果从未知技术收到一个标签,则使用此Intent动作广播该标签。
NFC两种识别过程:
第一种:使用前台分派系统
默认情况下,标签分派系统会根据标准的Intent解析过程确定哪个应用程序应该收到特定的标签,在Intent解析过程中,位于前台的Activity并不必其他应用程序优先级更高,因此,如果几个应用程序都被注册为接受扫描的标签,用户就需要选择使用哪个应用程序,即使此时你的应用程序位于前台。
通过使用前台分派系统,可以指定特定的一个具有高优先级的Activity使得当它位于前台时成为默认接受标签的应用程序,使用NFCAdapter的enable/disableForegroundDispatch方法可以切换前台分派系统。只有当一个Activity位于前台时才能使用前台分派系统,所以应该在onResume和onPause处理程序内启用和禁用改系统。
以下代码在onCreate方法中实现。
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
Intent nfcIntent = new Intent(this, getClass());
nfcIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
nfcPendingIntent = PendingIntent.getActivity(this, requestCode, nfcIntent, flags);
IntentFilter ndefIntentFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
ndefIntentFilter.addCategory(Intent.CATEGORY_DEFAULT);
try {
ndefIntentFilter.addDataType("text/plain");
} catch (IntentFilter.MalformedMimeTypeException e) {
e.printStackTrace();
}
IntentFilter tagIntentFilter = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
tagIntentFilter.addCategory(Intent.CATEGORY_DEFAULT);
IntentFilter techIntentFilter = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
techIntentFilter.addCategory(Intent.CATEGORY_DEFAULT);
IntentFilterArray = new IntentFilter[]{ndefIntentFilter, tagIntentFilter, techIntentFilter};
techListArray = new String[][]{
new String[]{
NfcF.class.getName()
}
};
protected void onResume() {
super.onResume();
nfcAdapter.enableForegroundDispatch(
this,
nfcPendingIntent, //用于打包Tag Intent的Intent
IntentFilterArray, //用于声明想要拦截的Intent的Intent Filter数组
techListArray // 想要处理的标签技术
);
}
使用前台分派系统,返回的Intent是通过onNewIntent方法中获取到的。
protected void onNewIntent(Intent intent){
String action = intent.getAction();
if(TextUtils.isEmpty(action)){
return;
}
if(NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)){
parseNdef(intent); //方法实现在后面
}else if(NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)){
parseTech(intent);//方法实现在后面
}else if(NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)){
parseTag(intent); //方法实现在后面
}
}
第二种:一般识别过程
一般识别过程就是不使用前台分排系统,这种情况和前台分派系统不一样,不需要进入到响应的Activity的前台(也就是当前Activity的可视化页面),但是她需要在响应的Activity对应的清单文件内容中添加IntentFilter。
<activity
android:name=".ShowActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</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>
一般情况下该Activity的启动模式为SingleTop或者SingleTask,是为了方便接受标签返回的Intent。而Intent的返回一般则该Activity生命周期方法onResume()中获得。
protected void onResume(){
super.onReume();
Intent intent = getIntent();
String action = intent.getAction();
if(TextUtils.isEmpty(action)){
return;
}
if(NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)){
parseNdef(intent); //方法实现在后面
}else if(NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)){
parseTech(intent);//方法实现在后面
}else if(NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)){
parseTag(intent); //方法实现在后面
}
}
以上大概已经阐述清楚了NFC标签的识别过程,下面就阐述一下,如何具体识别NFC标签中包含的数据内容。
1、MifareClassic射频卡
一般来说,给予MifareClassic的射频卡,一般内存大小有3种:
1K: 16个分区(sector),每个分区4个块(block),每个块(block) 16个byte数据。
2K: 32个分区,每个分区4个块(block),每个块(block) 16个byte数据。
4K:64个分区,每个分区4个块(block),每个块(block) 16个byte数据。
2、NFC射频卡结构
读取NFC标签内容需要知道标签的结构,当然以上图片显示的是1kb大小的NFC射频卡。
要想获得想要的内容,需要获得数据区的数据,由图可知每个块(block)的大小为16字节(byte),当然为了准确获取数据取内容,还需要根据写标签的具体结构操作。
解析NFC方法代码(以上作注释的方法)
private String parseTag(Intent intent) {
int sectorCount = 0;
byte[] content = {};
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
MifareClassic mifageClassic = MifareClassic.get(tag);
try {
mifageClassic.connect();
sectorCount = mifageClassic.getSectorCount();
int byteIndex = 0;
for (int i = 1; i < sectorCount; i++) {
boolean auth = mifageClassic.authenticateSectorWithKeyA(i,MifareClassic.KEY_DEFAULT);
if (!auth) {
return null;
}
int blockCount = mifageClassic.getBlockCountInSector(i);
int blockIndex = mifageClassic.sectorToBlock(i);
for (int j = 0; j < blockCount; j++) {
if (j + 1 == blockCount) {
continue;
}
byte buffer[] = new byte[16];
buffer = mifageClassic.readBlock(blockIndex);
if (blockIndex == 4) {
System.arraycopy(buffer, 9, content, byteIndex, 6);
byteIndex += 6;
} else {
System.arraycopy(buffer, 0, content, byteIndex, 16);
byteIndex += 16;
}
blockIndex++;
Log.i("content_buffer" + blockIndex, new String(content));
}
}
String contentStr = new String(content);
return contentStr;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private String parseNdef(Intent intent) {
wait2Scan(true);
Parcelable[] messages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
NdefMessage ndefs[];
String data = "";
if (messages != null) {
ndefs = new NdefMessage[messages.length];
for (int i = 0; i < messages.length; i++) {
ndefs[i] = (NdefMessage) messages[i];
}
if (ndefs != null && ndefs.length > 0) {
NdefRecord record = ndefs[0].getRecords()[0];
byte[] payload = record.getPayload();
String textEncoding = ((payload[0] & 0200) == 0) ? "UTF-8" : "UTF-16";
int languageCodeLength = payload[0] & 0077;
try {
data = new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
textContentTips.setText(data);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
return data;
}
private String parseTech(Intent intent) {
return parseTag(intent);
}