Android - 學習操作NFC - 1
在研讀了WP8 – Proximity APIs之後撰寫了幾篇文章,深深覺得NFC非常有趣,進一步希望了解
Android在這一塊的處理方式,因此,補上了該篇介紹一下自己在學習Android處理NFC時的心得。
〉概要:
在Android系統下,目前主要以支持NDEF NFC Data Exchange Format (NDEF)格式為主,它支持devices之間
或device與tag之間互相交換小型payload的資料。由於是屬於近距離的無線技術,典型是在4 cm或更接近時建立連線。
單一個tag就具有多種交易的方式,包括:read-only、加/解密、多種型式格式…等。但如果遇到是非預設支援的NDEF
在Android仍有自訂相關處理方式,往後進一步說明。
需注意的是:
‧Android 2.3.3 (API Level 10)才有支援完整的NFC功能;
‧Android 4.x 支持 Android Application Record (AAR);
‧Android 4.0 支持 Beam;
在Android developers官網裡分別有二篇文章說明NFC的使用:<NFC Basics>與<Advanced NFC>。
本篇主要根據<NFC Basics>學習在Android上:
‧如何傳送與接收NDEF data message;
來自NFC tag 、 Beaming NDEF Message由一個device到另一個;
‧操作NFC APIs、Android Beam™;
NDFE data從NFC tag中取出,交由tag dispatch system進行處理,包括:分析發現的NFC tags、將資料進行分類,
並且根據資料啟動應用程式。一個應用程式想要處理被掃瞄到的NFC tag,可以宣告intent filter與請求要處理的data。
Android Beam™功能允許一個device可發佈NDEF message至另一個device,透過二個devices直接tapping。這樣的方式
比過去使用Bluetooth、Wi-Fi容易許多,因為不需要在配對或連線驗證,只需二個devices接近至一定距離則自動觸發。
Android Beam是經由NFC APIs所提供的功能,任何應用程式均能使用它在二個device之間傳送資料,例如:分享聯絡人、
web pages、videos…等。
有了一些Android在處理NFC tag與APIs的概論之後,往下進一步討論當取得NFC tag中的資料時,要如何識別它是那種類型,
以及具有那些特殊的識別標籤與儲存的方式。
〉The Tag Dispatch System:
在Android系統中,如果有開啟NFC的功能,當設備螢幕鎖定時,NFC功能會被取消,直到設備被喚醒後,NFC功能才會啟動。
達到省電的效果。當Android-powered device發現一個NFC tag,最好的作法是自動啟動對應的應用程式,而不是詢問用戶選擇。
為什麼會這樣說呢?因為設備掃瞄NFC tags在非常近的距離,尤其是在背靠背時,要用戶移動手機回到前面選擇要啟動的程式,
有可能因為移動Tags或Devices離開了連線,非常不方便。因此,你應該開發讓你的Activity只處理您在乎的NFC tags,以防止出現
Activity Chooser要用戶選擇。
為了達到上述的目標,Android提供了tag dispatch system協助分析發現的NFC tags、剖析它們,並嘗式指定啟動特定應用程式。
主要做的事情,如下:
(1) 拆解NFC tag與搞清除是MIME type或URI的資料payload儲存於tag中;
(2) 封裝MIME type或URI的資料payload至intent object中;
(3) 基於建立好的intent object啟動activity;
這三件事也就是Android處理NFC tags/devices之間的重要流程,往下逐一說明(1)與(2),加上(3)的各自處理方式。
〉How NFC tags are mapped to MIME types and URIs:
在開發NFC application之前一定要先了解有多少種NFC tag類型、tag dispatch system如何剖析它們,以及當讀取到一個
NDFE message時要執行那些動作。相對的,如果要寫入NDEF message至devices或tag也有很多種方式要特別注意。
然而,Android支持大部分NDEF標準,而這些NDEF標準由NFC Forum所定義,相關的文獻如下:
<NFC Forum Specification Download>完整說明NFC的tag規格與<Creating common types of NDEF records>說明建立NDEF record。
‧NFC tag結構:
NFC tag中的NDEF資料被封裝於NFC message裡,其NFC message內可有1~N個records(NdefRecord),每一個NDEF record必需是
符合標準定義所建立而成。Android支持其他類型的tags,它們可能不包含NDEF data,如果要操作它可以使用android.nfc.tech
下的類別進行處理,相關的資訊可參考<Advanced NFC>。
有了基本NDEF tags的背景後,由於當發現一個NFC tag包含NDEF Formatted data,它將開始剖析並識別是否為MIME type或URI。
要做剖析與識別,需從NDEF message中的第一個NdefRecord中,識別它為何種資料類型,往下了解NDEF record的定義:
a. the First record (NdefRecord):
由四個資訊組合:
1). 3-bit TNF (Type Name Format):
說明如何解釋Type欄位 (Variable length type)的格式。透過下表說明the tag dispatch system如何透過TNF與type field
來識別該NDEF message是MIME type或URI。可以被配對到的NDEF message,系統會觸發ACTION_NDEF_DISCOVERED,
如果無法配對,系統會再倒給ACTION_TECH_DISCOVERED進行配對,如果沒有再退給ACTION_TAG_DISCOVERED。
2). Variable length type:
描述該record的類型。如果是TNF_WELL_KNOWN,使用該欄位需要參考Record Type Definition (RTD)。
3). Variable length ID:
該record的unique identifier。該欄位經常不使用,但如果需要唯一識別該tag,可以建立一個ID給他。
4). Variable length payload:
實際想要讀或寫的資料內容(data payload)。一個NDEF message可包括多個NDEF records,所以不要以為所有的資料
均寫在NDEF message的第一個record。
the tag dispatch system透過TNF與type field嘗試去配對NDEF message符合MIME type或URI。如果配對成功,系統會封裝
這些資訊至ACTION_NDEF_DISCOVERED intent,以及包括實際的data payload。然而,如何遇到無法配對或是NFC tag不包含
NDEF data造成無法配對,系統會再倒給ACTION_TECH_DISCOVERED進行配對,如果沒有再退給ACTION_TAG_DISCOVERED。
以下來了解TNF具有那些類型,以及當TNF為TNF_WELL_KNOWN時搭配RTD有那些:
Type Name Format (TNF) | Mapping |
TNF_ABSOLUTE_URI | Type欄位資料格式為:URI。 |
TNF_EMPTY | Android系統觸發ACTION_TECH_DISCOVERED的方式處理。 |
TNF_EXTERNAL_TYPE | Type欄位資料格式為:URN類型的URI。 URN被編碼放入NDEF type欄位,符合一個字段格式:<domain name>:<service name>。 Android系統轉譯成:vnd.android.nfc://ext/<domain name>:<service name>。 |
TNF_MIME_MEDIA | Type欄位資料格式為:MIME。 |
TNF_UNCHANGED | 第一個record為無效的,Android觸發ACTION_TECH_DISCOVERED的方式處理。 |
TNF_UNKNOWN | Android觸發ACTION_TECH_DISCOVERED的方式處理。 |
TNF_WELL_KNOWN | 設定在type field中的MIME type或URI,是取決於Record Type Definition (RTD)。 |
如果TNF值為:TNF_WELL_KNOWN,則會相關RTD的格式可再透過下表來看:
Record Type Definition (RTD) | Mapping |
RTD_ALTERNATIVE_CARRIER | Falls back to ACTION_TECH_DISCOVERED . |
RTD_HANDOVER_CARRIER | Falls back to ACTION_TECH_DISCOVERED . |
RTD_HANDOVER_REQUEST | Falls back to ACTION_TECH_DISCOVERED . |
RTD_HANDOVER_SELECT | Falls back to ACTION_TECH_DISCOVERED . |
RTD_SMART_POSTER | URI based on parsing the payload. |
RTD_TEXT | MIME type of text/plain. |
RTD_URI | URI based on payload. |
了解了如何識別該NDEF data message 為類型之後,接著往下說明當識別之後要怎麼觸發對應的處理與呼叫應用程式。
〉How NFC Tags are Dispatched to Applications:
當tag dispatch system建立一個intent裡面封裝了NFC tag與識別資訊,它將傳送給filters該intent的應用程式去處理。
如果有一個以上的應用程式處理該intent,Activity Chooser將呈現一個清單對話框讓用戶選擇要執行的Activity。
該tag dispatch system定義了三種intents,根據優先權由高至低進行排序:
(1). ACTION_NDEF_DISCOVERED:
當tag內容包括一個NDEF payload與具有可識別的類型被掃瞄到,該intent被用來啟動一個Activity。
這是最高的優先權,在往下交給其他的intent之前,tag dispatch system會嘗試去開啟並夾該intent啟動一個Activity。
(2). ACTION_TECH_DISCOVERED:
如果沒有activities註冊處理ACTION_NDEF_DISCOVERED該intent,tag dispatch system會發啟一個ACTION_TECH_DISCOVER的
intent給對應的應用程式進行處理。
tag dispatch system查覺該tag具有NDEF data但卻沒有對應至MIME type或URI,或是該tag沒有包括NDEF data但是一個
TNF_WELL_KNOWN的識別,它將會發啟一個ACTION_TECH_DISCOVERED的intent (而不會啟動ACTION_NDEF_DISCOVERED)。
(3). ACTION_TAG_DISCOVERED:
如果沒有activities註冊處理ACTION_NDEF_DISCOVERED、ACTION_TECH_DISCOVERED的intents,將會發啟該intent。
其本的tag dispatch system處理流程如下圖:
1. 當處理到一個NFC tag時,tag dispatch system發出ACTION_NDEF_DISCOVERED或ACTION_TECH_DISCOVERED任一種,
嘗試去啟動一個應用程式來處理該intent。
2. 如果沒有應用程式filter這個intent,嘗試再發出比較低順序的ACTION_TECH_DISCOVERED或ACTION_TAG_DISCOVERED,
直到有應用程式來處理該intent或tag dispatch system嘗試所有可能的intents。
3. 如果沒有任何應用程式filter任何的intents,則什麼都不做。
以上介紹了Android在處理NFC Tag的機制,主要透過tag dispatch system來進行事件的觸發與流程,往下將撰寫範例程式
進行說明:
〉範例說明:
(1). 在AppManifest.xml中增加必要的uses-permission、最低的SDK版本、設定uses-feature:
‧指定uses-permission:
<uses-permission android:name="android.permission.NFC"/>
‧指定最低的SDK版本:
<uses-sdk android:minSdkVersion="10"/>
API Level 9雖也有支援NFC,但只限於支持ACTION_TAG_DISCOVERED與透過EXTRA_NDEF_MESSAGES取得NDEF Message。
由於完整的NFC功能從Android 2.3.3 (API Level 10)才開始支援,所以要記得宣告使用的最低SDK版本,因為API Level 10支援
Reader/Writer的功能;另外,API Level 14提供了更簡單透過Android Beam進行Push NDEF Message至另一個設備,以及更方
便的方法建立NDEF records。
‧指定uses-feature:
<uses-feature android:name="android.hardware.nfc" android:required="true" />
指定該應用程式必需具有NFC晶片才能安裝,如果設定android:required = true代表Google Play會自動過濾掉沒有支持NFC晶片的設備,
這樣一來,具有這些設備的用戶將搜尋不到該應用程式。如果您的應用程式非以NFC為主要功能,可不設定該uses-feature或是將值
設定為false,改由程式中呼叫getDefaultAdapter()方法判斷是否為null進行功能的限制。
(2). 設定Filtering for NFC Intents:
為了讓應用程式可以處理NFC Tag被掃瞄後所發出的Intents,應用程式需要在AppManifest.xml註冊多個Filter來取得NFCT intents。
‧註冊ACTION_NDEF_DISCOVERED:處理大部分NDEF Message;
‧註冊ACTION_TECH_DISCOVERED:處理當沒有應用程式處理ACTION_NDEF_DISCOVERED或該Tag非NDEF message所退回的intent;
‧註冊ACTION_TAG_DISCOVERED:處理通常過於籠統的類別進行篩選。也就是上述二個intents除外的intent;
通常應用程式優先處理ACTION_NDEF_DISCOVERED、ACTION_TECH_DISCOVERED,除非這二個無法處理,最後才會輪到
ACTION_TAG_DISCOVERED。也因為ACTION_TAG_DISCOVERED可能偵測到的NFC Tag是比較特殊的格式,可以搭配<Advanced NFC>來做參考。
[注意]
由於NFC Tag部署有所不同,很多時候不是應用程式或系統所能控制之下,這並非不可能的,這就是為什麼必要時tag dispatch system
可以回退到其他兩個intents。當取得NFC Tag是有辦法識別的類型,且具有數據寫入的控制權,建議您使用的NDEF格式化標籤。
(2-1). 註冊filter ACTION_NDEF_DISCOVERED:
為了filter ACTION_NDEF_DISCOVERED intents,宣告intent filter要處理的資料類型。
以下例子使用處理MIME Type為text/plain:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain" />
</intent-filter>
以下例子使用處理URI類型:http://developer.android.com/index.html;
註冊處理為http的scheme,developer.android.com的host,以及固定第一個字段為:index.html。
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http"
android:host="developer.android.com"
android:pathPrefix="/index.html" />
</intent-filter>
(2-2). 註冊filter ACTION_TECH_DISCOVERED:
如果要註冊filter ACTION_TECH_DISCOVERED,需要在專案中增加一個新的XML檔案,定義該應用程式支持的 tech-list sets。
可以透過呼叫getTechList()來確認偵測到的NFC tag是否與定義的tech-list sets有所匹配。該檔案建議放罝於<project-root>/res/xml目錄下。
舉例來說:
如果一個tag被偵測到,它支持三種標準:MifareClassic、NdefFormatable與NfcA,在定義的tech-list sets就需要加入這些標準,
可能是一個、二個或三個標準都支持,以確保可以處理該intent。以下列出定義常見的tech-list集合:
<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>
另外,也可以定義多組tech-list sets。每一個tech-list set是獨立的,如果任何一組tech-list set被匹配到,activity將會被啟動,
該activity也可以透過getTechList()來確認偵測到的NFC tag是否與定義的tech-list sets有所匹配。它提供了 AND 與OR的定義匹配技術。
透過下列範例,說明支持NdefA與NdefB:
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
</resources>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.NfcB</tech>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
</resources>
定義好了resource,可以在AppManifest.xml中指定activity透過<meta-data />定義要使用那一個resource。如下:
<activity>
...
<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>
更多詳細的資料可以參考<Working with Supported Tag Technologies>。
(2-3). 註冊ACTION_TAG_DISCOVERED:
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED"/>
</intent-filter>
(3). 識別收到的NFC intent物件具有那些資訊:
上述介紹了應用程式要註冊的intent filter後,接著要處理是當應用程式補捉到filter所要的intent物件後,該怎麼取得我們要的資訊。
一個NFC intent以下的資訊:
‧EXTRA_TAG:(必要),一個Tag物件代表掃瞄到的tag;
‧EXTRA_NDEF_MESSAGES:(選擇),代表tag中所存在的NDEF Message陣列;
‧{@link android.nfc.NfcAdapter#EXTRA_ID:(選擇),代表一個tag的low-level ID;
如果您的應用程式被一個NFC tag被掃瞄到且建立了NFC intent所啟動,那該應用程式可以取得到上述三個資訊,
以ACTION_NDEF_DISCOVERED為例:
public void onResume() {
super.onResume();
...
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
Parcelable[] 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];
}
}
}
//process the msgs array
}
至於要取得Tag物件的話,可用下列方式:
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
======
以上是介紹了Android在偵測NFC tag的機制,包括如果註冊intent filter讓應用程式可以取得系統所廣播出來的intent,
進一步接收NDEF message intent。往下一篇,針對本篇所介紹的概念,進行對NFC tag的實作包括Reader/Writer。
References:
‧NFC Demo - Android sample code
‧Near Field Communication (重要)
‧Android NFC 开发教程(2): ApiDemos->NFC->ForegoundDispatch
‧Android NFC 開發教程(3): Mifare Tag 讀寫示例
‧Developer Document & NXP TagWriter & StickyNotes sample code
‧[Android]NFC能做什麼?阿達流NFC基本教學告訴你