??文章介紹NDEF和MifareClassic兩種格式數(shù)據(jù)讀寫標(biāo)簽的方法扇商。 在進行Nfc的讀寫之前巩割,了解卡片的存儲結(jié)構(gòu)是非常重要的(非常重要广鳍,尤其對理解MifareClassic數(shù)據(jù)格式)说莫。
卡片常用的數(shù)據(jù)格式:
MifareClassic數(shù)據(jù)格式就是NfcA。
IsoDep:各種交通卡膝蜈。如:北京市政交通卡锅移。
NfcB:二代身份證。
NfcF:Felica用的是饱搏。
NfcV:德州儀器的VicinityCard卡用的是帆啃。
Ndef:安卓主推的傳輸數(shù)據(jù)格式。
一窍帝、Nfc卡片存儲結(jié)構(gòu)
M1卡:容量1K努潘。16個扇區(qū)(sector),每個扇區(qū)4個塊(block)坤学,每個塊(block) 16個byte數(shù)據(jù)
每個扇區(qū)的前三個塊疯坤,可用于存貯數(shù)據(jù)。
第0扇區(qū)的第一個塊除外深浮,它用于存放廠商數(shù)據(jù)压怠,已經(jīng)固化,不可更改飞苇。
每個扇區(qū)的第四個塊為控制塊菌瘫,用于控制該扇形區(qū)的訪問權(quán)限。它的前6個字節(jié)為KeyA布卡,后6個字節(jié)為KeyB雨让,中間的4個字節(jié)為存取控制。KeyA和KeyB記錄了兩種加密方式忿等,存取控制里記錄了該扇區(qū)的讀寫權(quán)限支持KeyA驗證還是KeyB驗證栖忠。
扇形區(qū) | 塊 | 塊中的數(shù)據(jù) |
---|---|---|
扇區(qū) 0(sectorIndex 0) | 塊 0(blockIndex 0) | 廠家數(shù)據(jù)不可修改 |
扇區(qū) 0(sectorIndex 0) | 塊 1(blockIndex 1) | 0........................15 (byte) |
扇區(qū) 0(sectorIndex 0) | 塊 2(blockIndex 2) | 0........................15 (byte) |
扇區(qū) 0(sectorIndex 0) | 塊 3(blockIndex 3) | 密碼A?存取控制?密碼B |
: | : | : |
: | : | : |
扇區(qū) 15(sectorIndex 15) | 塊 60(blockIndex 60) | 0........................15 (byte) |
扇區(qū) 15(sectorIndex 15) | 塊 61(blockIndex 61) | 0........................15 (byte) |
扇區(qū) 15(sectorIndex 15) | 塊 62(blockIndex 62) | 0........................15 (byte) |
扇區(qū) 15(sectorIndex 15) | 塊 63(blockIndex 63) | 密碼A?存取控制?密碼B |
二、NFC數(shù)據(jù)過濾器
NFC有三種過濾器:ACTION_NDEF_DISCOVERED贸街,ACTION_TECH_DISCOVERED庵寞,ACTION_TAG_DISCOVERED。
1薛匪、過濾器介紹
ACTION_NDEF_DISCOVERED
當(dāng)卡片中包含NDEF格式的數(shù)據(jù)捐川,將使用該模式啟動Activity。
ACTION_TECH_DISCOVERED
當(dāng)卡片中包含非NDEF格式的數(shù)據(jù)(如:MifareClassic)將使用該模式啟動Activity逸尖。
ACTION_TAG_DISCOVERED
過濾規(guī)則是最不嚴格的古沥,只要符合NFC規(guī)范中的任一種則都會響應(yīng)。
優(yōu)先級順序冷溶,上面三種規(guī)則依次遞減
- 如果卡片是Ndef格式的數(shù)據(jù)
如果activity注冊了ACTION_NDEF_DISCOVERED红碑,會優(yōu)先響應(yīng)ACTION_NDEF_DISCOVERED彪置。
如果activity沒有注冊ACTION_NDEF_DISCOVERED电抚,會優(yōu)先響應(yīng)ACTION_TECH_DISCOVERED任岸。
如果activity沒有注冊ACTION_NDEF_DISCOVERED和ACTION_TECH_DISCOVERED,會響應(yīng)ACTION_TAG_DISCOVERED苗胀。 - 如果卡片是非Ndef格式的數(shù)據(jù)
即使activity注冊了ACTION_NDEF_DISCOVERED襟诸,也不會響應(yīng)ACTION_NDEF_DISCOVERED瓦堵。
如果activity注冊了ACTION_TECH_DISCOVERED,會優(yōu)先響應(yīng)ACTION_TECH_DISCOVERED歌亲。
如果activity沒有注冊ACTION_TECH_DISCOVERED菇用,會響應(yīng)ACTION_TAG_DISCOVERED。
ACTION_TAG_DISCOVERED的優(yōu)先級是最低陷揪,activity無法獲得更詳細的一些信息惋鸥,所以不會一開始就選擇TAG_DISCOVERED來響應(yīng)。
2悍缠、過濾器注冊
靜態(tài)注冊
在AndroidMainfest.xml中聲明卦绣。在需要處理NFC讀卡的Activity標(biāo)簽里聲明過濾器。當(dāng)在桌面刷卡的時候飞蚓,手機系統(tǒng)會彈窗出現(xiàn)可以處理該Intent的APP滤港。
<activity
android:name=".activity.InspectionsActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/inspection"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden|adjustPan">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<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>
篩選NFC卡片的類型。
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter"/>
在res>xml目錄下添加nfc_tech_filter.xml
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcB</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcF</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcV</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NdefFormatable</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.MifareClassic</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>
動態(tài)注冊
在代碼里聲明過濾器趴拧,在桌面刷卡的時候溅漾,不會彈出可響應(yīng)APP的彈窗。
Intent intent = new Intent(activity, activity.getClass());
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
mPendingIntent = PendingIntent.getActivity(activity, 0, intent, 0);
//intentFilter過濾----ndef
IntentFilter ndefFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
//文本類型
ndefFilter.addDataType("text/plain");
} catch (IntentFilter.MalformedMimeTypeException e) {
e.printStackTrace();
}
//intentFilter過濾----非ndef
IntentFilter techFilter = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
//intentFilter過濾器列表
mIntentFilter = new IntentFilter[]{ndefFilter, techFilter};
//匹配的數(shù)據(jù)格式列表
mTechList = new String[][]{
{MifareClassic.class.getName()},
{NfcA.class.getName()},
{Ndef.class.getName()},
{NdefFormatable.class.getName()}};
三著榴、NFC的工具類
1添履、初始化
mNfcAdapter = NfcAdapter.getDefaultAdapter(activity);
if (mNfcAdapter == null) {
ToastUtils.showShort("設(shè)備不支持NFC功能!");
} else {
if (!mNfcAdapter.isEnabled()) {
showSettingDailog();
} else {
Logger.e(TAG, "NFC功能已打開!");
init();
}
}
2、啟動
/**
* Nfc監(jiān)聽intent
*/
public void enableForegroundDispatch() {
if (mNfcAdapter != null && mNfcAdapter.isEnabled()) {
mNfcAdapter.enableForegroundDispatch(activity, mPendingIntent, mIntentFilter, mTechList);
}
}
2兄渺、關(guān)閉
/**
* 取消監(jiān)聽Nfc
*/
public void disableForegroundDispatch() {
if (mNfcAdapter != null && mNfcAdapter.isEnabled()) {
mNfcAdapter.disableForegroundDispatch(activity);
}
}
3缝龄、讀取NDEF格式
/**
* 讀取Ndef的數(shù)據(jù)
*
* @return
*/
private String readNdef(Intent intent) {
String info = "";
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(
NfcAdapter.EXTRA_NDEF_MESSAGES);
NdefMessage msgs[] = null;
if (rawMsgs != null) {
msgs = new NdefMessage[rawMsgs.length];
for (int i = 0; i < rawMsgs.length; i++) {
msgs[i] = (NdefMessage) rawMsgs[i];
}
NdefRecord record = msgs[0].getRecords()[0];
if (record != null) {
byte[] payload = record.getPayload();
//下面代碼分析payload:狀態(tài)字節(jié)+ISO語言編碼(ASCLL)+文本數(shù)據(jù)(UTF_8/UTF_16)
//其中payload[0]放置狀態(tài)字節(jié):如果bit7為0,文本數(shù)據(jù)以UTF_8格式編碼挂谍,如果為1則以UTF_16編碼
//bit6是保留位,默認為0
/*
* payload[0] contains the "Status Byte Encodings" field, per the
* NFC Forum "Text Record Type Definition" section 3.2.1.
*
* bit7 is the Text Encoding Field.
*
* if (Bit_7 == 0): The text is encoded in UTF-8 if (Bit_7 == 1):
* The text is encoded in UTF16
*
* Bit_6 is reserved for future use and must be set to zero.
*
* Bits 5 to 0 are the length of the IANA language code.
*/
String textEncoding = ((payload[0] & 0x80) == 0) ? "UTF-8"
: "UTF-16";
//處理bit5-0瞎饲。bit5-0表示語言編碼長度(字節(jié)數(shù))
int languageCodeLength = payload[0] & 0x3f;
//獲取語言編碼(從payload的第2個字節(jié)讀取languageCodeLength個字節(jié)作為語言編碼)
try {
String languageCode = new String(payload, 1, languageCodeLength,
"US-ASCII");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//解析出實際的文本數(shù)據(jù)
try {
info = new String(payload, languageCodeLength + 1,
payload.length - languageCodeLength - 1, textEncoding);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
return info;
}
5口叙、寫NDEF格式
/**
* 往nfc寫入數(shù)據(jù)
*/
public void writeNFCToTag(String text, Intent intent) throws IOException, FormatException {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//生成語言編碼的字節(jié)數(shù)組,中文編碼
byte[] langBytes = Locale.CHINA.getLanguage().getBytes(
Charset.forName("US-ASCII"));
//將要寫入的文本以UTF_8格式進行編碼
Charset utfEncoding = Charset.forName("UTF-8");
//由于已經(jīng)確定文本的格式編碼為UTF_8嗅战,所以直接將payload的第1個字節(jié)的第7位設(shè)為0
byte[] textBytes = text.getBytes(utfEncoding);
int utfBit = 0;
//定義和初始化狀態(tài)字節(jié)
char status = (char) (utfBit + langBytes.length);
//創(chuàng)建存儲payload的字節(jié)數(shù)組
byte[] data = new byte[1 + langBytes.length + textBytes.length];
//設(shè)置狀態(tài)字節(jié)
data[0] = (byte) status;
//設(shè)置語言編碼
System.arraycopy(langBytes, 0, data, 1, langBytes.length);
//設(shè)置實際要寫入的文本
System.arraycopy(textBytes, 0, data, 1 + langBytes.length,
textBytes.length);
NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,
NdefRecord.RTD_TEXT, new byte[0], data);
NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{record});
//轉(zhuǎn)換成字節(jié)獲得大小
int size = ndefMessage.toByteArray().length;
//2.判斷NFC標(biāo)簽的數(shù)據(jù)類型(通過Ndef.get方法)
Ndef ndef = Ndef.get(tag);
//判斷是否為NDEF標(biāo)簽
if (ndef != null) {
ndef.connect();
//判斷是否支持可寫
if (!ndef.isWritable()) {
return;
}
//判斷標(biāo)簽的容量是否夠用
if (ndef.getMaxSize() < size) {
return;
}
//3.寫入數(shù)據(jù)
ndef.writeNdefMessage(ndefMessage);
Logger.e(TAG, "寫入NDEF成功");
} else {
//當(dāng)我們買回來的NFC標(biāo)簽是沒有格式化的妄田,或者沒有分區(qū)的執(zhí)行此步
//Ndef格式類
NdefFormatable format = NdefFormatable.get(tag);
//判斷是否獲得了NdefFormatable對象,有一些標(biāo)簽是只讀的或者不允許格式化的
if (format != null) {
//連接
format.connect();
//格式化并將信息寫入標(biāo)簽
format.format(ndefMessage);
Logger.e(TAG, "格式化并寫入NDEF成功");
} else {
Logger.e(TAG, "格式化并寫入NDEF失敗");
}
}
}
6驮捍、讀取MifareClassic格式
private String readMifareClassic(Intent intent) {
String info = "";
List<byte[]> list = new ArrayList<>();
boolean auth = false;
//讀取TAG
MifareClassic mfc = MifareClassic.get(getNFCTag(intent));
try {
mfc.connect();
if (mfc.isConnected()) {
int sectorCount = mfc.getSectorCount();//獲取TAG中包含的扇區(qū)數(shù)
for (int j = 0; j < sectorCount; j++) {
auth = mfc.authenticateSectorWithKeyA(j,
MifareClassic.KEY_NFC_FORUM);
int bCount;
int bIndex;
if (auth) {
// 讀取扇區(qū)中的塊
bCount = mfc.getBlockCountInSector(j);
bIndex = mfc.sectorToBlock(j);
for (int i = 0; i < bCount; i++) {
byte[] data = mfc.readBlock(bIndex);
if (i < bCount - 1) {
if (!bytesToHexString(data).equals("0x00000000000000000000000000000000")) {
list.add(data);
}
}
bIndex++;
}
}
}
if (list.size() > 0) {
byte[] aa = new byte[list.size() * list.get(0).length];
for (int i = 0; i < list.size(); i++) {
byte[] bytes = list.get(i);
System.arraycopy(bytes, 0, aa, bytes.length * i, bytes.length);
}
info = new String(trim(aa), Charset.forName("utf-8"));
}
}
} catch (Exception e) {
Logger.e(TAG, e);
} finally {
try {
mfc.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
return info;
}
7疟呐、寫MifareClassic格式
public void writeMifareClassic(String data, Intent intent) throws IOException {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
MifareClassic mfc = MifareClassic.get(tag);
try {
mfc.connect();
int sectorCount = mfc.getSectorCount();//獲取TAG中包含的扇區(qū)數(shù)
for (int j = 0; j < sectorCount; j++) {
if (mfc.authenticateSectorWithKeyA(j,
MifareClassic.KEY_NFC_FORUM)) {
// 讀取扇區(qū)中的塊
int bCount = mfc.getBlockCountInSector(j);
int bIndex = mfc.sectorToBlock(j);
for (int i = 0; i < bCount - 1; i++) {
if (!bytesToHexString(mfc.readBlock(bIndex + i)).equals("0x00000000000000000000000000000000")) {
mfc.writeBlock(bIndex + i, new byte[16]);
}
}
}
}
byte[] bytes = new byte[16];
System.arraycopy(data.getBytes(), 0, bytes, 0, data.getBytes().length);
if (mfc.authenticateSectorWithKeyA(1,
MifareClassic.KEY_NFC_FORUM)) {
mfc.writeBlock(4, bytes);
}
Logger.e(TAG, "寫入MifareClassic成功");
} finally {
try {
mfc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
8、清空數(shù)據(jù)
public boolean clear(Intent intent) throws IOException {
boolean result = false;
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
MifareClassic mfc = MifareClassic.get(tag);
try {
mfc.connect();
int sectorCount = mfc.getSectorCount();//獲取TAG中包含的扇區(qū)數(shù)
for (int j = 0; j < sectorCount; j++) {
if (mfc.authenticateSectorWithKeyA(j,
MifareClassic.KEY_NFC_FORUM)) {
// 讀取扇區(qū)中的塊
int bCount = mfc.getBlockCountInSector(j);
int bIndex = mfc.sectorToBlock(j);
for (int i = 0; i < bCount - 1; i++) {
if (!bytesToHexString(mfc.readBlock(bIndex + i)).equals("0x00000000000000000000000000000000")) {
mfc.writeBlock(bIndex + i, new byte[16]);
}
}
}
}
result = true;
Logger.e(TAG, "清空成功");
} finally {
try {
mfc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
四东且、NFC調(diào)用
1启具、重寫onNewIntent()方法
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String id = nfcUtils.readNFCId(nfcUtils.getNFCTag(intent));
Logger.e(TAG, "nfcID:" + id);
String message = nfcUtils.readMessage(intent);
Logger.e(TAG, "nfcMessage:" + message);
}
2、啟動和關(guān)閉
@Override
protected void onResume() {
super.onResume();
Logger.e(TAG, "onResume");
nfcUtils.enableForegroundDispatch();
}
@Override
protected void onPause() {
super.onPause();
Logger.e(TAG, "onPause");
nfcUtils.disableForegroundDispatch();
}