NFC 基本概念

本文說明您在 Android 中執行的基本 NFC 工作。也會說明如何以 NDEF 訊息的形式收發 NFC 資料,並說明支援這些功能的 Android 架構 API。如需更多進階主題,包括探討非 NDEF 資料的討論,請參閱「進階 NFC」。

使用 NDEF 資料和 Android 時,有兩種主要用途:

  • 讀取 NFC 標記中的 NDEF 資料
  • 使用 Android BeamTM 在不同裝置之間傳輸 NDEF 訊息

透過「標記分派系統」處理 NFC 標記中的 NDEF 資料,標記分派系統可分析找到的 NFC 標記、適當分類資料,並啟動對分類資料感興趣的應用程式。應用程式想要處理掃描的 NFC 標記,可以宣告意圖篩選器,並要求處理資料。

Android BeamTM 功能可讓裝置實際輕觸裝置,藉此將 NDEF 訊息推送至其他裝置。相較於藍牙等無線技術,這種互動可讓您更輕鬆地傳送資料,因為有了 NFC,就不需要手動探索或配對裝置。當兩部裝置進入連線範圍時,就會自動啟動連線。Android Beam 可透過一組 NFC API 使用,因此任何應用程式都能在裝置之間傳輸資訊。例如,聯絡人、瀏覽器和 YouTube 應用程式會使用 Android Beam 與其他裝置共用聯絡人、網頁和影片。

代碼分派系統

除非在裝置的「設定」選單中停用 NFC,否則在螢幕解鎖的情況下,Android 裝置通常會尋找 NFC 標記。當 Android 裝置找到 NFC 標記時,預期的行為是讓最合適的活動處理意圖,不必先詢問使用者要使用什麼應用程式。由於裝置掃描 NFC 標記的範圍極短,因此使用者手動選取活動時,可能會強制他們將裝置移出標記,因而中斷連線。您應該開發活動,只處理活動重視的 NFC 標記,避免活動選擇工具顯示。

為協助您達成此目標,Android 提供特殊的標記分派系統,可分析掃描的 NFC 標記、進行剖析,並嘗試找出對掃描資料感興趣的應用程式。運作方式如下:

  1. 剖析 NFC 標記並確定 MIME 類型,或能識別標記中資料酬載的 URI。
  2. 將 MIME 類型或 URI 和酬載封裝至意圖。如需這前兩個步驟的相關說明,請參閱「NFC 標記如何對應至 MIME 類型和 URI」。
  3. 根據意圖啟動活動。相關說明請參閱「NFC 標記如何分派給應用程式」一文。

NFC 標記如何對應至 MIME 類型和 URI

開始編寫 NFC 應用程式前,請務必瞭解不同類型的 NFC 標記、標記分派系統如何剖析 NFC 標記,以及標記分派系統偵測到 NDEF 訊息時會採取的特殊工作。NFC 標記採用多種技術,也可能以多種方式寫入資料。Android 支援最多 NDEF 標準 (由 NFC 論壇定義)。

NDEF 資料會封裝在包含一或多筆記錄 (NdefRecord) 的訊息 (NdefMessage)。每項 NDEF 記錄的格式都必須符合您要建立的記錄類型。Android 也支援不含 NDEF 資料的其他類型的標記,您可以使用 android.nfc.tech 套件中的類別來處理這些資料。如要進一步瞭解這些技術,請參閱「進階 NFC」主題。使用這類標記時,必須自行編寫通訊協定堆疊,以便與標記通訊,因此建議您盡可能使用 NDEF,以方便開發,並盡可能支援 Android 裝置。

注意:如要下載完整的 NDEF 規格,請前往「NFC 論壇規格與應用程式文件」網站,並參閱「建立常見的 NDEF 記錄類型」一文,取得如何建立 NDEF 記錄的範例。

現在 NFC 標記已有一些背景,下列各節將詳細說明 Android 如何處理 NDEF 格式的標記。當 Android 裝置掃描含有 NDEF 格式資料的 NFC 標記時,系統會剖析訊息並嘗試找出資料的 MIME 類型或識別 URI。為此,系統會讀取 NdefMessage 中的第一個 NdefRecord,以判斷如何解譯整個 NDEF 訊息 (一個 NDEF 訊息可以有多個 NDEF 記錄)。格式正確的 NDEF 訊息中,第一個 NdefRecord 包含下列欄位:

