Near Field Communication (NFC) 為一短距離無線通信技術,通常有效通訊距離為4厘米以內。NFC工作頻率為13.56 兆赫茲,通信速率為106 kbit/秒到 848kbit/秒。
NFC通信總是由一個發起者(initiator)和一個接受者(target)組成。通常initiator 主動發送電磁場(RF)可以為被動式接受者(passive target)提供電源。其工作的基本原理和收音機類似。正是由於被動式接受者可以通過發起者提供電源,因此target 可以有非常簡單的形式,比如標籤,卡,sticker 的形式.
NFC 也支持點到點的通信(peer to peer) 此時參與通信的雙方都有電源支持。
和其它無線通信方式如Bluetooth相比,NFC 支持的通信帶寬和距離要小的多,但是它成本低,如價格標籤可能只有幾分錢,也不需要配對,搜尋設備等,通信雙方可以在靠近的瞬間完成交互。
在Android NFC 應用中,Android手機通常是作為通信中的發起者,也就是作為NFC 的讀寫器。Android手機也可以模擬作為NFC通信的接受者且從Android 2.3.3起也支持P2P通信。
Android對NFC的支持主要在 android.nfc 和android.nfc.tech 兩個包中。
android.nfc 包中主要類如下:
- NfcManager 可以用來管理Android設備中指出的所有NFC Adapter,但由於大部分Android設備只支持一個NFC Adapter,可以直接使用getDefaultAapater 來獲取系統支持的Adapter。
- NfcAdapter 為一NFC Adapter 對象,可以用來定義一個Intent使系統在檢測到NFC Tag時通知你定義的Activity,並提供用來註冊forground tag 消息發送的方法等。
- NdefMessage 和NdefRecord NDEF 為NFC forum 定義的數據格式。
- Tag 代表一個被動式Tag對象,可以代表一個標籤,卡片,鑰匙扣等。當Android設備檢測到一個Tag時,會創建一個Tag對象,將其放在Intent對象,然後發送到相應的Activity。
android.nfc.tech 中則定義了可以對Tag進行的讀寫操作的類,這些類按照其使用的技術類型可以分成不同的類如:NfcA, NfcB, NfcF,以及MifareClassic 等。
常見的Tag為Mifare ,後面的例子將以這種Tag 為例介紹NFC讀寫方法。
本例參考ApiDemos中NFC的ForegoundDispatch來介紹編寫Android NFC 的基本步驟,因為手邊只有MifareClassic類型的Tag ,需要對ForegoundDispatch的代碼做些修改來檢測MifareClassic 的類型的NFC Tag,讀寫其他類型的NFC Tag的基本步驟是一致的。
1. 在Android manifest 文件中申明和NFC相關的許可權和功能選項:
許可權申明:
<uses-permission android:name=”android.permission.NFC” />
最低版本要求,NFC是指Android2.3 (Level 10) 才開始支持的,因此最低版本要求必須指定為10.
<uses-sdk android:minSdkVersion=”10″/>
如果需要在Android Market上發布,需要指定手機支持NFC 功能。
<uses-feature android:name=”android.hardware.nfc” android:required=”true” />
為Activity申明它支持處理NFC Tag
比如我們的示例Activity 在Manifest 的申明如下:
<activity android:name=”.NFCDemoActivity”
android:label=”@string/app_name”
android:launchMode=”singleTop”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
<intent-filter>
<action android:name=”android.nfc.action.NDEF_DISCOVERED”/>
<data android:mimeType=”text/plain” />
</intent-filter>
<intent-filter>
<action
android:name=”android.nfc.action.TAG_DISCOVERED”
>
</action>
<category
android:name=”android.intent.category.DEFAULT”
>
</category>
</intent-filter>
<!– Add a technology 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/filter_nfc”
/>
</activity>
三種Activity NDEF_DISCOVERED ,TECH_DISCOVERED,TAG_DISCOVERED 指明的先後順序非常重要, 當Android設備檢測到有NFC Tag靠近時,會根據Action申明的順序給對應的Activity 發送含NFC消息的 Intent.
2. Android NFC 消息發送機制
當Android設備檢測到有NFC Tag時,理想的行為是觸發最合適的Activity來處理檢測到的Tag,這是因為NFC通常是在非常近的距離才起作用(<4m) ,如果此時需要用戶來選擇合適的應用來處理Tag,很容易斷開與Tag之間的通信。因此你需要選擇合適的Intent filter 只處理你想讀寫的Tag類型。
Android系統支持兩種NFC消息發送機制:Intent 發送機制和前台Activity 消息發送機制。
Intent 發送機制 當系統檢測到Tag時,Android系統提供manifest 中定義的Intent filter 來選擇合適的Activity來處理對應的Tag,當有多個Activity可以處理對應的Tag類型時,則會顯示Activity選擇窗口由用戶選擇:
前台Activity 消息發送機制 允許一個在前台運行的Activity在讀寫NFC Tag 具有優先權,此時如果Android檢測到有NFC Tag ,如果前台允許的Activity可以處理該種類型的Tag則該Activity具有優先權,而不出現Activity 選擇窗口。
這兩種方法基本上都是使用Intent-filter 來指明Activity可以處理的Tag類型,一個是使用Android的Manifest 來說明,一個是通過代碼來申明。
下圖顯示當Android檢測到Tag,消息發送的優先順序:
![Dispatch-process](https://i-blog.csdnimg.cn/blog_migrate/a3eac1c419142129b7a34d746e6c3022.png)
本例 NFCDemoActivity 支持兩種NFC消息發送機制,上面的XML指明了Intent 消息發送機制,其中
<meta-data android:name=”android.nfc.action.TECH_DISCOVERED”
android:resource=”@xml/filter_nfc”
/>
的filter_nfc 指明了支持處理的NFC Tag類型,filter_nfc.xml 定義如下:
<resources xmlns:xliff=”urn:oasis:names:tc:xliff:document:1.2″>
<!– capture anything using NfcF –>
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.MifareClassic</tech>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>
因為我只有MifareClassic 類型的Tag,所以只定義了MifareClassic相關的Tag類型,如果你可以處理所有Android支持的NFC類型,可以定義為:
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
<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.Ndef</tech>
<tech>android.nfc.tech.NdefFormatable</tech>
<tech>android.nfc.tech.MifareClassic</tech>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>
有了這個Manifest中的申明,當Android檢測到有Tag時,會顯示Activity選擇窗口,如上圖中的Reading Example。
當NFCDemoActiviy在前台運行時,我們希望只有它來處理Mifare 類型的Tag,此時可以使用前台消息發送機制,下面的代碼基本和ApiDemos中的NFC示例類似:
1 | public class NFCDemoActivity extends Activity { |
2 | private NfcAdapter mAdapter; |
3 | private PendingIntent mPendingIntent; |
4 | private IntentFilter[] mFilters; |
5 | private String[][] mTechLists; |
10 | public void onCreate(Bundle savedState) { |
11 | super .onCreate(savedState); |
13 | setContentView(R.layout.foreground_dispatch); |
14 | mText = (TextView) findViewById(R.id.text); |
15 | mText.setText( "Scan a tag" ); |
17 | mAdapter = NfcAdapter.getDefaultAdapter( this ); |
24 | mPendingIntent = PendingIntent.getActivity( this , 0 , |
26 | getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0 ); |
30 | = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED); |
32 | ndef.addDataType( "*/*" ); |
33 | } catch (MalformedMimeTypeException e) { |
34 | throw new RuntimeException( "fail" , e); |
36 | mFilters = new IntentFilter[] { |
42 | = new String[][] { new String[] { MifareClassic. class .getName() } }; |
46 | public void onResume() { |
48 | mAdapter.enableForegroundDispatch( this , |
49 | mPendingIntent, mFilters, mTechLists); |
53 | public void onNewIntent(Intent intent) { |
54 | Log.i( "Foreground dispatch" , |
55 | "Discovered tag with intent: " + intent); |
56 | mText.setText( "Discovered tag " + |
57 | ++mCount + " with intent: " + intent); |
61 | public void onPause() { |
63 | mAdapter.disableForegroundDispatch( this ); |
只改了一行,將處理NfcF類型的Tag 改為處理MifareClassic 類型的NFC Tag。
mTechLists = new String[][] { new String[] { MifareClassic.class.getName() } };
運行該示例,每靠近一次Tag,計數加1.
![20111005002](https://i-blog.csdnimg.cn/blog_migrate/7deb210e5676c50f41872a924807f50a.png)
前面例子介紹了檢測,讀寫NFC TAG開發的一般步驟,本例針對常用的Mifare Tag 具體說明。
Mifare Tag 可以有1K ,2K, 4K,其內存分區大同小異,下圖給出了1K位元組容量的Tag的內存分布:
數據分為16個區(Sector) ,每個區有4個塊(Block) ,每個塊可以存放16位元組的數據,其大小為16 X 4 X 16 =1024 bytes
每個區最後一個塊稱為Trailer ,主要用來存放讀寫該區Block數據的Key ,可以有A,B兩個Key,每個Key 長度為6個位元組,預設的Key值一般為全FF或是0. 由 MifareClassic.KEY_DEFAULT 定義。
因此讀寫Mifare Tag 首先需要有正確的Key值(起到保護的作用),如果鑒權成功
auth = mfc.authenticateSectorWithKeyA(j,
MifareClassic.KEY_DEFAULT);
然後才可以讀寫該區數據。
本例定義幾個Mifare相關的類 MifareClassCard ,MifareSector, MifareBlock 和MifareKey 以方便讀寫Mifare Tag.
Android 系統來檢測到NFC Tag, 將其封裝成Tag類,存放到Intent的NfcAdapter.EXTRA_TAG Extra 數據包中,可以使用MifareClassic.get(Tag) 獲取對象的 MifareClassic類。
1 | Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); |
4 | MifareClassic mfc = MifareClassic.get(tagFromIntent); |
下面為讀取Mifare card 的主要代碼:
2 | String action = intent.getAction(); |
4 | if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) { |
6 | Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); |
9 | MifareClassic mfc = MifareClassic.get(tagFromIntent); |
10 | MifareClassCard mifareClassCard= null ; |
17 | int secCount = mfc.getSectorCount(); |
18 | mifareClassCard= new MifareClassCard(secCount); |
21 | for ( int j = 0 ; j < secCount; j++) { |
22 | MifareSector mifareSector = new MifareSector(); |
23 | mifareSector.sectorIndex = j; |
25 | auth = mfc.authenticateSectorWithKeyA(j, |
26 | MifareClassic.KEY_DEFAULT); |
27 | mifareSector.authorized = auth; |
30 | bCount = mfc.getBlockCountInSector(j); |
31 | bCount =Math.min(bCount, MifareSector.BLOCKCOUNT); |
32 | bIndex = mfc.sectorToBlock(j); |
33 | for ( int i = 0 ; i < bCount; i++) { |
36 | byte []data = mfc.readBlock(bIndex); |
37 | MifareBlock mifareBlock = new MifareBlock(data); |
38 | mifareBlock.blockIndex = bIndex; |
43 | mifareSector.blocks[i] = mifareBlock; |
47 | mifareClassCard.setSector(mifareSector.sectorIndex, |
53 | ArrayList<String> blockData= new ArrayList<String>(); |
55 | for ( int i= 0 ;i<secCount;i++){ |
57 | MifareSector mifareSector=mifareClassCard.getSector(i); |
58 | for ( int j= 0 ;j<MifareSector.BLOCKCOUNT;j++){ |
59 | MifareBlock mifareBlock=mifareSector.blocks[j]; |
60 | byte []data=mifareBlock.getData(); |
61 | blockData.add( "Block " + blockIndex++ + " : " + |
62 | Converter.getHexString(data, data.length)); |
65 | String []contents= new String[blockData.size()]; |
66 | blockData.toArray(contents); |
67 | setListAdapter( new ArrayAdapter<String>( this , |
68 | android.R.layout.simple_list_item_1, contents)); |
69 | getListView().setTextFilterEnabled( true ); |
71 | } catch (IOException e) { |
72 | Log.e(TAG, e.getLocalizedMessage()); |
76 | if (mifareClassCard!= null ){ |
77 | mifareClassCard.debugPrint(); |
![20111009002](https://i-blog.csdnimg.cn/blog_migrate/05cc66d9446ff2fab3cbb4ef66fac2f9.png)