- By test - In 巨兽图鉴
NFC 基础知识
本文档介绍了您在 Android 中执行的基本 NFC 任务。它说明了如何以 NDEF 消息的形式发送和接收 NFC 数据,并介绍了支持这些功能的 Android 框架 API。有关更高级的主题(包括处理非 NDEF 数据),请参阅高级 NFC。
从 NFC 标签读取 NDEF 数据通过标签分发系统处理,该系统会分析发现的 NFC 标签,对数据进行适当分类,并启动对该分类数据感兴趣的应用程序。想要处理已扫描 NFC 标签的应用程序可以声明一个 intent 过滤器并请求处理该数据。
标签分发系统
除非在设备的“设置”菜单中禁用了 NFC,否则搭载 Android 的设备通常会在屏幕解锁时查找 NFC 标签。当搭载 Android 的设备发现 NFC 标签时,理想的行为是让最合适的 activity 处理该 intent,而无需询问用户使用哪个应用程序。由于设备在极短的距离内扫描 NFC 标签,如果强制用户手动选择 activity,很可能会导致他们将设备移离标签并中断连接。您应该开发自己的 activity,使其仅处理您关注的 NFC 标签,以防止“活动选择器”出现。
为了帮助您实现这一目标,Android 提供了一个特殊的标签分发系统,它会分析扫描到的 NFC 标签、解析它们,并尝试定位对扫描到的数据感兴趣的应用程序。它通过以下方式实现:
解析 NFC 标签并找出标识标签中数据负载的 MIME 类型或 URI。
将 MIME 类型或 URI 以及负载封装到一个 intent 中。前两个步骤在如何将 NFC 标签映射到 MIME 类型和 URI 中进行了描述。
根据该 intent 启动一个 activity。这在如何将 NFC 标签分发给应用程序 中进行了描述。
如何将 NFC 标签映射到 MIME 类型和 URI
在开始编写 NFC 应用程序之前,了解不同类型的 NFC 标签、标签分发系统如何解析 NFC 标签,以及标签分发系统在检测到 NDEF 消息时所做的特殊工作非常重要。NFC 标签采用多种技术,写入数据的方式也多种多样。Android 对 NFC 论坛 定义的 NDEF 标准提供了最广泛的支持。
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)。有效的 RTD 值在表 2 中进行了描述。
变长 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 被编码为 NDEF 类型字段中的缩写形式:
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
text/plain 的 MIME 类型。
RTD_URI
基于负载的 URI。
如何将 NFC 标签分发给应用程序
当标签分发系统完成创建封装 NFC 标签及其识别信息的 intent 后,它会将该 intent 发送给筛选该 intent 的感兴趣的应用程序。如果多个应用程序可以处理该 intent,则会显示“活动选择器”,以便用户选择 Activity。标签分发系统定义了三个 intent,按优先级从高到低排列:
ACTION_NDEF_DISCOVERED:此 intent 用于在扫描到包含 NDEF 负载且属于识别类型的标签时启动 Activity。这是最高优先级的 intent,标签分发系统尽可能在任何其他 intent 之前尝试使用此 intent 启动 Activity。注意: 从 Android 16 开始,扫描存储 URL 链接(即 URI 方案为 "https://" 或 "http://")的 NFC 标签将触发 ACTION_VIEW intent,而不是 ACTION_NDEF_DISCOVERED intent。
ACTION_TECH_DISCOVERED:如果没有 Activity 注册处理 ACTION_NDEF_DISCOVERED intent,标签分发系统会尝试使用此 intent 启动应用程序。如果扫描到的标签包含无法映射到 MIME 类型或 URI 的 NDEF 数据,或者标签不包含 NDEF 数据但属于已知标签技术,此 intent 也会直接启动(无需先启动 ACTION_NDEF_DISCOVERED)。
ACTION_TAG_DISCOVERED:如果没有 Activity 处理 ACTION_NDEF_DISCOVERED 或 ACTION_TECH_DISCOVERED intent,则启动此 intent。
标签分发系统的基本工作方式如下:
尝试使用标签分发系统在解析 NFC 标签时创建的 intent(ACTION_NDEF_DISCOVERED 或 ACTION_TECH_DISCOVERED)启动 Activity。
如果没有 Activity 筛选该 intent,则尝试使用下一个最低优先级的 intent(ACTION_TECH_DISCOVERED 或 ACTION_TAG_DISCOVERED)启动 Activity,直到有应用程序筛选该 intent 或标签分发系统尝试了所有可能的 intent。
如果没有应用程序筛选任何 intent,则不执行任何操作。
图 1. 标签分发系统
尽可能使用 NDEF 消息和 ACTION_NDEF_DISCOVERED intent,因为它是三个中粒度最细的一个。此 intent 允许您在比其他两个 intent 更合适的时机启动应用程序,从而为用户提供更好的体验。
在 Android 清单中请求 NFC 访问权限
在访问设备的 NFC 硬件并正确处理 NFC intent 之前,请在您的 AndroidManifest.xml 文件中声明以下项:
用于访问 NFC 硬件的 NFC
从 Android 17(API 级别 37)开始,如果应用的目标 SDK > Build.VERSION_CODES.BAKLAVA,则为了让 Activity 接收分发的 NFC intent,它必须受到 android.permission.DISPATCH_NFC_MESSAGE 权限保护。这确保只有 NFC 系统服务才能将 intent 分发给您的 Activity。此外,系统不会将 NFC intent 分发给处于停止状态的应用程序(例如,如果用户从未启动过该应用程序或该应用程序已被强制停止)。 android:name=".MyActivity" android:exported="true" android:permission="android.permission.DISPATCH_NFC_MESSAGE"> ...
您的应用程序可以支持的最低 SDK 版本。API 级别 9 仅通过 ACTION_TAG_DISCOVERED 支持有限的标签分发,并且仅通过 EXTRA_NDEF_MESSAGES extra 提供对 NDEF 消息的访问。其他任何标签属性或 I/O 操作都不可访问。API 级别 10 包含全面的读/写支持以及前台 NDEF 推送,API 级别 14 提供了创建 NDEF 记录的额外便捷方法。
uses-feature 元素,以便您的应用程序仅在具有 NFC 硬件的设备上显示在 Google Play 中
如果您的应用程序使用 NFC 功能,但该功能对您的应用程序而言并非至关重要,您可以省略 uses-feature 元素,并在运行时通过检查 getDefaultAdapter() 是否为 null 来检查 NFC 的可用性。
筛选 NFC intent
为了在扫描到您想要处理的 NFC 标签时启动您的应用程序,您的应用程序可以在 Android 清单中筛选一个、两个或全部三个 NFC intent。但是,您通常希望筛选 ACTION_NDEF_DISCOVERED intent,以便更好地控制应用程序启动的时机。ACTION_TECH_DISCOVERED intent 是在没有应用程序筛选 ACTION_NDEF_DISCOVERED 或负载不是 NDEF 时,针对 ACTION_NDEF_DISCOVERED 的回退。筛选 ACTION_TAG_DISCOVERED 的类别通常过于笼统。许多应用程序会在 ACTION_TAG_DISCOVERED 之前筛选 ACTION_NDEF_DISCOVERED 或 ACTION_TECH_DISCOVERED,因此您的应用程序启动的概率很低。ACTION_TAG_DISCOVERED 仅作为最后手段提供给应用程序,用于在没有安装其他应用程序来处理 ACTION_NDEF_DISCOVERED 或 ACTION_TECH_DISCOVERED intent 的情况下进行筛选。
由于 NFC 标签部署各不相同,且往往不受您的控制,因此并非总是可行,这就是为什么您可以在必要时回退到其他两个 intent 的原因。当您能够控制标签类型和写入的数据时,建议您使用 NDEF 格式化标签。接下来的章节描述了如何筛选每种类型的 intent。
ACTION_NDEF_DISCOVERED
要筛选 ACTION_NDEF_DISCOVERED intent,请声明 intent 过滤器以及您想要筛选的数据类型。以下示例筛选 MIME 类型为 text/plain 的 ACTION_NDEF_DISCOVERED intent。
android:name=".MyActivity" android:exported="true" android:permission="android.permission.DISPATCH_NFC_MESSAGE">
以下示例筛选形式为 https://android-docs.cn/index.html 的 URI。
android:name=".MyActivity" android:exported="true" android:permission="android.permission.DISPATCH_NFC_MESSAGE">
android:host="developer.android.com" android:pathPrefix="/index.html" />
ACTION_TECH_DISCOVERED
如果您的 activity 筛选 ACTION_TECH_DISCOVERED intent,您必须创建一个 XML 资源文件,在 tech-list 集合中指定您的 activity 支持的技术。如果 tech-list 集合是标签所支持技术的子集,则您的 activity 被视为匹配,您可以调用 getTechList() 获取该集合。
例如,如果扫描到的标签支持 MifareClassic、NdefFormatable 和 NfcA,则您的 tech-list 集合必须指定全部这三种技术,或者其中两种,或者其中一种(且不能包含其他内容),以便您的 activity 匹配成功。
以下示例定义了所有技术。您必须删除 NFC 标签不支持的技术。将此文件(您可以随意命名)保存到
您还可以指定多个 tech-list 集合。每个 tech-list 集合都被独立考量,如果任何单个 tech-list 集合是 getTechList() 返回技术的子集,则您的 activity 被视为匹配。这为匹配技术提供了 AND 和 OR 语义。以下示例匹配支持 NfcA 和 Ndef 技术,或者支持 NfcB 和 Ndef 技术的标签。
在您的 AndroidManifest.xml 文件中,在
android:name=".MyActivity" android:exported="true" android:permission="android.permission.DISPATCH_NFC_MESSAGE"> android:resource="@xml/nfc_tech_filter" />
有关处理标签技术和 ACTION_TECH_DISCOVERED intent 的更多信息,请参阅高级 NFC 文档中的处理支持的标签技术。
ACTION_TAG_DISCOVERED
注意: ACTION_TAG_DISCOVERED 从 Android 17(API 级别 37)开始已弃用。请改用 ACTION_NDEF_DISCOVERED 或 ACTION_TECH_DISCOVERED。
要筛选 ACTION_TAG_DISCOVERED,请使用以下 intent 过滤器:
android:name=".MyActivity" android:exported="true" android:permission="android.permission.DISPATCH_NFC_MESSAGE">
ACTION_VIEW
从 Android 16 开始,扫描存储 URL 链接的 NFC 标签将触发 ACTION_VIEW intent。要筛选 ACTION_VIEW,请参考此文档。使用 Android App Links 为 URL 打开您的应用。
从 intent 中获取信息
如果 activity 因为 NFC intent 而启动,您可以从该 intent 中获取有关扫描到的 NFC 标签的信息。根据扫描的标签,intent 可能包含以下 extra:
EXTRA_TAG(必需):表示扫描标签的 Tag 对象。
EXTRA_NDEF_MESSAGES(可选):从标签中解析出的 NDEF 消息数组。此 extra 在 ACTION_NDEF_DISCOVERED intent 上是强制性的。
EXTRA_ID(可选):标签的底层 ID。
要获取这些 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
// 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://android-docs.cn/index.html".toByteArray(Charset.forName("US-ASCII")),
emptyByteArray,
emptyByteArray
)
}
Java
NdefRecord uriRecord = new NdefRecord(
NdefRecord.TNF_ABSOLUTE_URI ,
"https://android-docs.cn/index.html".getBytes(Charset.forName("US-ASCII")),
new byte[0], new byte[0]);
上述 NDEF 记录的 intent 过滤器如下所示:
android:name=".MyActivity" android:exported="true" android:permission="android.permission.DISPATCH_NFC_MESSAGE">
android:host="developer.android.com" android:pathPrefix="/index.html" />
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 过滤器如下所示:
android:name=".MyActivity" android:exported="true" android:permission="android.permission.DISPATCH_NFC_MESSAGE">
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 过滤器如下所示:
android:name=".MyActivity" android:exported="true" android:permission="android.permission.DISPATCH_NFC_MESSAGE">
TNF_WELL_KNOWN 与 RTD_URI
您可以按以下方式创建 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 过滤器如下所示:
android:name=".MyActivity" android:exported="true" android:permission="android.permission.DISPATCH_NFC_MESSAGE">
android:host="example.com" android:pathPrefix="" />
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 过滤器如下所示:
android:name=".MyActivity" android:exported="true" android:permission="android.permission.DISPATCH_NFC_MESSAGE">
android:host="ext" android:pathPrefix="/com.example:externalType"/>
对于更通用的 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:externalType URN 转换为 vnd.android.nfc://ext/example.com:externalType URI,这正是示例中 intent 过滤器所声明的内容。
Android 应用程序记录
Android 应用程序记录 (AAR) 于 Android 4.0(API 级别 14)引入,它提供了更强的确定性,确保您的应用程序在扫描到 NFC 标签时被启动。AAR 在 NDEF 记录中嵌入了应用程序的包名。您可以将 AAR 添加到 NDEF 消息的任何 NDEF 记录中,因为 Android 会搜索整条 NDEF 消息以查找 AAR。如果找到 AAR,它会根据 AAR 中的包名启动该应用程序。如果设备上没有该应用程序,则会启动 Google Play 来下载该应用程序。
如果您想防止其他应用程序筛选相同的 intent 并处理您部署的特定标签,AAR 非常有用。由于包名约束,AAR 仅在应用程序级别受支持,而不像 intent 筛选那样在 Activity 级别受支持。如果您想在 Activity 级别处理 intent,请使用 intent 过滤器。
如果标签包含 AAR,标签分发系统的分发方式如下:
正常尝试使用 intent 过滤器启动 Activity。如果与 intent 匹配的 Activity 也与 AAR 匹配,则启动该 Activity。
如果筛选 intent 的 Activity 与 AAR 不匹配,或者多个 Activity 可以处理该 intent,或者没有 Activity 处理该 intent,则启动 AAR 指定的应用程序。
如果没有应用程序可以随 AAR 一起启动,请前往 Google Play 下载该 AAR 指定的应用程序。
注意: 您可以使用前台分发系统覆盖 AAR 和 intent 分发系统,这允许前台 activity 在发现 NFC 标签时拥有优先权。使用此方法时,activity 必须位于前台才能覆盖 AAR 和 intent 分发系统。
如果您仍然想筛选不包含 AAR 的扫描标签,您可以像往常一样声明 intent 过滤器。如果您的应用程序关注其他不包含 AAR 的标签,这非常有用。例如,您可能希望确保您的应用程序既能处理您部署的专有标签,也能处理第三方部署的通用标签。请记住,AAR 仅适用于 Android 4.0 或更高版本的设备,因此在部署标签时,您很可能希望结合使用 AAR 和 MIME 类型/URI,以支持最广泛的设备。此外,在部署 NFC 标签时,请考虑如何编写 NFC 标签以支持最多的设备(搭载 Android 的设备和其他设备)。您可以通过定义一个相对唯一的 MIME 类型或 URI 来实现这一点,从而使应用程序更容易区分。
Android 提供了一个简单的 API 来创建 AAR:createApplicationRecord()。您只需要将 AAR 嵌入到您的 NdefMessage 中的任何位置即可。除非 AAR 是 NdefMessage 中的唯一记录,否则不要将其放在第一条记录中。这是因为 Android 系统会检查 NdefMessage 的第一条记录以确定标签的 MIME 类型或 URI,而该类型或 URI 用于为应用程序筛选创建 intent。以下代码展示了如何创建 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")}
);
)
NFC 标签扫描应用允许列表
从 Android 16 开始,当应用程序首次收到用于扫描 NFC 标签的 NFC intent 时,系统会通知用户。在通知中,用户可以选择不再允许该应用程序扫描 NFC 标签。
应用程序可以使用 NfcAdapter.isTagIntentAllowed() 检查用户是否已允许该应用扫描 NFC 标签。
应用程序可以通过发送 ACTION_CHANGE_TAG_INTENT_PREFERENCE intent 来提示用户再次允许 NFC 标签扫描。
注意: NFC 标签扫描应用允许列表可在“设置”>“应用”>“特殊应用权限”>“通过 NFC 启动”下访问。
此机制的引入旨在解决用户的担忧,即某些已为 NFC 标签 intent 注册了 intent 过滤器的应用程序,在用户将手机靠近 NFC 标签(信用卡、另一部手机/手表等)时会被反复带到前台。