3 位元 TNF (類型名稱格式)
說明如何解讀變數長度類型欄位。如需有效值的說明,請參閱表 1
變數長度類型
說明記錄的類型。如果使用 TNF_WELL_KNOWN,請使用這個欄位指定記錄類型定義 (RTD)。表 2 說明瞭有效的 RTD 值。
變數長度 ID
記錄的專屬 ID。這個欄位不常使用,但如果您需要唯一識別標記,可以為該標記建立 ID。
可變長度酬載
您要讀取或寫入的實際資料酬載。NDEF 訊息可能包含多個 NDEF 記錄,因此請勿假設完整酬載位於 NDEF 訊息的第一個 NDEF 記錄中。

標記分派系統會使用 TNF 和類型欄位,嘗試將 MIME 類型或 URI 對應至 NDEF 訊息。如果成功,它會在 ACTION_NDEF_DISCOVERED 意圖中封裝這些資訊和實際的酬載。但有時候,標記分派系統無法根據第一筆 NDEF 記錄判斷資料類型。當 NDEF 資料無法對應至 MIME 類型或 URI,或是 NFC 標記未包含要開頭的 NDEF 資料時,就會發生這種情況。在這種情況下,含有標記技術相關資訊和酬載的 Tag 物件會改為封裝在 ACTION_TECH_DISCOVERED 意圖內。

表 1 說明瞭標記分派系統如何將 TNF 和類型欄位對應至 MIME 類型或 URI。並說明哪些 TNF 無法對應至 MIME 類型或 URI。在這種情況下,標記分派系統會改回 ACTION_TECH_DISCOVERED

例如,如果標記分派系統遇到 TNF_ABSOLUTE_URI 類型的記錄,就會將該記錄的變數長度類型欄位對應至 URI。標記分派系統會將該 URI 連同標記的其他資訊 (例如酬載) 封裝在 ACTION_NDEF_DISCOVERED 意圖的資料欄位中。另一方面,如果遇到 TNF_UNKNOWN 類型的記錄,則會改為建立封裝標記技術的意圖。

表 1. 支援的 TNF 及其對應項目

類型名稱格式 (TNF) 對應
TNF_ABSOLUTE_URI 依據類型欄位而定的 URI。
TNF_EMPTY 我們將改用 ACTION_TECH_DISCOVERED
TNF_EXTERNAL_TYPE 以類型欄位中的 URN 為依據的 URI。URN 經過編碼為 NDEF 類型欄位,格式為:<domain_name>:<service_name>。 Android 會以下列格式對應至 URI:vnd.android.nfc://ext/<domain_name>:<service_name>
TNF_MIME_MEDIA 根據類型欄位設定的 MIME 類型。
TNF_UNCHANGED 第一項記錄中無效,因此可以改回 ACTION_TECH_DISCOVERED
TNF_UNKNOWN 我們將改用 ACTION_TECH_DISCOVERED
TNF_WELL_KNOWN MIME 類型或 URI,視記錄類型定義 (RTD) 而定 (您在類型欄位中設定)。如要進一步瞭解可用的 RTD 及其對應,請參閱表 2

表 2. TNF_WELL_KNOWN 支援的 RTD 及其對應關係

記錄類型定義 (RTD) 對應
RTD_ALTERNATIVE_CARRIER 我們將改用 ACTION_TECH_DISCOVERED
RTD_HANDOVER_CARRIER 我們將改用 ACTION_TECH_DISCOVERED
RTD_HANDOVER_REQUEST 我們將改用 ACTION_TECH_DISCOVERED
RTD_HANDOVER_SELECT 我們將改用 ACTION_TECH_DISCOVERED
RTD_SMART_POSTER 基於剖析酬載而產生的 URI。
RTD_TEXT text/plain 的 MIME 類型。
RTD_URI 以酬載為依據的 URI。

如何將 NFC 標記分派到應用程式

標記分派系統建立可封裝 NFC 標記及其個人識別資訊的意圖後,會將意圖傳送至對該意圖進行篩選的感興趣的應用程式。如有多個應用程式可以處理意圖,系統會顯示活動選擇工具,讓使用者選取活動。標記分派系統會定義三個意圖,按優先順序由高到低依序列出:

  1. ACTION_NDEF_DISCOVERED:這個意圖可在掃描含有 NDEF 酬載的標記且為可辨識的類型時啟動活動。這是優先順序最高的意圖,標記調派系統會盡可能先使用這個意圖,再嘗試其他意圖啟動活動。
  2. ACTION_TECH_DISCOVERED:如果沒有為了處理 ACTION_NDEF_DISCOVERED 意圖而註冊的活動,標記分派系統會嘗試以這個意圖啟動應用程式。如果掃描的標記包含無法對應至 MIME 類型或 URI 的 NDEF 資料,或是標記不包含 NDEF 資料,但是已知的標記技術,則此意圖也會直接啟動 (不會先從 ACTION_NDEF_DISCOVERED 開始運作)。
  3. ACTION_TAG_DISCOVERED:如果沒有任何活動處理 ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED 意圖,就會啟動此意圖。

