NFC 基础知识

本文介绍了您会在 Android 中执行的基本 NFC 任务,其中说明了如何使用 以 NDEF 消息的形式接收 NFC 数据,并介绍了支持 这些功能。如需了解更高级的主题,包括有关使用非 NDEF 数据的讨论, 请参阅高级 NFC

将 NDEF 数据与 Android 结合使用时,会有两个主要用例:

从 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 关心的 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 对 NFC Forum 定义的 NDEF 标准的支持最广。

NDEF 数据被封装在消息 (NdefMessage) 中,消息中包含一个 一条或多条记录(NdefRecord 条)。每条 NDEF 记录都必须符合 您要创建的记录类型的规范。Android 设备 还支持其他类型的不包含 NDEF 数据的标签,您可以使用 android.nfc.tech 软件包中的类。了解详情 请参阅高级 NFC 主题。要使用这些其他类型的代码 因此我们建议您 以简化开发并尽可能为 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 被编码到 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 MIME 类型或 URI,具体取决于您在 type 字段。请参阅表 2,详细了解 可用的 RTD 及其映射。

表 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 标签分发到应用

当标签调度系统创建完 intent 来封装 NFC 标签及其 它会将 intent 发送给感兴趣的应用, 过滤器。如果多个应用可以处理 Intent,则 Activity 选择器 以便用户可以选择相应 activity。标签调度系统定义了三个 intent, 它们按优先级从高到低排序:

  1. ACTION_NDEF_DISCOVERED:此 intent 用于启动 扫描到包含 NDEF 有效负载的代码且属于可识别类型时的活动。这是 标签调度系统尝试使用此 另一个 intent 的前面。
  2. ACTION_TECH_DISCOVERED:如果没有 activity 注册到 处理 ACTION_NDEF_DISCOVERED intent 来启动应用,标签调度系统会尝试使用此 intent 启动应用。本次 intent 也会直接启动(无需先启动 ACTION_NDEF_DISCOVERED),前提是扫描到的标记 包含无法映射到 MIME 类型或 URI 的 NDEF 数据,或者如果标记不包含 NDEF 但采用的是已知的代码技术
  3. ACTION_TAG_DISCOVERED:此 intent 已启动 如果没有处理 ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED 的 activity intent。

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

  1. 尝试使用由标签调度系统创建的 intent 启动 activity 读取 NFC 标签(例如, ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVERED)。
  2. 如果没有适用于该 intent 的 activity 过滤器,请尝试使用下一个 优先级最低的 intent(ACTION_TECH_DISCOVEREDACTION_TAG_DISCOVERED),直到应用过滤 intent,直到标签调度系统尝试了所有可能的 intent。
  3. 如果没有应用过滤任何 Intent,则不执行任何操作。
。 <ph type="x-smartling-placeholder">
</ph>
图 1.标签调度系统

请尽可能使用 NDEF 消息和 ACTION_NDEF_DISCOVERED intent,因为这是 三者。通过该 intent,您可以在比 其他两个 intent,从而为用户提供更好的体验。

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

您必须先声明这些 API,然后才能访问设备的 NFC 硬件并正确处理 NFC intent AndroidManifest.xml 文件:

  • 用于访问 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 级别 Android 14 提供了一种将 NDEF 消息推送到其他设备的更简便方式,借助 Android Beam 和其他功能 创建 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 Intent

为了在扫描到您要处理的 NFC 标签时启动您的应用,您的应用 可以在 Android 清单中过滤一个、两个或全部三个 NFC intent。不过,您 通常希望过滤出 ACTION_NDEF_DISCOVERED intent,以获取 能够最大限度地控制应用的启动时间当没有任何应用过滤ACTION_TECH_DISCOVEREDACTION_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 intent。

由于 NFC 标签部署各有不同,而且很多时候都是不受您的控制,因此有时无法实现这一点 因此您可以在必要时回退到另外两个 intent。如果您 标记和写入的数据类型,因此建议您使用 NDEF 格式 代码。以下几部分介绍了如何过滤各类 Intent。

ACTION_NDEF_DISCOVERED

如需过滤 ACTION_NDEF_DISCOVERED intent,请声明 intent 过滤器以及要过滤的数据类型。通过 针对“ACTION_NDEF_DISCOVERED”的以下示例过滤条件 MIME 类型为 text/plain 的 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

<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() 获取。

例如,如果扫描的标签支持 MifareClassic、NdefFormatable 和 NfcA,您的 “tech-list”集必须指定全部三种、两种或其中一种技术(不指定 else),以使您的活动能够匹配。

