本文档描述了高级 NFC 主题,例如使用各种标签技术、写入 NFC 标签以及前台调度(允许前台应用即使在其他应用过滤相同意图时也能处理意图)。
使用支持的标签技术
在使用 NFC 标签和搭载 Android 系统的设备时,用于在标签上读取和写入数据的主要格式是 NDEF。当设备扫描带有 NDEF 数据的标签时,Android 提供支持来解析消息并在可能的情况下将其以 NdefMessage
的形式提供。但是,在某些情况下,您扫描的标签不包含 NDEF 数据,或者 NDEF 数据无法映射到 MIME 类型或 URI。在这些情况下,您需要直接与标签打开通信,并使用您自己的协议(以原始字节表示)进行读取和写入。Android 使用 android.nfc.tech
包为这些用例提供了通用支持,该包在表 1 中进行了描述。您可以使用 getTechList()
方法确定标签支持的技术,并使用 android.nfc.tech
提供的类之一创建相应的 TagTechnology
对象。
类 | 描述 |
---|---|
TagTechnology |
所有标签技术类都必须实现的接口。 |
NfcA |
提供对 NFC-A (ISO 14443-3A) 属性和 I/O 操作的访问。 |
NfcB |
提供对 NFC-B (ISO 14443-3B) 属性和 I/O 操作的访问。 |
NfcF |
提供对 NFC-F (JIS 6319-4) 属性和 I/O 操作的访问。 |
NfcV |
提供对 NFC-V (ISO 15693) 属性和 I/O 操作的访问。 |
IsoDep |
提供对 ISO-DEP (ISO 14443-4) 属性和 I/O 操作的访问。 |
Ndef |
提供对已格式化为 NDEF 的 NFC 标签上的 NDEF 数据和操作的访问。 |
NdefFormatable |
为可能支持 NDEF 格式的标签提供格式化操作。 |
搭载 Android 系统的设备不需要支持以下标签技术。
类 | 描述 |
---|---|
MifareClassic |
如果此 Android 设备支持 MIFARE,则提供对 MIFARE Classic 属性和 I/O 操作的访问。 |
MifareUltralight |
如果此 Android 设备支持 MIFARE,则提供对 MIFARE Ultralight 属性和 I/O 操作的访问。 |
使用标签技术和 ACTION_TECH_DISCOVERED 意图
当设备扫描包含 NDEF 数据但无法映射到 MIME 或 URI 的标签时,标签调度系统会尝试使用 ACTION_TECH_DISCOVERED
意图启动活动。当扫描包含非 NDEF 数据的标签时,也会使用 ACTION_TECH_DISCOVERED
。有了此回退机制,如果标签调度系统无法为您解析数据,您就可以直接使用标签上的数据。使用标签技术的基本步骤如下:
- 过滤
ACTION_TECH_DISCOVERED
意图,指定您想要处理的标签技术。更多信息,请参见过滤 NFC 意图。通常情况下,当无法将NDEF消息映射到MIME类型或URI,或者扫描的标签不包含NDEF数据时,标签调度系统会尝试启动一个ACTION_TECH_DISCOVERED
意图。有关如何确定此信息的更多信息,请参见标签调度系统。 - 当您的应用程序接收到意图时,从意图中获取
Tag
对象。Kotlin
var tagFromIntent: Tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
Java
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
- 通过调用
android.nfc.tech
包中类的get
工厂方法之一,获取TagTechnology
的实例。您可以通过在调用get
工厂方法之前调用getTechList()
来枚举标签支持的技术。例如,要从Tag
中获取MifareUltralight
的实例,请执行以下操作。Kotlin
MifareUltralight.get(intent.getParcelableExtra(NfcAdapter.EXTRA_TAG))
Java
MifareUltralight.get(intent.getParcelableExtra(NfcAdapter.EXTRA_TAG));
读取和写入标签
读取和写入 NFC 标签涉及从意图中获取标签并打开与标签的通信。您必须定义自己的协议栈才能读取和写入标签数据。但是,请记住,在直接使用标签时,您仍然可以读取和写入 NDEF 数据。如何构建取决于您自己。以下示例演示了如何使用 MIFARE Ultralight 标签。
Kotlin
package com.example.android.nfc import android.nfc.Tag import android.nfc.tech.MifareUltralight import java.io.IOException import java.nio.charset.Charset class MifareUltralightTagTester { fun writeTag(tag: Tag, tagText: String) { MifareUltralight.get(tag)?.use { ultralight -> ultralight.connect() Charset.forName("US-ASCII").also { usAscii -> ultralight.writePage(4, "abcd".toByteArray(usAscii)) ultralight.writePage(5, "efgh".toByteArray(usAscii)) ultralight.writePage(6, "ijkl".toByteArray(usAscii)) ultralight.writePage(7, "mnop".toByteArray(usAscii)) } } } fun readTag(tag: Tag): String? { return MifareUltralight.get(tag)?.use { mifare -> mifare.connect() val payload = mifare.readPages(4) String(payload, Charset.forName("US-ASCII")) } } }
Java
package com.example.android.nfc; import android.nfc.Tag; import android.nfc.tech.MifareUltralight; import android.util.Log; import java.io.IOException; import java.nio.charset.Charset; public class MifareUltralightTagTester { private static final String TAG = MifareUltralightTagTester.class.getSimpleName(); public void writeTag(Tag tag, String tagText) { MifareUltralight ultralight = MifareUltralight.get(tag); try { ultralight.connect(); ultralight.writePage(4, "abcd".getBytes(Charset.forName("US-ASCII"))); ultralight.writePage(5, "efgh".getBytes(Charset.forName("US-ASCII"))); ultralight.writePage(6, "ijkl".getBytes(Charset.forName("US-ASCII"))); ultralight.writePage(7, "mnop".getBytes(Charset.forName("US-ASCII"))); } catch (IOException e) { Log.e(TAG, "IOException while writing MifareUltralight...", e); } finally { try { ultralight.close(); } catch (IOException e) { Log.e(TAG, "IOException while closing MifareUltralight...", e); } } } public String readTag(Tag tag) { MifareUltralight mifare = MifareUltralight.get(tag); try { mifare.connect(); byte[] payload = mifare.readPages(4); return new String(payload, Charset.forName("US-ASCII")); } catch (IOException e) { Log.e(TAG, "IOException while reading MifareUltralight message...", e); } finally { if (mifare != null) { try { mifare.close(); } catch (IOException e) { Log.e(TAG, "Error closing tag...", e); } } } return null; } }
使用前台调度系统
前台调度系统允许活动拦截意图,并声明比处理相同意图的其他活动更高的优先级。使用此系统需要构建一些数据结构,以便 Android 系统能够将相应的意图发送到您的应用程序。要启用前台调度系统,
- 在您的活动的
onCreate()
方法中添加以下代码。- 创建一个可变的
PendingIntent
对象,以便 Android 系统在扫描标签时可以使用标签的详细信息填充它。Kotlin
val intent = Intent(this, javaClass).apply { addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) } var pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE)
Java
PendingIntent pendingIntent = PendingIntent.getActivity( this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_MUTABLE);
- 声明意图过滤器以处理您想要拦截的意图。前台调度系统将指定的意图过滤器与设备扫描标签时接收到的意图进行检查。如果匹配,则您的应用程序将处理该意图。如果不匹配,则前台调度系统将回退到意图调度系统。指定一个
null
意图过滤器和技术过滤器数组,表示您想要过滤所有回退到TAG_DISCOVERED
意图的标签。下面的代码片段处理NDEF_DISCOVERED
的所有 MIME 类型。您应该只处理您需要的那些。Kotlin
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)
Java
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED); try { ndef.addDataType("*/*"); /* Handles all MIME based dispatches. You should specify only the ones that you need. */ } catch (MalformedMimeTypeException e) { throw new RuntimeException("fail", e); } intentFiltersArray = new IntentFilter[] {ndef, };
- 设置您的应用程序想要处理的一组标签技术。调用
Object.class.getName()
方法来获取您想要支持的技术的类。Kotlin
techListsArray = arrayOf(arrayOf<String>(NfcF::class.java.name))
Java
techListsArray = new String[][] { new String[] { NfcF.class.getName() } };
- 创建一个可变的
- 覆盖以下活动生命周期回调,并添加逻辑以在活动失去焦点(
onPause()
)和重新获得焦点(onResume()
)时启用和禁用前台调度。enableForegroundDispatch()
必须从主线程调用,并且只有当活动处于前台时才能调用(在onResume()
中调用可以保证这一点)。您还需要实现onNewIntent
回调来处理来自扫描的 NFC 标签的数据。
Kotlin
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 }
Java
public void onPause() { super.onPause(); adapter.disableForegroundDispatch(this); } public void onResume() { super.onResume(); adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray); } public void onNewIntent(Intent intent) { Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); // do something with tagFromIntent }