代碼分派系統的基本運作方式如下:

  1. 剖析 NFC 標記時 (ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED),請嘗試使用標記分派系統建立的意圖來啟動活動。
  2. 如果沒有針對該意圖篩選活動,請嘗試以優先順序次低的意圖 (ACTION_TECH_DISCOVEREDACTION_TAG_DISCOVERED) 啟動活動,直到意圖的應用程式篩選或標記分派系統嘗試所有可能的意圖為止。
  3. 如果沒有應用程式篩選任何意圖,則不執行任何動作。
圖 1:標記調度系統

請盡可能使用 NDEF 訊息和 ACTION_NDEF_DISCOVERED 意圖,因為這是三者中最具體的意圖。這項意圖可讓您比另外兩個意圖在適當時機啟動應用程式,為使用者提供更優質的體驗。

在 Android 資訊清單中要求 NFC 存取權

您必須先在 AndroidManifest.xml 檔案中宣告以下項目,才能存取裝置的 NFC 硬體並正確處理 NFC 意圖:

  • 存取 NFC 硬體的 NFC <uses-permission> 元素:
    <uses-permission android:name="android.permission.NFC" />
    
  • 應用程式可支援的最低 SDK 版本。API 級別 9 僅支援透過 ACTION_TAG_DISCOVERED 進行有限的標記傳送作業,且只會透過 EXTRA_NDEF_MESSAGES 額外提供 NDEF 訊息的存取權。無法存取其他代碼屬性或 I/O 作業。API 級別 10 提供全方位的讀取/寫入支援和前景 NDEF 推送,API 級別 14 可透過 Android Beam 以及額外的便利方法建立 NDEF 記錄,讓您輕鬆將 NDEF 訊息推送至其他裝置。
    <uses-sdk android:minSdkVersion="10"/>
    
  • uses-feature 元素可讓您的應用程式只在具備 NFC 硬體的裝置顯示在 Google Play 中:
    <uses-feature android:name="android.hardware.nfc" android:required="true" />
    

    如果您的應用程式使用 NFC 功能,但該功能對應用程式並不重要,您可以省略 uses-feature 元素,然後檢查 getDefaultAdapter() 是否為 null,以便在執行階段查看 NFC 可用性。

NFC 意圖篩選器

如要在掃描掃描您想處理的 NFC 標記時啟動應用程式,應用程式可以在 Android 資訊清單中篩選一、兩個或全部三個 NFC 意圖。不過,您通常會想篩選 ACTION_NDEF_DISCOVERED 意圖,以便在應用程式啟動時進行最大的控制。沒有任何應用程式篩選 ACTION_NDEF_DISCOVERED 或酬載並非 NDEF 時,ACTION_TECH_DISCOVERED 意圖是 ACTION_NDEF_DISCOVERED 的備用方案。篩選 ACTION_TAG_DISCOVERED 通常太過籠統,無法篩選類別。許多應用程式會在 ACTION_TAG_DISCOVERED 之前篩選 ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED,因此應用程式極有可能啟動。只有在未安裝其他應用程式來處理 ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED 意圖的情況下,應用程式才必須使用 ACTION_TAG_DISCOVERED 進行篩選。

由於 NFC 標記部署作業相當多元,而且並非由您控管,因此並非完全如此,因此在必要時,您可以改回使用另外兩個意圖。如果您能夠控管寫入的標記與資料類型,建議您使用 NDEF 設定標記格式。以下各節說明如何篩選各種意圖。

ACTION_NDEF_探索

如要篩選 ACTION_NDEF_DISCOVERED 意圖,請宣告意圖篩選器以及您要篩選的資料類型。以下範例篩選了 MIME 類型為 text/plainACTION_NDEF_DISCOVERED 意圖:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain" />
</intent-filter>

下列範例以 https://developer.android.com/index.html 的形式篩選 URI。

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
   <data android:scheme="https"
              android:host="developer.android.com"
              android:pathPrefix="/index.html" />
</intent-filter>

行動_TECH_探索

如果您的活動篩選的是 ACTION_TECH_DISCOVERED 意圖,您必須建立 XML 資源檔案,在 tech-list 組合中指定活動支援的技術。如果 tech-list 集是標記支援的技術子集,只要呼叫 getTechList() 即可取得標記。

舉例來說,如果掃描的標記支援 Mifare Classic、NdefFormatable 和 NfcA,則 tech-list 集必須指定全部三、兩項或其中一項技術 (且不要指定其他技術),才能比對您的活動。

下列範例定義所有技術。您必須移除 NFC 標記不支援的組合。將這個檔案儲存至 <project-root>/res/xml 資料夾中 (您可自由命名)。