以下示例定义了所有技术。您必须移除 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 intent,请参阅使用支持的标签 技术一文。

ACTION_TAG_DISCOVERED

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

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

从 Intent 中获取信息

如果某个 activity 因 NFC intent 而启动,您可以获取所扫描 NFC 的相关信息 标记。根据扫描到的标记,intent 可以包含以下 extra:

如需获取这些 extra,请检查您的 activity 是否使用 NFC intent 以确保对标签进行了扫描,然后从 intent。以下示例会检查 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 记录,以帮助您写入 使用 Android Beam 发送 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_URITNF_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 规范, 声明必须从urn:nfc:ext: NDEF 记录。因此您只需提供域名(在示例中为 example.com) 和类型(本例中为 externalType),以英文冒号分隔。 分派 TNF_EXTERNAL_TYPE 时,Android 会将 urn:nfc:ext:example.com:externalType URN 转换为 vnd.android.nfc://ext/example.com:externalType URI,该 URI 是 intent 过滤器进行声明。

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 级别处理 intent,请使用 intent 过滤器

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

  1. 尝试照常使用 Intent 过滤器启动 Activity。如果与 intent 也与 AAR 匹配,则启动 activity。
  2. 如果过滤该 intent 的 activity 与 AAR,如果多个 activity 可以处理 intent,或者没有 activity 处理 intent,则启动 通过 AAR 指定的应用。
  3. 如果没有任何应用可通过 AAR 启动,请前往 Google Play 下载 基于 AAR 的应用。

注意:您可以使用 前台 调度系统,当某个 NFC 标签收到 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 中的任意位置。你不想要 使用NdefMessage的第一条记录,除非 AAR 是唯一的 记录在 NdefMessage 中。这是因为 系统会检查 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:

一个 activity 一次只能推送一条 NDEF 消息,因此 setNdefPushMessageCallback() 优先 高于 setNdefPushMessage()(如果两者都设置了)。要使用 Android Beam,则必须遵循以下一般准则:

  • 传输数据的 Activity 必须在前台运行。两部设备都必须 屏幕就会解锁
  • 您必须将要传输的数据封装在 NdefMessage 中, 对象。
  • 接收传输数据的 NFC 设备必须支持 com.android.npp NDEF 推送协议或 NFC Forum 的 SNEP(简单 NDEF 交换) 协议)。搭载 API 级别 9 (Android) 的设备必须使用 com.android.npp 协议 2.3 至 API 级别 13 (Android 3.2)。必须同时启用 com.android.npp 和 SNEP API 级别 14 (Android 4.0) 及更高版本。

注意:如果您的 activity 启用了 Android Beam,并且 则标准 intent 调度系统会被停用。但是,如果您的活动 启用 前台调度,那么它仍然可以扫描与 前台调度。

要启用 Android Beam,请执行以下操作:

  1. 创建一个包含 NdefRecordNdefMessage 要推送到另一台设备上的应用。
  2. 使用 NdefMessage 调用 setNdefPushMessage(),或调用 setNdefPushMessageCallback,并在以下对象的 onCreate() 方法中传入 NfcAdapter.CreateNdefMessageCallback 对象: 活动这些方法至少需要一个您希望在 Android 设备上启用的 activity Beam 以及要激活的其他 activity 的可选列表。

    一般情况下,如果您的 activity 只需要setNdefPushMessage() 当两台设备处于通信范围内时,始终推送相同的 NDEF 消息。您所使用的 setNdefPushMessageCallback,当您 应用关注应用的当前上下文,并希望推送 NDEF 消息 具体取决于用户在您的应用中执行的操作

以下示例展示了简单 activity 如何在NfcAdapter.CreateNdefMessageCallbackonCreate() activity(请参阅 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 已开始下载该应用。因此,以下意图 对于搭载 Android 4.0 或更高版本的设备(如果使用了 AAR),在技术上不需要该过滤器:

<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 标签或收到包含 AAR 的 Android Beam 时, 类型 com.example.android.beam,或者 NDEF 格式的邮件包含 MIME 记录时 类型为 application/vnd.com.example.android.beam

即使 AAR 保证应用已启动或下载,intent 过滤器也会 因为这让您可以在 而不是始终在 AAR 指定的软件包内启动主 activity。 AAR 不具备 Activity 级别的粒度。另外,由于某些 Android 设备不支持 支持 AAR,您还应将身份信息嵌入到 NDEF 的第一条 NDEF 记录中 并对其进行过滤,以防万一请参阅创建通用文件 NDEF 记录的类型,详细了解如何创建记录。