Android 2.3.3 近場通信NFC介紹
NFC( 近場通信 )
NFC 是一套短距離的無線通信,通常距離是 4 厘米或更短。 NFC 工作頻率是 13.56M Hz, 傳輸速率是 106kbit/s 到848kbit/s. NFC 總是在一個發(fā)起者和一個被動目標之間發(fā)生。發(fā)起者發(fā)出近場無線電波,這個近場可以給被動目標供電。這些被動的目標包括不需要電源的標簽,卡,也可以是有電源的設備。
與其他無線通信技術(shù)比較, 例如藍牙和 WiFi , NFC 提供更低貸款和距離,并且低成本,不需要供電,不需要實現(xiàn)匹配,整個通信過程僅僅是短短的靠近一秒就能完成。
一個帶有 NFC 支持的 android 設備通常是一個發(fā)起者。也可以作為 NFC 的讀寫設備。他將檢測 NFC tags 并且打開一個Activity 來處理 . Android 2.3.3 還有支持有限的 P2P 。
Tags 分很多種,其中簡單的只提供讀寫段,有的只能讀。復雜的 tags 可以支持一些運算,加密來控制對 tags 里數(shù)據(jù)段的讀寫。甚至一些 tags 上有簡單的操作系統(tǒng),允許一些復雜的交互和可以執(zhí)行一些代碼。
API 概覽
Android.nfc package 包含頂層類用來與本地 NFC 適配器交互 . 這些類可以表示被檢測到的 tags 和用 NDEF 數(shù)據(jù)格式。
android.nfc.techpackage 包含那些對 tag 查詢屬性和進行 I/O 操作的類。這些類分別標示一個 tag 支持的不同的NFC 技術(shù)標準。
聲明 Android Manifest.xml 的元素
在你能訪問一個設備的 NFC 硬件和正確的處理 NFC 的 Intent 之前,需要在 AndroidManifest.xml 中先聲明下面的項:
1. NFC 使用 元素來訪問 NFC 硬件 :
2. 最小 SDK 版本需要設置正確, API level 9 只包含有限的 tag 支持 , 包括:
. 通過 ACTION_TAG_DISCOVERED來發(fā)布 Tag 信息
. 只有通過 EXTRA_NDEF_MESSAGES擴展來訪問 NDEF 消息
. 其他的 tag 屬性和 I/O 操作都不支持
所以你可能想要用 API level 10 來實現(xiàn)對 tag 的廣泛的讀寫支持。
3. uses-feature 元素定義:你的程序可以再 android 市場里顯示有 NFC 硬件。
4. NFC intent filter 告訴 android 系統(tǒng)你的 activity 能處理 NFC 數(shù)據(jù),可以定義 1 個或多個 intent filter :
android:resource = "@xml/nfc_tech_filter .xml" />
上邊 3 個 intent filters 有優(yōu)先級,更多信息可以看下面的 Tag 發(fā)布系統(tǒng)
也可以看 NFCDemo 例子的 AndroidManifest.xml 來有個更深的理解。
Tag 發(fā)布系統(tǒng)
當 android 設備掃描到一個 NFC tag ,通用的行為是自動找最合適的 Activity 會處理這個 tag Intent 而不需要用戶來選擇哪個 Activity 來處理。因為設備掃描 NFC tags 是在很短的范圍和時間,如果讓用戶選擇的話,那就有可能需要移動設備,這樣將會打斷這個掃描過程。你應該開發(fā)你只處理需要處理的 tags 的 Activity ,以防止讓用戶選擇使用哪個 Activity來處理的情況。 Android 提供兩個系統(tǒng)來幫助你正確的識別一個 NFC tag 是否是你的 Activity 想要處理的: Intent 發(fā)布系統(tǒng)和前臺 Activity 發(fā)布系統(tǒng)。
Intent 發(fā)布系統(tǒng)檢查所有 Activities 的 intent filters ,找出那些定義了可以處理此 tag 的 Activity ,如果有多個 Activity 都配置了處理同一個 tag Intent ,那么將使用 Activity 選擇器來讓用戶選擇使用哪個 Activity 。用戶選擇之后,將使用選擇的 Activity 來處理此 Intent .
前臺發(fā)布系統(tǒng)允許一個 Activity 覆蓋掉 Intent 發(fā)布系統(tǒng)而首先處理此 tag Intent ,這要求你將要處理 Tag Intent 的 Activity運行在前臺,這樣當一個 NFC tag 被掃描到,系統(tǒng)先檢測前臺的 Activity 是否支持處理此 Intent ,如果支持,即將此Intent 傳給此 Activity ,如果不支持,則轉(zhuǎn)到 Intent 發(fā)布系統(tǒng)。
使用 Intent 發(fā)布系統(tǒng)
Intent 發(fā)布系統(tǒng)指定了 3 個 intent 有不同的優(yōu)先級。通常當一個 tag 被檢測到之后, Intent 就被啟動( start )了,這個啟動遵循以下行為 :
· android.nfc.action.NDEF_DISCOVERED : 這個 intent 是在一個包含 NDEF 負載的 tag 被檢測到時啟動,這是最高優(yōu)先級的 intent, android 系統(tǒng)不會讓你指定一個 Intent 能處理所有的 NFC 數(shù)據(jù)類型,你必須在AndroidManifest.xml 中指定與 NFC tag 對應的 元素,這樣當掃描到的 tag 傳過來的數(shù)據(jù)類型與你定義的相匹配時,你的 Activity 就會被調(diào)用。例如想處理一個包含 plain text 的 NDEF_DISCOVERED intent ,你要按照如下定義AndroidManifest.xml file:
如果 NDEF_DISCOVERED intent 已經(jīng)被啟動, TECH_DISCOVERED 和 TAG_DISCOVERED intents 將不會被啟動。假如一個未知的 tag 或者不包含 NDEF 負載的 tag 被檢測到,此 Intent 就不會被啟動。
· android.nfc.action.TECH_DISCOVERED : 如果 NDEF_DISCOVERED intent 沒啟動或者沒有一個 Activity 的filter 檢測 NDEF_DISCOVERED ,并且此 tag 是已知的,那么此 TECH_DISCOVERED Intent 將會啟動 .TECH_DISCOVERED intent 要求你在一個資源文件里 (xml) 里指定你要支持 technologies 列表。更多細節(jié)請看下面的Specifying tag technologies to handle .
· android.nfc.action.TAG_DISCOVERED : 如果沒有一個 activity 處理 _DISCOVERED and TECH_DISCOVEREDintents 或者 tag 被檢測為未知的,那么此 Intent 將會被啟動。
Specifying tag technologies to handle 指定處理的 technologies
假如你的 Activity 在 AndroidManifest.xml 文件里聲明了處理 android.nfc.action.TECH_DISCOVERED intent,你必須創(chuàng)建一個 Xml 格式的資源文件,并加上你的 activity 支持的 technologies 到 tech-list 集合里。這樣你的 activity將被認作能處理這些 tech-list 的處理者,如果 tag 使用的 technology 屬于你的定義的 list 里,你的 Activity 將接收此Intent 。你可以用 getTechList() 來獲得 tag 支持的 technologies 。
例如:如果一個 tag 被檢測到支持 MifareClassic, NdefFormatable, 和 NfcA ,你的 tech-list 集合必須指定了其中的一項或者多項來保證你的 Activity 能處理此 Intent 。
下面是一個資源文件例子,定義了所有的 technologies. 你可以根據(jù)需要刪掉不需要的項,將此文件以任意名字 +.xml 保存到 /res/xml 文件夾 .
asis:names:tc:xliff:document:1.2" >
android.nfc.tech.IsoDep
android.nfc.tech.NfcA
android.nfc.tech.NfcB
android.nfc.tech.NfcF
android.nfc.tech.NfcV
android.nfc.tech.Ndef
android.nfc.tech.NdefFormatable
android.nfc.tech.MifareClassic
android.nfc.tech.MifareUltralight
你也可以指定多個 tech-list 集合,每個集合都認做獨立的。如果任何單個 tech-list 集合是 getTechList() 返回的technologies 集合的子集,那么你的 Activity 將被認為匹配了。這個還提供 ’ 與 ’ 和 ’ 或 ’ 操作。下面的例子表示支持NfcA 和 NDef 的卡,或者支持 NfcB 和 NDef 的卡:
asis:names:tc:xliff:document:1.2" >
android.nfc.tech.NfcA
android.nfc.tech.Ndef
asis:names:tc:xliff:document:1.2" >
android.nfc.tech.NfcB
android.nfc.tech.Ndef
在 AndroidManifest.xml 文件中 , 指定這個 tech-list 資源文件的方法是在 元素中創(chuàng)建 元素,例如下面例子 :
...
android:resource = "@xml/nfc_tech_filter" />
...
使用前臺發(fā)布系統(tǒng) Using the foreground dispatch system
前臺發(fā)布系統(tǒng)允許一個 Activity 攔截一個 tag Intent 獲得最高優(yōu)先級的處理,這種方式很容易使用和實現(xiàn):
1. 添加下列代碼到 Activity 的 onCreate() 方法里
a. 創(chuàng)建一個 PendingIntent對象 , 這樣 Android 系統(tǒng)就能在一個 tag 被檢測到時定位到這個對象
PendingIntent pendingIntent = PendingIntent . getActivity (
this , 0 , new Intent ( this , getClass ()). addFlags ( Intent .FLAG_ACTIVITY_SINGLE_TOP ), 0 );
b. 在 Intent filters 里聲明你想要處理的 Intent ,一個 tag 被檢測到時先檢查前臺發(fā)布系統(tǒng),如果前臺 Activity 符合Intent filter 的要求,那么前臺的 Activity 的將處理此 Intent 。如果不符合,前臺發(fā)布系統(tǒng)將 Intent 轉(zhuǎn)到 Intent 發(fā)布系統(tǒng)。如果指定了 null 的 Intent filters ,當任意 tag 被檢測到時,你將收到 TAG_DISCOVERED intent 。因此請注意你應該只處理你想要的 Intent 。
IntentFilter ndef = new IntentFilter ( NfcAdapter . ACTION_NDEF_DISCOVERED);
try {
ndef . addDataType ( "*/*" ); /* Handles all MIME based dispatches.
You should specify only the ones that you need. */
}
catch ( MalformedMimeTypeException e ) {
throw new RuntimeException ( "fail" , e );
}
intentFiltersArray = new IntentFilter [] {
ndef ,
};
c. 設置一個你程序要處理的 Tag technologies 的列表,調(diào)用 Object.class.getName() 方法來獲得你想要支持處理的 technology 類。
techListsArray = new String [][] { new String [] { NfcF . class . getName () } };
2. 覆蓋下面的方法來打開或關(guān)閉前臺發(fā)布系統(tǒng)。比如 onPause() 和 onResume ()方法。必須在主線程里調(diào)用[url=http://developer.android.com/reference/android/nfc/NfcAdapter.html#enableForegroundDispatch%28android.app.Activity,%20android.app.PendingIntent,%20android.content.IntentFilter[],%20java.lang.String[][]%29]enableForegroundDispatch(Activity, PendingIntent, IntentFilter[], String[][])[/url]而且Activity 在前臺(可以在 onResume() 里調(diào)用來保證這點)。你也要覆蓋 onNewIntent 回調(diào)來處理得到的 NFC tag 數(shù)據(jù)。
public void onPause () {
super . onPause ();
mAdapter . disableForegroundDispatch ( this );
}
public void onResume () {
super . onResume ();
mAdapter . enableForegroundDispatch ( this , pendingIntent , intentFiltersArray, techListsArray );
}
public void onNewIntent ( Intent intent ) {
Tag tagFromIntent = intent . getParcelableExtra ( NfcAdapter . EXTRA_TAG );
//do something with tagFromIntent
}
See the ForegroundDispatch sample from API Demos for the complete sample.
使用 NFC tag 上的數(shù)據(jù)
NFC tag 上的數(shù)據(jù)是以字節(jié)存放,所以你可以將其轉(zhuǎn)換成其他你想要的格式。當往 tag 寫東西時,你必須以字節(jié)格式來寫。 Android 提供 API 來幫助寫符合 NDEF 標準的信息。使用此標準能保證你的數(shù)據(jù)在往 tag 寫時能被所有 Android NFC 設備支持。然而,很多 tag 使用他們自己的標準來存儲數(shù)據(jù),這些標準也被 Android 支持。但你必須自己實現(xiàn)協(xié)議棧來讀寫這些 tag 。你可以在 android.nfc.tech 里找到所有支持的 technologies ,并且可以在 TagTechnology 接口里對technology 有個了解。這一段是簡單介紹在 android 系統(tǒng)里怎樣使用 NDEF 消息。這不意味著是一個完整的 NDEF 功能的介紹。但標出了主要需要注意和使用的東西。
為了方便使用 NDEF 消息, android 提供 NdefRecord和 NdefMessage來包裝原始字節(jié)數(shù)據(jù)為 NDEF 消息。一個NdefMessage 是保存 0 個或多個 NdefRecords 的容器。每個 NdefRecord 有自己的唯一類型名字格式,記錄類型和 ID 來與其他記錄區(qū)分開。你可以存儲不同類型的記錄,不同的長度到同一個 NdefMessage 。 NFC tag 容量的限制決定你的NdefMessage 的大小。
那些支持 Ndef 和 NdefFormatable 技術(shù)的 tag 可以返回和接受 NdefMessage 對象為參數(shù)來進行讀寫操作。你需要創(chuàng)建你自己的邏輯來為其他在 android.nfc.tech 的 tag 技術(shù)實現(xiàn)讀寫字節(jié)的操作。
你可以從 NFC Forum(http://www.nfc-forum.org/specs/) 下載 NDEF 消息標準的技術(shù)文檔,比如純文本和智慧型海報 . NFCDemo 例子里聲明了純文本和智慧型海報的 NDef 消息。
讀一個 NFC tag
當一個 NFC tag 靠近一個 NFC 設備,一個相應的 Intent 將在設備上被創(chuàng)建。然后通知合適的程序來處理此 Intent 。
下面的方法處理 TAG_DISCOVERED intent 并且使用迭代器來獲得包含在 NDEF tag 負載的數(shù)據(jù)
NdefMessage [] getNdefMessages ( Intent intent ) {
// Parse the intent
NdefMessage [] msgs = null ;
String action = intent . getAction ();
if ( NfcAdapter . ACTION_TAG_DISCOVERED . equals ( action )) {
arcelable [] rawMsgs = intent . getParcelableArrayExtra ( NfcAdapter .EXTRA_NDEF_MESSAGES );
if ( rawMsgs != null ) {
msgs = new NdefMessage [ rawMsgs . length ];
for ( int i = 0 ; i < rawMsgs . length ; i ++) {
msgs [ i ] = ( NdefMessage ) rawMsgs [ i ];
}
}
else {
// Unknown tag type
byte [] empty = new byte [] {};
NdefRecord record = new NdefRecord ( NdefRecord . TNF_UNKNOWN , empty , empty, empty );
NdefMessage msg = new NdefMessage ( new NdefRecord [] { record });
msgs = new NdefMessage [] { msg };
}
}
else {
Log . e ( TAG , "Unknown intent " + intent );
finish ();
}
return msgs ;
}
請記住 NFC 設備讀到的數(shù)據(jù)是 byte 類型,所以你可能需要將他轉(zhuǎn)成其他格式來呈現(xiàn)給用戶。 NFCDemo 例子展示了怎樣用 com.example.android.nfc.record 中的類來解析 NDEF 消息,比如純文本和智慧型海報。
寫 NFC tag
往 NFC tag 寫東西涉及到構(gòu)造一個 NDEF 消息和使用與 tag 匹配的 Tag 技術(shù)。下面的代碼展示怎樣寫一個簡單的文本到NdefFormatable tag :
NdefFormatable tag = NdefFormatable . get ( t );
Locale locale = Locale . US ;
final byte [] langBytes = locale . getLanguage (). getBytes ( Charsets . US_ASCII );
String text = "Tag, you're it!" ;
final byte [] textBytes = text . getBytes ( Charsets . UTF_8 );
final int utfBit = 0 ;
final char status = ( char ) ( utfBit + langBytes . length );
final byte [] data = Bytes . concat ( new byte [] {( byte ) status }, langBytes , textBytes);
NdefRecord record = NdefRecord ( NdefRecord . TNF_WELL_KNOWN , NdefRecord . RTD_TEXT , newbyte [ 0 ], data );
try {
NdefRecord [] records = { text };
NdefMessage message = new NdefMessage ( records );
tag . connect ();
tag . format ( message );
}
catch ( Exception e ){
//do error handling
}
點對點的數(shù)據(jù)交換
前臺推送技術(shù)支持簡單點對點的數(shù)據(jù)交換,你可以用 enableForegroundNdefPush(Activity, NdefMessage)方法來打開此功能 . 為了用這個功能:
· 推送數(shù)據(jù)的 Activity 必須是前臺 Activity 。
· 你必須將你要發(fā)送的數(shù)據(jù)封裝到 NdefMessage 對象里。
· 接收推送數(shù)據(jù)的設備必須支持 com.android.npp NDEF 推送協(xié)議,這個對于 Android 設備是可選的
假如你的 Activity 打開了前臺推送功能并且位于前臺,這時標準的 Intent 發(fā)布系統(tǒng)是禁止的。然而,如果你的Activity 允許前臺發(fā)布系統(tǒng),那么此時檢測 tag 的功能仍然是可用的,不過只適用于前臺發(fā)布系統(tǒng)。
要打開前臺推送 :
1. 創(chuàng)建一個你要推送給其他 NFC 設備的包含 NdefRecords 的 NdefMessage 。
2. 在你的 Activity 里實現(xiàn) onResume()和 onPause()的回調(diào)來正確處理前臺推送的生命周期。你必須在你的Activity 位于前臺并在主線程里調(diào)用 enableForegroundNdefPush(Activity, NdefMessage)(可以在onResume() 里調(diào)用來保證這點) .
public void onResume () {
super . onResume ();
if ( mAdapter != null )
mAdapter . enableForegroundNdefPush ( this , myNdefMessage );
}
public void onPause () {
super . onPause ();
if ( mAdapter != null )
mAdapter . disableForegroundNdefPush ( this );
}
當 Activity 位于前臺,你可以靠近另外一個 NFC 設備來推送數(shù)據(jù)。請參考例子 ForegroundNdefPush 來了解點對點數(shù)據(jù)交換。