<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 組合。每個 tech-list 組合都會分開考慮,如果任何單一 tech-list 組合是 getTechList() 傳回的技術子集,您的活動就會視為相符。這會提供用於比對技術的 ANDOR 語意。以下範例比對了可支援 NfcA 和 Ndef 技術,或是可支援 NfcB 和 Ndef 技術的代碼:

<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>
    <tech-list>
        <tech>android.nfc.tech.NfcB</tech>
        <tech>android.nfc.tech.Ndef</tech>
    </tech-list>
</resources>

AndroidManifest.xml 檔案中,指定您剛剛在 <activity> 元素內的 <meta-data> 元素中建立的資源檔案,如以下範例所示:

<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>

如要進一步瞭解如何使用標記技術和 ACTION_TECH_DISCOVERED 意圖,請參閱進階 NFC 文件中的「使用支援的標記技術」一節。

ACTION_TAG_探索

如要篩選 ACTION_TAG_DISCOVERED,請使用下列意圖篩選器:

<intent-filter>
    <action android:name="android.nfc.action.TAG_DISCOVERED"/>
</intent-filter>

從意圖取得資訊

如果活動是因為 NFC 意圖而啟動,您可以從意圖中取得已掃描 NFC 標記的相關資訊。視掃描的標記而定,意圖可能包含下列額外項目:

如要取得這些額外功能,請檢查活動是否是使用其中一個 NFC 意圖啟動,以確保系統已掃描標記,然後再從意圖中取得額外項目。以下範例會檢查 ACTION_NDEF_DISCOVERED 意圖,並從意圖額外項目取得 NDEF 訊息。

Kotlin

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    ...
    if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) {
        intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)?.also { rawMessages ->
            val messages: List<NdefMessage> = rawMessages.map { it as NdefMessage }
            // Process the messages array.
            ...
        }
    }
}

Java

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    ...
    if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
        Parcelable[] rawMessages =
            intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
        if (rawMessages != null) {
            NdefMessage[] messages = new NdefMessage[rawMessages.length];
            for (int i = 0; i < rawMessages.length; i++) {
                messages[i] = (NdefMessage) rawMessages[i];
            }
            // Process the messages array.
            ...
        }
    }
}

或者,您也可以從意圖取得 Tag 物件,其中包含酬載,並可讓您列舉標記的技術:

Kotlin

val tag: Tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)

Java

Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

建立常見的 NDEF 記錄類型

本節說明如何建立常見的 NDEF 記錄類型,協助您寫入 NFC 標記或使用 Android Beam 傳送資料。從 Android 4.0 (API 級別 14) 開始,您可以使用 createUri() 方法自動建立 URI 記錄。從 Android 4.1 (API 級別 16) 開始,您可以使用 createExternal()createMime() 來建立 MIME 和外部類型 NDEF 記錄。請盡可能使用這些輔助方法,以免手動建立 NDEF 記錄時出錯。

本節也會說明如何為記錄建立對應的意圖篩選器。所有 NDEF 記錄範例都應位於您要寫入標記或控制的 NDEF 訊息的第一個 NDEF 記錄中。

TNF_ABSOLUTE_URI

注意:建議您使用 RTD_URI 類型,而非 TNF_ABSOLUTE_URI,因為這樣更有效率。

您可以透過下列方式建立 TNF_ABSOLUTE_URI NDEF 記錄:

Kotlin

val uriRecord = ByteArray(0).let { emptyByteArray ->
    NdefRecord(
            TNF_ABSOLUTE_URI,
            "https://developer.android.com/index.html".toByteArray(Charset.forName("US-ASCII")),
            emptyByteArray,
            emptyByteArray
    )
}

Java

NdefRecord uriRecord = new NdefRecord(
    NdefRecord.TNF_ABSOLUTE_URI ,
    "https://developer.android.com/index.html".getBytes(Charset.forName("US-ASCII")),
    new byte[0], new byte[0]);

上一個 NDEF 記錄的意圖篩選器看起來會像這樣:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="https"
        android:host="developer.android.com"
        android:pathPrefix="/index.html" />
</intent-filter>

TNF_MIME 媒體

您可以透過下列方式建立 TNF_MIME_MEDIA NDEF 記錄:

使用 createMime() 方法:

Kotlin

val mimeRecord = NdefRecord.createMime(
        "application/vnd.com.example.android.beam",
        "Beam me up, Android".toByteArray(Charset.forName("US-ASCII"))
)

Java

NdefRecord mimeRecord = NdefRecord.createMime("application/vnd.com.example.android.beam",
    "Beam me up, Android".getBytes(Charset.forName("US-ASCII")));

手動建立 NdefRecord

