NFC 基础知识

本文介绍了您会在 Android 中执行的基本 NFC 任务,如何以 NDEF 消息的形式收发 NFC 数据,以及支持这些功能的 Android 框架 API。如需详细了解高级主题(包括有关如何处理非 NDEF 数据的讨论),请参阅高级 NFC

从 NFC 标签读取 NDEF 数据的操作由标签调度系统进行处理,该系统会分析已发现的 NFC 标签,对相应数据进行适当分类,然后启动对分类后的数据感兴趣的应用。想要处理扫描的 NFC 标签的应用可以声明 intent 过滤器并请求处理数据。

标签调度系统

Android 设备通常会在屏幕解锁后查找 NFC 标签,除非设备的“设置”菜单中停用了 NFC 功能。当 Android 设备发现 NFC 标签时,期望的行为是让最合适的 activity 处理 intent,而不询问用户要使用哪个应用。由于设备是在非常短的范围内扫描 NFC 标签,因此让用户手动选择 activity 可能会迫使他们将设备远离标签并断开连接。您应以适当方式开发您的 activity,使其仅处理所关注的 NFC 标签,以避免 Activity 选择器出现。

为帮助您实现这一目标,Android 提供了一个特殊的标签调度系统,用于分析扫描到的 NFC 标签、解析它们并尝试找到对扫描到的数据感兴趣的应用。具体方法如下:

  1. 解析 NFC 标签并确定 MIME 类型或 URI(后者用于标识标签中的数据负载)。
  2. 将 MIME 类型或 URI 与负载一起封装到 Intent 中。如何将 NFC 标签映射到 MIME 类型和 URI 中介绍了前两个步骤。
  3. 根据 Intent 启动 Activity。如何将 NFC 标签分发到应用中介绍了此步骤。

如何将 NFC 标签映射到 MIME 类型和 URI

在开始编写 NFC 应用之前,请务必了解不同类型的 NFC 标签、标签调度系统如何解析 NFC 标签,以及标签调度系统在检测到 NDEF 消息后所执行的特殊工作。NFC 标签涉及多种技术,也可以通过许多不同的方式将数据写入 NFC 标签中。Android 对 NFC Forum 定义的 NDEF 标准的支持最完备。

NDEF 数据封装在包含一条或多条记录 (NdefRecord) 的消息 (NdefMessage) 内。每条 NDEF 记录的格式都必须正确,符合您要创建的记录所属的类型对应的规范。Android 还支持其他类型的不包含 NDEF 数据的标签,您可以通过使用 android.nfc.tech 软件包中的类来使用这些标签。如需详细了解这些技术,请参阅高级 NFC 主题。在处理这些其他类型的标签时,您需要通过编写自己的协议栈来与标签进行通信,因此,我们建议您尽可能使用 NDEF,以简化开发,同时最大限度地支持 Android 设备。

注意:如需下载完整的 NDEF 规范,请前往 NFC Forum 规范和应用文档网站,并参阅创建常见类型的 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。
可变长度负载
您要读取或写入的实际数据负载。一个 NDEF 消息可以包含多条 NDEF 记录,因此不要假定 NDEF 消息的第一条 NDEF 记录中就有完整的负载。

标签调度系统使用 TNF 和类型字段来尝试将 MIME 类型或 URI 映射到 NDEF 消息。如果成功映射,它会将相关信息与实际负载一起封装到 ACTION_NDEF_DISCOVERED Intent 内。不过,在某些情况下,标签调度系统无法根据第一条 NDEF 记录确定数据类型。如果 NDEF 数据无法映射到 MIME 类型或 URI,或者 NFC 标签不包含 NDEF 数据,就会出现上述情况。在此类情况下,标签调度系统会转而将含有标签技术相关信息的 Tag 对象及负载封装到 ACTION_TECH_DISCOVERED intent 中。

表 1 介绍了标签调度系统如何将 TNF 和类型字段映射到 MIME 类型或 URI。还介绍了哪些 TNF 无法映射到 MIME 类型或 URI。如果无法映射,标签调度系统会回退到 ACTION_TECH_DISCOVERED

例如,如果标签调度系统遇到 TNF_ABSOLUTE_URI 类型的记录,则会将该记录的可变长度类型字段映射到 URI。标签调度系统将此 URI 连同与标签有关的其他信息(如负载)一起封装在 ACTION_NDEF_DISCOVERED intent 的数据字段中。另一方面,如果标签调度系统遇到 TNF_UNKNOWN 类型的记录,则会转而创建一个 intent,用于封装与标签技术相关的信息。

表 1. 支持的 TNF 及其映射

