NFC是Near Field Communication(近场通信)的简称。是一种新兴的技术,使用了NFC技术的设备(例如移动电话)可以在彼此靠近的情况下进行数据交换,是由非接触式射频识别(RFID)及互连互通技术整合演变而来的,通过在单一芯片上集成感应式读卡器、感应式卡片和点对点通信的功能,利用移动终端实现移动支付、电子票务、门禁、移动身份识别、防伪等应用。
支持 NFC 的 Android 设备同时支持以下三种主要操作模式:
- 读取器/写入器模式:支持 NFC 设备读取和/或写入被动 NFC 标签和贴纸。
- 点对点模式:支持 NFC 设备与其他 NFC 对等设备交换数据;Android Beam 使用的就是此操作模式。
- 卡模拟模式:支持 NFC 设备本身充当 NFC 卡。然后,可以通过外部 NFC 读取器(例如 NFC 销售终端)访问模拟 NFC 卡。
标签调度系统
Android 提供了一个特殊的标签调度系统,用于分析扫描到的 NFC 标签、解析它们并尝试找到对扫描到的数据感兴趣的应用
- 解析 NFC 标签并确定 MIME 类型或 URI(后者用于标识标签中的数据负载)。
- 将 MIME 类型或 URI 与负载一起封装到 Intent 中。
- 根据 Intent 启动 Activity。
标签调度系统使用 TNF 和类型字段来尝试将 MIME 类型或 URI 映射到 NDEF 消息。如果成功映射,它会将相关信息与实际负载一起封装到 ACTION_NDEF_DISCOVERED
Intent 内。不过,在某些情况下,标签调度系统无法根据第一条 NDEF 记录来确定数据的类型。如果 NDEF 数据无法映射到 MIME 类型或 URI,或者 NFC 标签不包含 NDEF 数据,就会出现上述情况。在此类情况下,标签调度系统会转而将含有标签技术相关信息的 Tag
对象及负载封装到 ACTION_TECH_DISCOVERED
Intent 中。
NDEF
NFC Data Exchange Format : NFC数据交换格式,NFC组织约定的NFC tag中的数据格式。NDEF 数据封装在包含一条或多条记录 (NdefRecord
) 的消息 (NdefMessage
) 内。每条 NDEF 记录的格式都必须正确,符合您要创建的记录所属的类型对应的规范。Android 还支持其他类型的不包含 NDEF 数据的标签,您可以使用 android.nfc.tech
软件包中的类处理这些标签.
当 Android 设备扫描包含 NDEF 格式数据的 NFC 标签时,它会解析该消息并尝试确定数据的 MIME 类型或起标识作用的 URI。为此,系统需要读取 NdefMessage
中的第一条 NdefRecord
,以确定如何解读整个 NDEF 消息(一个 NDEF 消息可能具有多条 NDEF 记录)。
过滤Intent
如果有多个应用可处理该 Intent,系统会显示 Activity 选择器,供用户选择要使用的 Activity。标签调度系统定义了三种 Intent,按优先级从高到低列出如下:
-
ACTION_NDEF_DISCOVERED
:如果扫描到包含 NDEF 负载的标签,并且可识别其类型,则使用此 Intent 启动 Activity。这是优先级最高的 Intent,标签调度系统会尽可能尝试使用此 Intent 启动 Activity,在行不通时才会尝试使用其他 Intent。 -
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 启动 Activity
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rbQ7SOsk-1596446163521)(Android中NFC相关技术-一/0.png)]
将NFC标签分发到应用
1.声明权限
在 AndroidManifest.xml
文件中声明以下内容,然后才能访问设备的 NFC 硬件并正确处理 NFC Intent:
//用于访问 NFC 硬件的 NFC <uses-permission> 元素:
<uses-permission android:name="android.permission.NFC" />
uses-feature
元素,以便您的应用仅在那些具备 NFC 硬件的设备的 Google Play 中显示
//可选的
<uses-feature android:name="android.hardware.nfc" android:required="true" />
2.过滤NFC 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="http"
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 会被视为一个匹配项。还可以指定多个 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>
将此文件(你可以随便命名)保存到 <project-root>/res/xml
文件夹中,在 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_TAG_DISCOVERED
要过滤 ACTION_TAG_DISCOVERED
,请使用以下 Intent 过滤器:
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED"/>
</intent-filter>
3.从 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 消息。
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.
...
}
}
}
4.创建NDEF记录
这些 NDEF 记录示例都应该位于您写入标签或传输到另一设备的 NDEF 消息的第一条 NDEF 记录中。
RTD 为 RTD_TEXT 的 TNF_WELL_KNOWN
您可以通过以下方式创建一条 TNF_WELL_KNOWN
NDEF 记录:
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)
}
上一条 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>
使用 createMime()
方法创建一条 TNF_MIME_MEDIA
NDEF 记录:
val mimeRecord = NdefRecord.createMime(
"application/vnd.com.example.android.beam",
"Beam me up, Android".toByteArray(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>
使用前台调度系统
前台调度系统,Activity 可以拦截 Intent 并声明自己可优先于其他 Activity 处理同一 Intent。
- 在 Activity 的
onCreate()
方法中添加以下代码:
- 创建一个
PendingIntent
对象,这样 Android 系统会使用扫描到的标签的详情对其进行填充。
val intent = Intent(this, javaClass).apply {
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
var pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
- 声明 Intent 过滤器,以处理您要拦截的 Intent。前台调度系统会对照设备扫描标签时所获得的 Intent 来检查所指定的 Intent 过滤器。如果匹配,那么应用会处理该 Intent。如果不匹配,那么前台调度系统会回退到 Intent 调度系统。指定 Intent 过滤器和技术过滤器的null数组,以指明要过滤所有回退到
TAG_DISCOVERED
Intent 的标签。以下代码段会处理NDEF_DISCOVERED的所有 MIME 类型。您应只处理需要的内容。
val ndef = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED).apply {
try {
addDataType("*/*") /* Handles all MIME based dispatches.
You should specify only the ones that you need. */
} catch (e: IntentFilter.MalformedMimeTypeException) {
throw RuntimeException("fail", e)
}
}
intentFiltersArray = arrayOf(ndef)
- 设置应用要处理的一组标签技术。调用
Object.class.getName()
方法以获取要支持的技术的类。
techListsArray = arrayOf(arrayOf<String>(NfcF::class.java.name))
- 替换以下 Activity 生命周期回调,并添加相应逻辑,以分别在 Activity 失去 (
onPause()
) 焦点和重新获得 (onResume()
) 焦点时启用和停用前台调度。enableForegroundDispatch()
必须从主线程调用,并且只能在 Activity 在前台运行时调用(在onResume()
中调用可确保这一点)。您还需要实现onNewIntent
回调以处理扫描到的 NFC 标签中的数据。
public override fun onPause() {
super.onPause()
adapter.disableForegroundDispatch(this)
}
public override fun onResume() {
super.onResume()
adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray)
}
public override fun onNewIntent(intent: Intent) {
val tagFromIntent: Tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
//do something with tagFromIntent
}