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 標記有多種技術,您也可以透過多種不同方式將資料寫入 NFC 標記。Android 支援最常使用的 NDEF 標準,這項標準由 NFC 論壇定義。

NDEF 資料會封裝在包含一或多個記錄 (NdefRecord) 的訊息 (NdefMessage) 中。每筆 NDEF 記錄都必須根據要建立的記錄類型指定格式正確。Android 也支援不含 NDEF 資料的其他類型的標記。您可以使用 android.nfc.tech 套件中的類別,以使用這些標記。如要進一步瞭解這些技術,請參閱進階 NFC 主題。使用這些其他類型的標記時,您必須編寫自己的通訊協定堆疊來與標記通訊,因此我們建議您在可能方便開發及最大限度地支援 Android 裝置時使用 NDEF。

注意:如要下載完整的 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)。如要瞭解有效的 RTD 值,請參閱表 2
變數長度 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。標記分派系統在 ACTION_NDEF_DISCOVERED 意圖的資料欄位中會封裝該 URI 和標記的其他資訊,例如酬載。另一方面,如果遇到 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 會以 vnd.android.nfc://ext/<domain_name>:<service_name> 格式將此項目對應至 URI。
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 酬載的標記且屬於可辨識的類型,此意圖就可用來啟動活動。這是優先順序最高的意圖,標記調度系統會盡可能先使用這個意圖啟動 Activity,然後再嘗試其他意圖。
  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 元素可讓應用程式在 Google Play 中顯示,前提是裝置必須具備 NFC 硬體:
    <uses-feature android:name="android.hardware.nfc" android:required="true" />
    

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

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

動作消失

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

舉例來說,如果掃描的標記支援 MifareClassic、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 組合都會視為獨立項目,如果 getTechList() 傳回的技術子集有任一 tech-list 組合,系統就會將您的活動視為比對相符。這會為比對技術提供 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>

TNF_WELL_KNOWN (含 RTD_TEXT)

您可以按照下列方式建立 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 會搜尋整個 NDEF 訊息以獲取 AAR。如果找到 AAR,系統就會根據 AAR 內的套件名稱啟動應用程式。如果裝置上沒有該應用程式,系統會啟動 Google Play 來下載應用程式。

如果您不希望其他應用程式篩選同一個意圖,也希望處理您已部署的特定標記,AAR 非常實用。由於套件名稱限制的關係,AAR 只能在應用程式層級受到支援,與意圖篩選器一樣,不在活動層級。如果您想在活動層級處理意圖,請使用意圖篩選器

如果標記包含 AAR,標記分派系統會以下列方式分派:

  1. 嘗試照常使用意圖篩選器啟動活動。如果符合意圖的活動也符合 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()。您只需要在 NdefMessage 的任何位置嵌入 AAR。您不想使用 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 裝置之間進行簡單的點對點資料交換。要將資料傳輸到其他裝置的應用程式必須在前景執行,且裝置不得接收接收資料的應用程式。當抬頭裝置與接收裝置接觸足夠的接觸時,傳輸裝置會顯示「輕觸即可傳輸」使用者介面。使用者可以選擇是否將訊息傳輸至接收裝置。

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

您可以呼叫下列兩種方法之一,為您的應用程式啟用 Android Beam:

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

  • 影響資料的活動必須在前景。兩部裝置的螢幕都必須解鎖。
  • 您必須在 NdefMessage 物件中封裝您要維護的資料。
  • 接收傳輸資料的 NFC 裝置必須支援 com.android.npp NDEF 推送通訊協定或 NFC 論壇的 SNEP (簡易 NDEF 交換通訊協定)。搭載 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 會開始下載該應用程式。因此,如果使用 AAR,Android 4.0 或以上版本的裝置就不需使用下列意圖篩選器:

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

現在,只要系統在掃描 NFC 標記、接收 AAR 類型為 com.example.android.beam 或 NDEF 格式的訊息包含 application/vnd.com.example.android.beam 類型的 MIME 記錄時,即可啟動 com.example.android.beam應用程式。

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