类型名称格式 (TNF) 映射
TNF_ABSOLUTE_URI 基于类型字段的 URI。
TNF_EMPTY 回退到 ACTION_TECH_DISCOVERED
TNF_EXTERNAL_TYPE 基于类型字段中 URN 的 URI。URN 以缩短形式 (<domain_name>:<service_name>) 编码到 NDEF 类型字段中。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 标签及其标识信息的 intent 后,它会将该 intent 发送给感兴趣的应用,由这些应用对其进行过滤。如果有多个应用可处理该 intent,系统会显示 activity 选择器,供用户选择要使用的 activity。代码植入调度系统定义了三种 intent,按优先级从高到低列出如下:

  1. ACTION_NDEF_DISCOVERED:如果扫描到包含 NDEF 负载的标签,并且可识别其类型,则使用此 intent 启动 activity。这是优先级最高的 intent,标签调度系统会尽可能尝试使用此 intent 启动 activity,在行不通时才会尝试使用其他 intent。
  2. ACTION_TECH_DISCOVERED:如果没有登记要处理 ACTION_NDEF_DISCOVERED intent 的 activity,则标签调度系统会尝试使用此 intent 来启动应用。此外,如果扫描到的标签包含无法映射到 MIME 类型或 URI 的 NDEF 数据,或者该标签不包含 NDEF 数据,但它使用了已知的标签技术,那么也会直接启动此 intent(无需先启动 ACTION_NDEF_DISCOVERED)。
  3. ACTION_TAG_DISCOVERED:如果没有处理 ACTION_NDEF_DISCOVERED 或者 ACTION_TECH_DISCOVERED intent 的 activity,则使用此 intent 启动 activity。

标签调度系统的基本工作方式如下:

  1. 在解析 NFC 标签(ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED)时,尝试使用由标签调度系统创建的 intent 启动 activity。
  2. 如果不存在过滤该 intent 的 activity,则尝试使用下一优先级的 intent(ACTION_TECH_DISCOVEREDACTION_TAG_DISCOVERED)启动 activity,直到应用过滤该 intent 或者直到标签调度系统试完所有可能的 intent。
  3. 如果没有应用过滤任何 Intent,则不执行任何操作。
图 1. 标签调度系统

尽可能使用 NDEF 消息和 ACTION_NDEF_DISCOVERED intent,因为它是三种 intent 中最具体的一种。与另外两个 intent 相比,此 intent 可让您在更合适的时间启动应用,从而为用户提供更好的体验。

在 Android 清单中请求 NFC 访问权限

您必须先在 AndroidManifest.xml 文件中声明以下内容,然后才能访问设备的 NFC 硬件并正确处理 NFC intent:

  • NFC <uses-permission> 元素(用于访问 NFC 硬件):
    <uses-permission android:name="android.permission.NFC" />
    
  • 您的应用支持的最低 SDK 版本。API 级别 9 仅通过 ACTION_TAG_DISCOVERED 支持有限的标签调度,并且只能通过 EXTRA_NDEF_MESSAGES extra 提供对 NDEF 消息的访问权限。无法访问其他任何标签属性或 I/O 操作。API 级别 10 提供全面的读取器/写入器支持以及前台 NDEF 推送功能;API 级别 14 则提供了用于创建 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 Intent

如需在扫描到您要处理的 NFC 标签时启动您的应用,您的应用可以在 Android 清单中过滤一个、两个或所有三个 NFC intent。不过,您通常需要过滤 ACTION_NDEF_DISCOVERED intent,以便最大限度地控制应用的启动时间。如果没有应用过滤 ACTION_NDEF_DISCOVERED,或者负载不是 NDEF,ACTION_TECH_DISCOVERED intent 会取代 ACTION_NDEF_DISCOVEREDACTION_TAG_DISCOVERED 通常因过于笼统而不适合过滤。许多应用会在过滤 ACTION_TAG_DISCOVERED 前过滤 ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED,导致您的应用启动的概率会比较低。过滤 ACTION_TAG_DISCOVERED 是应用在没有其他应用来处理 ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED intent 的情况下的最后一道保险。

由于 NFC 标签部署各有不同,并且很多时候它们都不由您控制,因此,ACTION_NDEF_DISCOVERED 不一定每次都可用,您可以根据需要回退到另外两种 intent。如果您可以控制标签和写入数据的类型,建议您使用 NDEF 来设置标签的格式。以下几部分介绍了如何过滤各类 Intent。

ACTION_NDEF_DISCOVERED

如需过滤 ACTION_NDEF_DISCOVERED intent,请声明 intent 过滤器以及要过滤的数据类型。以下示例展示了如何过滤 MIME 类型为 text/plainACTION_NDEF_DISCOVERED intent:

<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

如果您的 activity 过滤 ACTION_TECH_DISCOVERED intent,您必须创建一个 XML 资源文件,用它在 tech-list 集内指定您的 activity 所支持的技术。如果 tech-list 集是标签所支持的技术(可通过调用 getTechList() 来获取)的子集,则您的 activity 会被视为一个匹配项。

