什么是NFC近場(chǎng)通信
NFC的全稱是“Near Field Communication”,意思是近場(chǎng)通信、與鄰近的區(qū)域通信。大眾所熟知的NFC技術(shù)應(yīng)用,主要是智能手機(jī)的刷卡支付功能。別看智能手機(jī)是近十年前才出現(xiàn)的,NFC的歷史可比智能手機(jī)要悠久得多,它脫胎于上世紀(jì)的RFID無(wú)線射頻識(shí)別技術(shù)。
所謂RFID是“Radio Frequency Identification”的縮寫,它通過(guò)無(wú)線電信號(hào)便可識(shí)別特定目標(biāo)并讀寫數(shù)據(jù),而無(wú)需自身與該目標(biāo)之間建立任何機(jī)械或者光學(xué)接觸。像日常生活中的門禁卡、公交卡,乃至二代身份證,都是采用了RFID技術(shù)的卡片。若想讀寫這些RFID卡片,則需相應(yīng)的讀卡器,只要用戶把卡片靠近,讀卡器就會(huì)產(chǎn)生感應(yīng)動(dòng)作。
既然RFID已經(jīng)廣泛使用,那么何苦又要另外制定NFC標(biāo)準(zhǔn)呢?其實(shí)正是因?yàn)镽FID用的地方太多了,導(dǎo)致隨意性較大,反而不便于更好地管控。所以業(yè)界重新定義了NFC規(guī)范,試圖在兩個(gè)方面彌補(bǔ)RFID的固有缺憾:
1、RFID的信號(hào)傳播距離較遠(yuǎn),致使位于遠(yuǎn)處的設(shè)備也可能獲取卡片信息,這對(duì)安全性較高的場(chǎng)合是不可接受的。而NFC的有效工作距離在十厘米之內(nèi),即可避免卡片信息被竊取的風(fēng)險(xiǎn)。
2、RFID的讀寫操作是單向的,也就是說(shuō),只有讀卡器能讀寫卡片,卡片不能拿讀卡器怎么樣?,F(xiàn)在NFC不再沿用“讀卡器——卡片”的模式,取而代之的是只有NFC設(shè)備的概念,兩個(gè)NFC設(shè)備允許互相讀寫,既可以由設(shè)備A讀寫設(shè)備B,也可以由設(shè)備B讀寫設(shè)備A。
改進(jìn)之后的NFC技術(shù)既提高了安全性,又拓寬了應(yīng)用場(chǎng)合,同時(shí)還兼容現(xiàn)有的大部分RFID卡片,因此在智能手機(jī)上運(yùn)用NFC而非RFID也就不足為怪了。
帶有NFC功能的手機(jī),在實(shí)際生活中主要有三項(xiàng)應(yīng)用:讀卡、寫卡、分享內(nèi)容(兩部手機(jī)之間傳輸數(shù)據(jù))。為了能更迅速地了解NFC技術(shù)在Android中的開(kāi)發(fā)流程,下面通過(guò)相對(duì)簡(jiǎn)單的讀卡功能,來(lái)介紹如何進(jìn)行手機(jī)App的NFC開(kāi)發(fā)。
首先App工程要在AndroidManifest.xml中聲明NFC的操作權(quán)限,下面是配置聲明的例子:
<!--NFC-->
<uses-permission android:name="android.permission.NFC"/>
<uses-feature android:name="android.hardware.nfc"android:required="true"/>
其次還要對(duì)活動(dòng)頁(yè)面聲明NFC過(guò)濾器,目前Android支持NDEF_DISCOVERED、TAG_DISCOVERED、TECH_DISCOVERED這三種過(guò)濾器,最好把它們都加入到過(guò)濾器列表中,示例如下:
<activity android:name=".NfcActivity">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
</intent-filter>
<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"/>
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource=" xml/nfc_tech_filter"/>
</activity>
其中TECH_DISCOVERED類型另外指定了過(guò)濾器的來(lái)源是 xml/nfc_tech_filter,該文件的實(shí)際路徑為xml/nfc_tech_filter.xml,文件內(nèi)容如下所示:
<resources>
<!--可以處理所有Android支持的NFC類型-->
<tech-list>
<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.IsoDep</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>
上面的過(guò)濾器列表乍看過(guò)去真是令人大吃一驚,這都是些什么東東,它們之間有哪些區(qū)別呢?倘若認(rèn)真對(duì)這幾個(gè)專業(yè)術(shù)語(yǔ)追根溯源,勢(shì)必要一番長(zhǎng)篇大論才能理清其中的歷史脈絡(luò),因此不妨將事情簡(jiǎn)單化,這些NFC類型只不過(guò)是一個(gè)大家族內(nèi)部的兄弟姐妹罷了。譬如說(shuō)中國(guó)近代史上顯赫的宋氏三姐妹,原是同一對(duì)父母,然后分別嫁給三個(gè)人罷了。NFC類型雖多,常見(jiàn)的NfcA、NfcB、IsoDep三個(gè)系出ISO14443標(biāo)準(zhǔn)(即RFID卡標(biāo)準(zhǔn)),它們仨各自用于生活中的幾種場(chǎng)合,說(shuō)明如下:
1、NfcA遵循ISO14443-3A標(biāo)準(zhǔn),常用于門禁卡;
2、NfcB遵循ISO14443-3B標(biāo)準(zhǔn),常用于二代身份證;
3、IsoDep遵循ISO14443-4標(biāo)準(zhǔn),常用于公交卡;
好不容易把AndroidManifest.xml的相關(guān)配置弄完,接著便是代碼方面的處理邏輯了。NFC編碼主要有三個(gè)步驟:初始化適配器、啟用感應(yīng)/禁用感應(yīng)、接收到感應(yīng)消息并對(duì)消息解碼,下面分別進(jìn)行介紹:
一、初始化NFC適配器
這里的初始化動(dòng)作又可分解為三部分:
1、調(diào)用NfcAdapter類的getDefaultAdapter方法,獲取系統(tǒng)當(dāng)前默認(rèn)的NFC適配器。
2、聲明一個(gè)延遲意圖,告訴系統(tǒng)一旦接收到NFC感應(yīng),則應(yīng)當(dāng)啟動(dòng)哪個(gè)頁(yè)面進(jìn)行處理。
3、定義一個(gè)NFC消息的過(guò)濾器,這個(gè)過(guò)濾器是AndroidManifest.xml所配置過(guò)濾器的子集。因?yàn)榻酉聛?lái)要讀取的卡片兼容RFID標(biāo)準(zhǔn)(ISO14443家族),所以過(guò)濾器的動(dòng)作名稱為NfcAdapter.ACTION_TECH_DISCOVERED,并且設(shè)置該動(dòng)作包含了兩項(xiàng)卡片標(biāo)準(zhǔn),分別是NfcA(用于門禁卡)和IsoDep(用于公交卡)。
詳細(xì)的NFC初始化代碼示例如下:
private void initNfc(){
//獲取默認(rèn)的NFC適配器
nfcAdapter=NfcAdapter.getDefaultAdapter(this);
if(nfcAdapter==null){
tv_nfc_result.setText("當(dāng)前手機(jī)不支持NFC");
return;
}else if(!nfcAdapter.isEnabled()){
tv_nfc_result.setText("請(qǐng)先在系統(tǒng)設(shè)置中啟用NFC功能");
return;
}
//探測(cè)到NFC卡片后,必須以FLAG_ACTIVITY_SINGLE_TOP方式啟動(dòng)Activity,
//或者在AndroidManifest.xml中設(shè)置launchMode屬性為singleTop或者singleTask,
//保證無(wú)論NFC標(biāo)簽靠近手機(jī)多少次,Activity實(shí)例都只有一個(gè)。
Intent intent=new Intent(this,NfcActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
//聲明一個(gè)NFC卡片探測(cè)事件的相應(yīng)動(dòng)作
mPendingIntent=PendingIntent.getActivity(this,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);
try{
//定義一個(gè)過(guò)濾器(檢測(cè)到NFC卡片)
mFilters=new IntentFilter[]{new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED,"*/*")};
}catch(Exception e){
e.printStackTrace();
}
//讀標(biāo)簽之前先確定標(biāo)簽類型
mTechLists=new String[][]{new String[]{NfcA.class.getName()},{IsoDep.class.getName()}};
}
二、啟用NFC感應(yīng)/禁用NFC感應(yīng)
為了讓測(cè)試App能夠接收NFC的感應(yīng)動(dòng)作,需要重載Activity的onResume函數(shù),在該函數(shù)中調(diào)用NFC適配器的enableForegroundDispatch方法,指定啟用NFC功能時(shí)的響應(yīng)動(dòng)作以及過(guò)濾條件。另外也需重載onPause函數(shù),在該函數(shù)中調(diào)用NFC適配器的disableForegroundDispatch方法,表示當(dāng)前頁(yè)面在暫停狀態(tài)之時(shí)不再接收NFC感應(yīng)消息。具體的NFC啟用和禁用代碼如下所示:
Override
protected void onResume(){
super.onResume();
if(nfcAdapter!=null&&nfcAdapter.isEnabled()){
//為本App啟用NFC感應(yīng)
nfcAdapter.enableForegroundDispatch(this,mPendingIntent,mFilters,mTechLists);
}
}
Override
public void onPause(){
super.onPause();
if(nfcAdapter!=null&&nfcAdapter.isEnabled()){
//禁用本App的NFC感應(yīng)
nfcAdapter.disableForegroundDispatch(this);
}
}
三、接收到感應(yīng)消息并對(duì)消息解碼
通過(guò)前面的第二步啟用NFC感應(yīng)之后,一旦App接收到感應(yīng)消息,就會(huì)回調(diào)Activity的onNewIntent函數(shù),因此開(kāi)發(fā)者可以重寫該函數(shù)來(lái)處理NFC的消息內(nèi)容。以NFC技術(shù)常見(jiàn)的小區(qū)門禁卡為例,門禁卡采取的子標(biāo)準(zhǔn)為NfcA,對(duì)應(yīng)的數(shù)據(jù)格式則為MifareClassic。于是利用MifareClassic類的相關(guān)方法即可獲取卡片數(shù)據(jù),下面是MifareClassic類的方法說(shuō)明:
get:從Tag對(duì)象中獲取卡片對(duì)象的信息。該方法為靜態(tài)方法。
connect:連接卡片數(shù)據(jù)。
close:釋放卡片數(shù)據(jù)。
getType:獲取卡片的類型。TYPE_CLASSIC表示傳統(tǒng)類型,TYPE_PLUS表示增強(qiáng)類型,TYPE_PRO表示專業(yè)類型。
getSectorCount:獲取卡片的扇區(qū)數(shù)量。
getBlockCount:獲取卡片的分塊個(gè)數(shù)。
getSize:獲取卡片的存儲(chǔ)空間大小,單位字節(jié)。
使用MifareClassic工具查詢卡片數(shù)據(jù)的流程很常規(guī),先調(diào)用connect方法建立連接,然后調(diào)用各個(gè)get方法獲取詳細(xì)信息,最后調(diào)用close方法關(guān)閉連接。具體的門禁卡讀取代碼示例如下:
Override
protected void onNewIntent(Intent intent){
super.onNewIntent(intent);
String card_info="";
String action=intent.getAction();//獲取到本次啟動(dòng)的action
if(action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED)//NDEF類型
||action.equals(NfcAdapter.ACTION_TECH_DISCOVERED)//其他類型
||action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)){//未知類型
//從intent中讀取NFC卡片內(nèi)容
Tag tag=intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//獲取NFC卡片的序列號(hào)
byte[]ids=tag.getId();
card_info=String.format("卡片的序列號(hào)為:%s",ByteArrayChange.ByteArrayToHexString(ids));
if(rb_guard_card.isChecked()){
String result=readGuardCard(tag);
card_info=String.format("%s\n詳細(xì)信息如下:\n%s",card_info,result);
tv_nfc_result.setText(card_info);
}
}
}
//讀取小區(qū)門禁卡信息
public String readGuardCard(Tag tag){
MifareClassic classic=MifareClassic.get(tag);
String info;
try{
classic.connect();//連接卡片數(shù)據(jù)
int type=classic.getType();//獲取TAG的類型
String typeDesc;
if(type==MifareClassic.TYPE_CLASSIC){
typeDesc="傳統(tǒng)類型";
}else if(type==MifareClassic.TYPE_PLUS){
typeDesc="增強(qiáng)類型";
}else if(type==MifareClassic.TYPE_PRO){
typeDesc="專業(yè)類型";
}else{
typeDesc="未知類型";
}
info=String.format("\t卡片類型:%s\n\t扇區(qū)數(shù)量:%d\n\t分塊個(gè)數(shù):%d\n\t存儲(chǔ)空間:%d字節(jié)",
typeDesc,classic.getSectorCount(),classic.getBlockCount(),classic.getSize());
}catch(Exception e){
e.printStackTrace();
info=e.getMessage();
}finally{//無(wú)論是否發(fā)生異常,都要釋放資源
try{
classic.close();//釋放卡片數(shù)據(jù)
}catch(Exception e){
e.printStackTrace();
info=e.getMessage();
}
}
return info;
}
編碼完畢,找一臺(tái)支持NFC的手機(jī)安裝測(cè)試App,啟動(dòng)應(yīng)用前注意開(kāi)啟手機(jī)的NFC功能。然后進(jìn)入App的測(cè)試頁(yè)面,拿一張門禁卡靠近手機(jī)背面(門禁卡不一定是卡片,也可能是鑰匙扣模樣),稍等片刻便會(huì)讀取并顯示門禁卡的基本信息,卡片信息截圖如下所示: