NFC 基础知识

本文档介绍了在 Android 中执行的基本 NFC 任务。它解释了如何以 NDEF 消息的形式发送和接收 NFC 数据,并介绍了支持这些功能的 Android 框架 API。有关更多高级主题(包括有关使用非 NDEF 数据的讨论),请参阅 高级 NFC

读取 NFC 标签中的 NDEF 数据由 标签分发系统 处理,该系统会分析已发现的 NFC 标签,适当地对数据进行分类,并启动对已分类数据感兴趣的应用。想要处理已扫描 NFC 标签的应用可以 声明意图过滤器 并请求处理数据。

标签分发系统

除非在设备的“设置”菜单中禁用了 NFC,否则 Android 设备通常会在屏幕解锁时查找 NFC 标签。当 Android 设备发现 NFC 标签时,理想的行为是让最合适的活动处理意图,而无需询问用户使用哪个应用。由于设备在非常短的范围内扫描 NFC 标签,因此让用户手动选择活动可能会迫使他们将设备移开标签并中断连接。您应该开发您的活动以仅处理您的活动关心的 NFC 标签,以防止出现“活动选择器”。

为了帮助您实现此目标,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标签包含各种各样的技术,也可以通过许多不同的方式写入数据。Android最支持NDEF标准,该标准由NFC论坛定义。

NDEF数据封装在一个包含一个或多个记录的消息(NdefMessage)内(NdefRecord)。每个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)。有效的RTD值在表2中进行了描述。
可变长度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 取决于您在类型字段中设置的记录类型定义 (RTD) 的MIME类型或URI。有关可用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 MIME类型为text/plain
RTD_URI 基于有效负载的URI。

NFC标签如何分发到应用程序

当标签分发系统完成创建封装NFC标签及其标识信息的意图后,它会将该意图发送到对该意图进行过滤的感兴趣的应用程序。如果多个应用程序可以处理该意图,则会显示活动选择器,以便用户可以选择活动。标签分发系统定义了三个意图,按优先级从高到低列出:

  1. ACTION_NDEF_DISCOVERED:当扫描到包含NDEF有效负载且类型已识别的标签时,此意图用于启动Activity。这是最高优先级的意图,只要有可能,标签分发系统就会在任何其他意图之前尝试使用此意图启动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标签时创建的意图启动Activity(ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED)。
  2. 如果没有活动过滤该意图,则尝试使用下一个最低优先级的意图启动Activity(ACTION_TECH_DISCOVEREDACTION_TAG_DISCOVERED),直到应用程序过滤该意图或标签分发系统尝试所有可能的意图为止。
  3. 如果没有应用程序过滤任何意图,则不执行任何操作。
图1. 标签分发系统

尽可能使用NDEF消息和ACTION_NDEF_DISCOVERED意图,因为它在这三个意图中最具体。此意图允许您在比其他两个意图更合适的时间启动应用程序,从而为用户提供更好的体验。

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

在您可以访问设备的NFC硬件并正确处理NFC意图之前,请在AndroidManifest.xml文件中声明以下项目:

  • 用于访问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提供了创建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_TECH_DISCOVERED意图是ACTION_NDEF_DISCOVERED的回退方案,用于没有应用程序过滤ACTION_NDEF_DISCOVERED的情况,或有效负载不是NDEF的情况。过滤ACTION_TAG_DISCOVERED通常过于笼统,许多应用程序会在ACTION_TAG_DISCOVERED之前过滤ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED,因此您的应用程序启动的可能性很低。ACTION_TAG_DISCOVERED仅作为最后手段供应用程序在没有其他应用程序安装来处理ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED意图的情况下的过滤选项。

由于NFC标签部署多种多样,而且很多时候不受您的控制,因此这并不总是可行的,这就是为什么您在必要时可以回退到其他两个意图的原因。当您可以控制标签类型和写入的数据时,建议您使用NDEF来格式化标签。以下部分描述了如何过滤每种类型的意图。

ACTION_NDEF_DISCOVERED

要过滤ACTION_NDEF_DISCOVERED意图,请声明意图过滤器以及您想要过滤的数据类型。以下示例过滤具有text/plain MIME类型的ACTION_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

如果您的活动过滤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集都是独立考虑的,如果任何单个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_DISCOVERED

要过滤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 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_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-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_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-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>

对于更通用的NFC标签部署,请使用TNF_EXTERNAL_TYPE以更好地支持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:externalTypeURN转换为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()。您只需将AAR嵌入到您的NdefMessage中的任何位置即可。除非AAR是NdefMessage中的唯一记录,否则您不希望使用NdefMessage的第一条记录。这是因为Android系统检查NdefMessage的第一条记录以确定标签的MIME类型或URI,该类型或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")}
        );
)