例如,如果扫描到的标签支持 MifareClassic、NdefFormatable 和 NfcA,为了使它们与您的 activity 匹配,您的 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() 返回的技术的子集,则您的 activity 会被视为一个匹配项。这为匹配技术提供了 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 intent,请参阅高级 NFC 文档中的使用支持的标签技术

ACTION_TAG_DISCOVERED

如需过滤 ACTION_TAG_DISCOVERED,请使用以下 intent 过滤器:

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

从 Intent 中获取信息

如果某个 activity 由于 NFC intent 而启动,您可以从该 intent 中获取有关扫描到的 NFC 标签的信息。intent 可以包含以下 extra,具体取决于扫描到的标签:

如需获取这些 extra,请检查您的 activity 是不是使用某个 NFC intent 启动的,以确保已扫描到标签,然后获取 intent 的 extra。以下示例展示了如何检查 ACTION_NDEF_DISCOVERED intent 并从 intent extra 获取 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.
            ...
        }
    }
}

或者,您也可以从 intent 获取 Tag 对象,该对象将包含载荷并允许您枚举标签的技术:

Kotlin

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

Java

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

创建常见类型的 NDEF 记录

本部分介绍了如何创建常见类型的 NDEF 记录,以帮助向 NFC 标签写入数据。从 Android 4.0(API 级别 14)开始引入平台的 createUri() 方法可帮助您自动创建 URI 记录。从 Android 4.1(API 级别 16)开始引入平台的 createExternal()createMime() 可帮助您创建 MIME 和外部类型的 NDEF 记录。请尽可能使用这些辅助方法,以免手动创建 NDEF 记录时出错。

本部分还介绍了如何为记录创建相应的 intent 过滤器。所有这些 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 过滤器如下所示:

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

您可以通过以下方式创建一条 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 过滤器如下所示:

<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 为 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 过滤器如下所示:

<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 为 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 过滤器如下所示:

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

您可以通过以下方式创建一条 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 过滤器如下所示:

<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 Forum 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,也就是示例中的 intent 过滤器声明的 URI。

Android 应用记录

Android 4.0(API 级别 14)中引入,Android 应用记录 (AAR) 可以更好地确定应用会在扫描到 NFC 标签时启动。AAR 在 NDEF 记录内嵌入了应用的软件包名称。您可以将 AAR 添加到 NDEF 消息的任意 NDEF 记录中,因为 Android 会在整个 NDEF 消息中搜索 AAR。如果找到 AAR,它会根据 AAR 中的软件包名称启动相应应用。如果设备上没有该应用,则会启动 Google Play,供用户下载该应用。

如果您想要阻止其他应用过滤同一 intent,并阻止其他应用潜在地处理您已部署的特定标签,则可以使用 AAR。由于软件包名称方面的限制,AAR 仅在应用级别受支持,与 intent 过滤一样在 activity 级别不受支持。如果您要在 activity 级别处理 intent,请使用 intent 过滤器

如果标签包含 AAR,标签调度系统将按以下方式执行分发:

  1. 尝试照常使用 Intent 过滤器启动 Activity。如果与 intent 匹配的 activity 也与 AAR 匹配,则启动该 activity。
  2. 如果过滤该 intent 的 activity 与 AAR 不匹配,或者有多个 activity 可以处理该 intent,或者没有 activity 可以处理该 intent,则启动 AAR 指定的应用。
  3. 如果 AAR 指定的应用都没法启动,则转到 Google Play,请用户根据 AAR 下载相应的应用。

注意:您可以使用前台调度系统替换 AAR 和 intent 调度系统,以便在发现 NFC 标签后优先启动前台 activity。使用此方法时,Activity 必须在前台运行才能替换 AAR 和 intent 调度系统。

如果您仍希望过滤扫描到的不包含 AAR 的标签,则可以照常声明 intent 过滤器。如果您的应用对其他不包含 AAR 的标签感兴趣,这将非常有用。例如,您可能需要保证您的应用能够处理由您部署的专有标签以及由第三方部署的常规标签。请注意,AAR 特定于搭载 Android 4.0 或更高版本的设备,因此,在部署标签时,您很可能需要将 AAR 和 MIME 类型/URI 结合使用,以支持各种设备。此外,在部署 NFC 标签时,请考虑:要如何编写 NFC 标签,才能使其支持大多数设备(Android 设备和其他设备)。为此,您可以定义相对唯一的 MIME 类型或 URI,从而使应用更容易区分它们。

Android 提供了 createApplicationRecord() 这个简单的 API 来创建 AAR。您只需将 AAR 嵌入 NdefMessage 中的任意位置即可。不要使用 NdefMessage 的第一条记录,除非 AAR 是 NdefMessage 中的唯一记录。这是因为 Android 系统会检查 NdefMessage 的第一条记录,以此来确定标签的 MIME 类型或 URI;在创建应用要过滤的 intent 时需要用到标签的 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")}
        );
)