Kotlin

val mimeRecord = Charset.forName("US-ASCII").let { usAscii ->
    NdefRecord(
            NdefRecord.TNF_MIME_MEDIA,
            "application/vnd.com.example.android.beam".toByteArray(usAscii),
            ByteArray(0),
            "Beam me up, Android!".toByteArray(usAscii)
    )
}

Java

NdefRecord mimeRecord = new NdefRecord(
    NdefRecord.TNF_MIME_MEDIA ,
    "application/vnd.com.example.android.beam".getBytes(Charset.forName("US-ASCII")),
    new byte[0], "Beam me up, Android!".getBytes(Charset.forName("US-ASCII")));

上一個 NDEF 記錄的意圖篩選器看起來會像這樣:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="application/vnd.com.example.android.beam" />
</intent-filter>

含 RTD_TEXT 的 TNF_WELL_KNOWN

您可以透過下列方式建立 TNF_WELL_KNOWN NDEF 記錄:

Kotlin

fun createTextRecord(payload: String, locale: Locale, encodeInUtf8: Boolean): NdefRecord {
    val langBytes = locale.language.toByteArray(Charset.forName("US-ASCII"))
    val utfEncoding = if (encodeInUtf8) Charset.forName("UTF-8") else Charset.forName("UTF-16")
    val textBytes = payload.toByteArray(utfEncoding)
    val utfBit: Int = if (encodeInUtf8) 0 else 1 shl 7
    val status = (utfBit + langBytes.size).toChar()
    val data = ByteArray(1 + langBytes.size + textBytes.size)
    data[0] = status.toByte()
    System.arraycopy(langBytes, 0, data, 1, langBytes.size)
    System.arraycopy(textBytes, 0, data, 1 + langBytes.size, textBytes.size)
    return NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, ByteArray(0), data)
}

Java

public NdefRecord createTextRecord(String payload, Locale locale, boolean encodeInUtf8) {
    byte[] langBytes = locale.getLanguage().getBytes(Charset.forName("US-ASCII"));
    Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8") : Charset.forName("UTF-16");
    byte[] textBytes = payload.getBytes(utfEncoding);
    int utfBit = encodeInUtf8 ? 0 : (1 << 7);
    char status = (char) (utfBit + langBytes.length);
    byte[] data = new byte[1 + langBytes.length + textBytes.length];
    data[0] = (byte) status;
    System.arraycopy(langBytes, 0, data, 1, langBytes.length);
    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);
    return record;
}

上一個 NDEF 記錄的意圖篩選器看起來會像這樣:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/plain" />
</intent-filter>

含有 RTD_URI 的 TNF_WELL_KNOWN

您可以透過下列方式建立 TNF_WELL_KNOWN NDEF 記錄:

使用 createUri(String) 方法:

Kotlin

val rtdUriRecord1 = NdefRecord.createUri("https://example.com")

Java

NdefRecord rtdUriRecord1 = NdefRecord.createUri("https://example.com");

使用 createUri(Uri) 方法:

Kotlin

val rtdUriRecord2 = Uri.parse("https://example.com").let { uri ->
    NdefRecord.createUri(uri)
}

Java

Uri uri = Uri.parse("https://example.com");
NdefRecord rtdUriRecord2 = NdefRecord.createUri(uri);

手動建立 NdefRecord

Kotlin

val uriField = "example.com".toByteArray(Charset.forName("US-ASCII"))
val payload = ByteArray(uriField.size + 1)                   //add 1 for the URI Prefix
payload [0] = 0x01                                           //prefixes https://www. to the URI
System.arraycopy(uriField, 0, payload, 1, uriField.size)     //appends URI to payload
val rtdUriRecord = NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, ByteArray(0), payload)

Java

byte[] uriField = "example.com".getBytes(Charset.forName("US-ASCII"));
byte[] payload = new byte[uriField.length + 1];              //add 1 for the URI Prefix
payload[0] = 0x01;                                           //prefixes https://www. to the URI
System.arraycopy(uriField, 0, payload, 1, uriField.length);  //appends URI to payload
NdefRecord rtdUriRecord = new NdefRecord(
    NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, new byte[0], payload);

上一個 NDEF 記錄的意圖篩選器看起來會像這樣:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="https"
        android:host="example.com"
        android:pathPrefix="" />
</intent-filter>

TNF 外部類型

您可以透過下列方式建立 TNF_EXTERNAL_TYPE NDEF 記錄:

使用 createExternal() 方法:

Kotlin

var payload: ByteArray //assign to your data
val domain = "com.example" //usually your app's package name
val type = "externalType"
val extRecord = NdefRecord.createExternal(domain, type, payload)

Java

