本文介绍了您会在 Android 中执行的基本 NFC 任务,还介绍了如何以 NDEF 消息的形式收发 NFC 数据,以及支持这些功能的 Android 框架 API。如需了解更高级的主题(包括关于如何使用非 NDEF 数据的讨论),请参阅高级 NFC。
将 NDEF 数据与 Android 结合使用时,会有两个主要用例:
- 从 NFC 标签读取 NDEF 数据
- 使用 Android BeamTM 将 NDEF 消息从一台设备传输到另一台设备
从 NFC 标签读取 NDEF 数据的操作由标签调度系统处理,该系统分析发现的 NFC 标签,对数据进行适当分类,然后启动对分类数据感兴趣的应用。想要处理扫描的 NFC 标签的应用可以声明 intent 过滤器并请求处理数据。
借助 Android BeamTM 功能,设备可以将 NDEF 消息推送到另一台设备,只需将两台设备实际靠在一起即可。与蓝牙等其他无线技术相比,这种互动提供了一种更简便的数据发送方式,因为使用 NFC 时无需手动发现或配对设备。当两部设备接近时,连接会自动开始。Android Beam 通过一组 NFC API 提供,因此任何应用都可以在设备之间传输信息。例如,通讯录、浏览器和 YouTube 应用使用 Android Beam 与其他设备共享联系人、网页和视频。
标签调度系统
Android 设备通常会在屏幕解锁时查找 NFC 标签,除非在设备的“设置”菜单中停用了 NFC 功能。当 Android 设备发现 NFC 标签时,期望的行为是让最合适的 activity 处理 intent,而无需询问用户要使用哪个应用。由于设备在非常短的范围内扫描 NFC 标签,因此让用户手动选择 activity 可能会迫使用户将设备从标签中移除并断开连接。您应将 activity 开发为仅处理 activity 关注的 NFC 标签,以防止 activity 选择器显示。
为了帮助您实现这一目标,Android 提供了一个特殊的标签调度系统,该系统可以分析扫描的 NFC 标签、解析这些标签,并尝试定位对扫描的数据感兴趣的应用。其实现方式如下:
- 解析 NFC 标签并确定 MIME 类型或 URI(用于标识标签中的数据载荷)。
- 将 MIME 类型或 URI 与负载一起封装到 Intent 中。NFC 标签如何映射到 MIME 类型和 URI 中介绍了前两个步骤。
- 根据 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。如果多个应用可以处理该 intent,系统会显示 activity 选择器,以便用户选择该 activity。标签调度系统定义了三种 intent,按优先级从高到低的顺序列出:
-
ACTION_NDEF_DISCOVERED
:如果扫描到包含 NDEF 载荷的标签,并且其类型可识别,则此 intent 用于启动 activity。这是优先级最高的 intent,标签调度系统会尽可能优先使用此 intent 启动一个 activity。 ACTION_TECH_DISCOVERED
:如果没有注册用于处理ACTION_NDEF_DISCOVERED
intent 的 activity,标签调度系统会尝试使用此 intent 启动应用。如果扫描的标签包含无法映射到 MIME 类型或 URI 的 NDEF 数据,或者标签不包含 NDEF 数据但属于已知的标签技术,此 intent 也会直接启动(无需先启动ACTION_NDEF_DISCOVERED
)。ACTION_TAG_DISCOVERED
:如果没有处理ACTION_NDEF_DISCOVERED
或ACTION_TECH_DISCOVERED
intent 的 activity,就会启动此 intent。
标签调度系统的基本工作方式如下:
- 尝试使用解析 NFC 标签(
ACTION_NDEF_DISCOVERED
或ACTION_TECH_DISCOVERED
)时由标签调度系统创建的 intent 启动 activity。 - 如果没有 activity 过滤该 intent,请尝试使用优先级次低的 intent(
ACTION_TECH_DISCOVERED
或ACTION_TAG_DISCOVERED
)启动 activity,直到应用过滤该 intent,或直到标签调度系统尝试所有可能的 intent。 - 如果没有应用过滤任何 Intent,则不执行任何操作。
![](https://developer.android.google.cn/static/images/nfc_tag_dispatch.png?authuser=6&hl=th)
请尽可能使用 NDEF 消息和 ACTION_NDEF_DISCOVERED
intent,因为这是三条消息中最具体的一种。与另外两个 intent 相比,此 intent 可让您在更合适的时间启动应用,从而为用户提供更好的体验。
在 Android 清单中请求 NFC 访问权限
您需要先在 AndroidManifest.xml
文件中声明以下几项,然后才能访问设备的 NFC 硬件并正确处理 NFC intent:
- 用于访问 NFC 硬件的 NFC
<uses-permission>
元素:<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 提供了一种使用 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 Intent
如需在扫描到要处理的 NFC 标签时启动您的应用,您的应用可以在 Android 清单中过滤出一个、两个或全部三个 NFC intent。不过,您通常需要过滤 ACTION_NDEF_DISCOVERED
intent,以充分控制应用的启动时间。如果没有应用过滤 ACTION_NDEF_DISCOVERED
或载荷不是 NDEF,ACTION_TECH_DISCOVERED
intent 会作为 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:
<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 资源文件,用于指定您的 activity 在 tech-list
集中支持的技术。如果 tech-list
集是标记所支持的技术(可通过调用 getTechList()
获取)的子集,则您的 activity 会被视为匹配。
例如,如果扫描的标签支持 MifareClassic、NdefFormatable 和 NfcA,您的 tech-list
集必须指定所有这三种技术、两种技术或其中一种技术(无任何其他方式),才能让您的 activity 进行匹配。
以下示例定义了所有技术。您必须移除您的 NFC 标签不支持的 SDK。将此文件(您可以随意为其命名)保存在 <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 会被视为一个匹配项。这为匹配技术提供了 AND
和 OR
语义。以下示例与可以支持 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_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<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 Beam 发送数据。从 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,标签调度系统将按以下方式执行分发:
- 尝试照常使用 Intent 过滤器启动 Activity。如果与 intent 匹配的 activity 也与 AAR 匹配,则启动该 activity。
- 如果过滤该 intent 的 activity 与 AAR 不匹配,或者有多个 activity 可以处理该 intent,或者没有 activity 可以处理该 intent,请启动 AAR 指定的应用。
- 如果所有应用都不能通过 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。您只需在 NdefMessage
中的任意位置嵌入 AAR 即可。最好不要使用 NdefMessage
的第一条记录,除非 AAR 是 NdefMessage
中的唯一记录。这是因为,Android 系统会检查 NdefMessage
的第一条记录,以确定标记的 MIME 类型或 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")} ); )
向其他设备传输 NDEF 消息
Android Beam 可在两台 Android 设备之间进行简单的点对点数据交换。想要将数据传输到其他设备的应用必须在前台运行,并且接收数据的设备不得处于锁定状态。当传输设备与接收设备充分接触时,传输设备会显示“触摸即可传输”界面。然后,用户可以选择是否将消息传输到接收设备。
注意:API 级别 10 提供前台 NDEF 推送,其功能与 Android Beam 类似。此后,这些 API 已废弃,但可用于支持旧版设备。有关详情,请参阅《enableForegroundNdefPush()
》。
调用以下两种方法之一可为您的应用启用 Android Beam:
setNdefPushMessage()
:接受NdefMessage
以设置为要传输的消息。在两台设备距离足够近时,自动传输消息。setNdefPushMessageCallback()
:接受包含createNdefMessage()
的回调,当设备处于可接收数据的范围内时,系统会调用该回调。通过此回调,您可以仅在必要时创建 NDEF 消息。
activity 一次只能推送一条 NDEF 消息,因此 setNdefPushMessageCallback()
优先于 setNdefPushMessage()
(如果同时设置了两者)。要使用 Android Beam,必须满足以下一般准则:
- 传输数据的 Activity 必须在前台运行。两部设备的屏幕都必须处于解锁状态。
- 您必须将要传输的数据封装在
NdefMessage
对象中。 - 接收传输数据的 NFC 设备必须支持
com.android.npp
NDEF 推送协议或 NFC Forum 的 SNEP(简单 NDEF 交换协议)。API 级别 9 (Android 2.3) 到 API 级别 13 (Android 3.2) 的设备需要使用com.android.npp
协议。API 级别 14 (Android 4.0) 及更高版本需要com.android.npp
和 SNEP。
注意:如果您的 activity 启用了 Android Beam 且在前台运行,则标准 intent 调度系统会停用。不过,如果您的 activity 还支持 前台调度,则仍然可以扫描与前台调度中设置的 intent 过滤器相匹配的标签。
要启用 Android Beam,请执行以下操作:
- 创建一个
NdefMessage
,其中包含要推送到其他设备的NdefRecord
。 - 使用
NdefMessage
调用setNdefPushMessage()
,或调用setNdefPushMessageCallback
,并在 activity 的onCreate()
方法中传入NfcAdapter.CreateNdefMessageCallback
对象。这些方法需要至少一个您想通过 Android Beam 启用的 activity,以及要激活的其他 activity 的可选列表。一般来说,如果当两台设备处于通信的范围内时,如果您的 activity 只需要始终推送同一 NDEF 消息,您通常可以使用
setNdefPushMessage()
。如果您的应用关注应用的当前上下文并希望根据用户在应用中执行的操作来推送 NDEF 消息,可以使用setNdefPushMessageCallback
。
以下示例展示了简单 activity 如何在 activity 的 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 过滤器:
<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>
借助此 intent 过滤器,com.example.android.beam
应用现在可以在扫描 NFC 标签或收到具有 com.example.android.beam
类型的 AAR 的 Android Beam 时,或者在 NDEF 格式的消息包含 application/vnd.com.example.android.beam
类型的 MIME 记录时启动。
尽管 AAR 可保证启动或下载应用,但我们还是建议您使用 intent 过滤器,因为它们允许您在应用中启动所选的 activity,而不是始终启动 AAR 指定的软件包内的主 activity。AAR 不具备 Activity 级别的粒度。此外,由于某些 Android 设备不支持 AAR,因此您还应在 NDEF 消息的第一条 NDEF 记录中嵌入标识信息,并对此进行过滤,以备不时之需。如需详细了解如何创建记录,请参阅创建 NDEF 记录的常见类型。