byte[] payload; //assign to your data
String domain = "com.example"; //usually your app's package name
String type = "externalType";
NdefRecord extRecord = NdefRecord.createExternal(domain, type, payload);

手動建立 NdefRecord

Kotlin

var payload: ByteArray
...
val extRecord = NdefRecord(
        NdefRecord.TNF_EXTERNAL_TYPE,
        "com.example:externalType".toByteArray(Charset.forName("US-ASCII")),
        ByteArray(0),
        payload
)

Java

byte[] payload;
...
NdefRecord extRecord = new NdefRecord(
    NdefRecord.TNF_EXTERNAL_TYPE, "com.example:externalType".getBytes(Charset.forName("US-ASCII")),
    new byte[0], payload);

上一個 NDEF 記錄的意圖篩選器看起來會像這樣:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="vnd.android.nfc"
        android:host="ext"
        android:pathPrefix="/com.example:externalType"/>
</intent-filter>

使用 TNF_EXTERNAL_TYPE 進行更一般的 NFC 標記部署,為 Android 和非 Android 裝置提供更完善的支援。

注意TNF_EXTERNAL_TYPE 的 URN 具有標準格式:urn:nfc:ext:example.com:externalType,但 NFC 論壇 RTD 規格宣告必須在 NDEF 記錄中省略 URN 的 urn:nfc:ext: 部分。因此,您只需要提供網域 (範例中為 example.com) 和類型 (範例中為 externalType) 並以半形冒號分隔的類型。分派 TNF_EXTERNAL_TYPE 時,Android 會將 urn:nfc:ext:example.com:externalType URN 轉換為 vnd.android.nfc://ext/example.com:externalType URI,這是範例中的意圖篩選器宣告。

Android 應用程式記錄

Android 應用程式記錄 (AAR) 導入於 Android 4.0 (API 級別 14) 中,可讓您在掃描 NFC 標記時更準確地啟動應用程式。AAR 含有內嵌在 NDEF 記錄內的應用程式的套件名稱。您可以將 AAR 新增至 NDEF 訊息的任何 NDEF 記錄中,因為 Android 會搜尋 AAR 的整個 NDEF 訊息。如果找到 AAR,就會根據 AAR 中的套件名稱啟動應用程式。如果裝置上沒有該應用程式,系統會啟動 Google Play 下載應用程式。

如要避免其他應用程式依相同意圖進行篩選,並可能處理您部署的特定標記,AAR 就能派上用場。由於套件名稱限制的關係,AAR 只能在應用程式層級使用,與意圖篩選一樣,無法在活動層級支援 AAR。如要在活動層級處理意圖,請使用意圖篩選器

如果標記內含 AAR,代碼分派系統就會以以下方式傳送:

  1. 嘗試照常使用意圖篩選器啟動活動。如果符合意圖的 Activity 也符合 AAR,請啟動活動。
  2. 如果用於篩選意圖的活動與 AAR 不符,如果有多個活動可以處理該意圖,或是沒有任何活動處理意圖,則啟動 AAR 指定的應用程式。
  3. 如果沒有任何應用程式的開頭是 AAR,請前往 Google Play 下載採用 AAR 的應用程式。

注意:您可以使用前景調度系統覆寫 AAR 和意圖調度系統,讓系統在找到 NFC 標記時,讓前景活動優先執行。使用這個方法時,活動必須在前景執行,才能覆寫 AAR 和意圖調派系統。

如果您仍想篩選不含 AAR 的掃描標記,可以照常宣告意圖篩選器。如果您的應用程式想查看其他不含 AAR 的標記,這就非常實用。舉例來說,您可能希望確保應用程式會處理您部署的專屬標記和第三方部署的一般標記。請注意,AAR 僅適用於 Android 4.0 以上版本裝置,因此在部署標記時,您最好搭配使用 AAR 和 MIME 類型/URI,以便支援最多種裝置。此外,在部署 NFC 標記時,請考慮如何編寫 NFC 標記,以便支援大多數的裝置 (Android 裝置和其他裝置)。如要這麼做,請定義相對獨特的 MIME 類型或 URI,讓應用程式更容易區分。

Android 提供簡易的 API 可用於建立 AAR createApplicationRecord()。您只需將 AAR 嵌入 NdefMessage 中的任何位置即可。您不會使用 NdefMessage 的第一筆記錄,除非 AAR 是 NdefMessage 中唯一的記錄。這是因為 Android 系統會檢查 NdefMessage 的第一個記錄,判斷標記的 MIME 類型或 URI (用來建立用於篩選應用程式的意圖)。以下程式碼說明如何建立 AAR:

Kotlin

val msg = NdefMessage(
        arrayOf(
                ...,
                NdefRecord.createApplicationRecord("com.example.android.beam")
        )
)

Java

NdefMessage msg = new NdefMessage(
        new NdefRecord[] {
            ...,
            NdefRecord.createApplicationRecord("com.example.android.beam")}
        );
)

將 NDEF 訊息傳輸至其他裝置

Android Beam 可讓您在兩部 Android 裝置之間輕鬆交換點對點資料。如果應用程式要將資料傳輸至其他裝置,就必須在前景執行,且裝置不得被鎖定。當正在充電的裝置與接收端裝置接觸足夠的接觸後,漫遊裝置會顯示「輕觸即可傳輸」 UI。接著,使用者可以選擇是否將訊息傳輸至接收裝置。

注意:前景 NDEF 推送功能適用於 API 級別 10,可提供與 Android Beam 類似的功能。這些 API 已淘汰,但可以支援舊版裝置。詳情請參閱《enableForegroundNdefPush()》。

您可以呼叫以下其中一種方法,為應用程式啟用 Android Beam:

一項活動一次只能推送一則 NDEF 訊息,因此如果同時設定,setNdefPushMessageCallback() 的優先順序高於 setNdefPushMessage()。如要使用 Android Beam,必須滿足下列一般規範:

  • 繫結資料的活動必須在前景執行。兩部裝置的螢幕都必須解鎖。
  • 您必須將要封裝的資料封裝在 NdefMessage 物件中。
  • 接收橫樑資料的 NFC 裝置必須支援 com.android.npp NDEF 推送通訊協定,或 NFC 論壇的 SNEP (簡易 NDEF Exchange 通訊協定)。如果裝置搭載 API 級別 9 (Android 2.3) 至 API 級別 13 (Android 3.2),則須使用 com.android.npp 通訊協定。API 級別 14 (Android 4.0) 以上版本都需要 com.android.npp 和 SNEP。

注意:如果您的活動會啟用 Android Beam 且位於前景,則標準意圖分派系統就會停用。然而,如果您的活動也啟用了 前景分派功能,則仍可掃描符合前景分派作業中設定的意圖篩選器相符的標記。

如何啟用 Android Beam:

  1. 建立 NdefMessage,其中包含要推送至其他裝置的 NdefRecord
  2. 使用 NdefMessage 呼叫 setNdefPushMessage(),或呼叫 setNdefPushMessageCallback,在活動的 onCreate() 方法中傳入 NfcAdapter.CreateNdefMessageCallback 物件。這些方法需要至少一個要透過 Android Beam 啟用的活動,以及其他要啟用的活動選用清單。

    一般來說,如果 Activity 需要隨時推送相同的 NDEF 訊息,也就是兩部裝置在連線範圍內即可進行通訊,通常就會使用 setNdefPushMessage()。如果您的應用程式需要目前的背景資訊,並希望根據使用者在應用程式內執行的動作推送 NDEF 訊息,請使用 setNdefPushMessageCallback

以下範例顯示簡易活動如何在活動的 onCreate() 方法中呼叫 NfcAdapter.CreateNdefMessageCallback (如需完整範例,請參閱 AndroidBeamDemo)。這個範例也提供有助於建立 MIME 記錄的方法:

Kotlin

package com.example.android.beam

import android.app.Activity
import android.content.Intent
import android.nfc.NdefMessage
import android.nfc.NdefRecord
import android.nfc.NfcAdapter
import android.nfc.NfcAdapter.CreateNdefMessageCallback
import android.nfc.NfcEvent
import android.os.Bundle
import android.os.Parcelable
import android.widget.TextView
import android.widget.Toast
import java.nio.charset.Charset

class Beam : Activity(), NfcAdapter.CreateNdefMessageCallback {
    
    private var nfcAdapter: NfcAdapter? = null
    private lateinit var textView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
        textView = findViewById(R.id.textView)
        // Check for available NFC Adapter
        nfcAdapter = NfcAdapter.getDefaultAdapter(this)
        if (nfcAdapter == null) {
            Toast.makeText(this, "NFC is not available", Toast.LENGTH_LONG).show()
            finish()
            return
        }
        // Register callback
        nfcAdapter?.setNdefPushMessageCallback(this, this)
    }

    override fun createNdefMessage(event: NfcEvent): NdefMessage {
        val text = "Beam me up, Android!\n\n" +
                "Beam Time: " + System.currentTimeMillis()
        return NdefMessage(
                arrayOf(
                        createMime("application/vnd.com.example.android.beam", text.toByteArray())
                )
                /**
                 * The Android Application Record (AAR) is commented out. When a device
                 * receives a push with an AAR in it, the application specified in the AAR
                 * is guaranteed to run. The AAR overrides the tag dispatch system.
                 * You can add it back in to guarantee that this
                 * activity starts when receiving a beamed message. For now, this code
                 * uses the tag dispatch system.
                 *///,NdefRecord.createApplicationRecord("com.example.android.beam")
        )
    }

    override fun onResume() {
        super.onResume()
        // Check to see that the Activity started due to an Android Beam
        if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) {
            processIntent(intent)
        }
    }

    override fun onNewIntent(intent: Intent) {
        // onResume gets called after this to handle the intent
        setIntent(intent)
    }

    /**
     * Parses the NDEF Message from the intent and prints to the TextView
     */
    private fun processIntent(intent: Intent) {
        textView = findViewById(R.id.textView)
        // only one message sent during the beam
        intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)?.also { rawMsgs ->
            (rawMsgs[0] as NdefMessage).apply {
                // record 0 contains the MIME type, record 1 is the AAR, if present
                textView.text = String(records[0].payload)
            }
        }
    }
}

Java

package com.example.android.beam;

import android.app.Activity;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcEvent;
import android.os.Bundle;
import android.os.Parcelable;
import android.widget.TextView;
import android.widget.Toast;
import java.nio.charset.Charset;


public class Beam extends Activity implements CreateNdefMessageCallback {
    NfcAdapter nfcAdapter;
    TextView textView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        TextView textView = (TextView) findViewById(R.id.textView);
        // Check for available NFC Adapter
        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if (nfcAdapter == null) {
            Toast.makeText(this, "NFC is not available", Toast.LENGTH_LONG).show();
            finish();
            return;
        }
        // Register callback
        nfcAdapter.setNdefPushMessageCallback(this, this);
    }

    @Override
    public NdefMessage createNdefMessage(NfcEvent event) {
        String text = ("Beam me up, Android!\n\n" +
                "Beam Time: " + System.currentTimeMillis());
        NdefMessage msg = new NdefMessage(
                new NdefRecord[] { createMime(
                        "application/vnd.com.example.android.beam", text.getBytes())
         /**
          * The Android Application Record (AAR) is commented out. When a device
          * receives a push with an AAR in it, the application specified in the AAR
          * is guaranteed to run. The AAR overrides the tag dispatch system.
          * You can add it back in to guarantee that this
          * activity starts when receiving a beamed message. For now, this code
          * uses the tag dispatch system.
          */
          //,NdefRecord.createApplicationRecord("com.example.android.beam")
        });
        return msg;
    }

    @Override
    public void onResume() {
        super.onResume();
        // Check to see that the Activity started due to an Android Beam
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
            processIntent(getIntent());
        }
    }

    @Override
    public void onNewIntent(Intent intent) {
        // onResume gets called after this to handle the intent
        setIntent(intent);
    }

    /**
     * Parses the NDEF Message from the intent and prints to the TextView
     */
    void processIntent(Intent intent) {
        textView = (TextView) findViewById(R.id.textView);
        Parcelable[] rawMsgs = intent.getParcelableArrayExtra(
                NfcAdapter.EXTRA_NDEF_MESSAGES);
        // only one message sent during the beam
        NdefMessage msg = (NdefMessage) rawMsgs[0];
        // record 0 contains the MIME type, record 1 is the AAR, if present
        textView.setText(new String(msg.getRecords()[0].getPayload()));
    }
}

請注意,這個程式碼會註解排除 AAR,您可以移除此項目。如果您啟用了 AAR,AAR 中指定的應用程式一律會收到 Android Beam 訊息。如果沒有該應用程式,Google Play 就會開始下載應用程式。因此,若是使用 Android 4.0 以上版本的裝置,如果是使用 AAR,則技術上不需要下列意圖篩選器:

<intent-filter>
  <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
  <category android:name="android.intent.category.DEFAULT"/>
  <data android:mimeType="application/vnd.com.example.android.beam"/>
</intent-filter>

有了這個意圖篩選器,com.example.android.beam應用程式現在可以在掃描 NFC 標記、收到類型為 com.example.android.beam 的 Android Beam 或 NDEF 格式訊息包含 application/vnd.com.example.android.beam 類型的 MIME 記錄時啟動。

雖然 AAR 可保證應用程式已啟動或下載,但仍建議您使用意圖篩選器,因為這類篩選器可讓您在應用程式中啟動您選擇的活動,而不是一律在 AAR 指定的套件內啟動主要活動。AAR 沒有活動層級的精細程度。此外,由於部分 Android 裝置不支援 AAR,您也應在 NDEF 訊息的第一筆 NDEF 記錄中嵌入識別資訊,並依此篩選資訊。如要進一步瞭解如何建立記錄,請參閱「建立 NDEF 記錄的常見類型」一文。