NFC 高级概述

本文档介绍了高级 NFC 主题,例如使用各种标签技术、写入 NFC 标签以及前台调度,后者允许前台应用处理意图,即使其他应用过滤了相同的意图。

使用受支持的标签技术

在使用 NFC 标签和搭载 Android 的设备时,用于在标签上读取和写入数据的主要格式为 NDEF。当设备扫描具有 NDEF 数据的标签时,Android 提供了对解析消息并在可能的情况下以 NdefMessage 格式传递消息的支持。但是,在某些情况下,您扫描的标签可能不包含 NDEF 数据,或者 NDEF 数据无法映射到 MIME 类型或 URI。在这些情况下,您需要直接与标签建立通信,并使用您自己的协议(以原始字节为单位)进行读写。Android 通过 android.nfc.tech 包为这些用例提供了通用支持,该包在 表 1 中进行了描述。您可以使用 getTechList() 方法确定标签支持的技术,并使用 android.nfc.tech 提供的类之一创建相应的 TagTechnology 对象。

表 1. 受支持的标签技术

描述
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 数据以及已格式化为 NDEF 的 NFC 标签上的操作的访问。
NdefFormatable 为可能可格式化为 NDEF 的标签提供格式化操作。

以下标签技术不需要搭载 Android 的设备支持。

表 2. 可选支持的标签技术

描述
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。拥有此回退功能允许您直接处理标签上的数据,如果标签分发系统无法为您解析它。使用标签技术的步骤如下

  1. 筛选 ACTION_TECH_DISCOVERED 意图,指定您要处理的标签技术。有关更多信息,请参阅 筛选 NFC 意图。通常,当 NDEF 消息无法映射到 MIME 类型或 URI,或者扫描的标签不包含 NDEF 数据时,标签分发系统会尝试启动 ACTION_TECH_DISCOVERED 意图。有关如何确定此操作的更多信息,请参阅 标签分发系统
  2. 当您的应用程序收到意图时,从意图中获取 Tag 对象

    Kotlin

    var tagFromIntent: Tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
    

    Java

    Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    
  3. 通过调用 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 系统构建一些数据结构,以便能够将适当的意图发送到您的应用程序。要启用前台分发系统

  1. 在活动的 onCreate() 方法中添加以下代码
    1. 创建一个可变的 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);
      
    2. 声明意图过滤器以处理您要拦截的意图。前台分发系统使用接收到的意图(当设备扫描标签时)检查指定的意图过滤器。如果匹配,则您的应用程序将处理该意图。如果不匹配,则前台分发系统将回退到意图分发系统。指定 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, };
      
    3. 设置您的应用程序要处理的标签技术的数组。调用 Object.class.getName() 方法以获取要支持的技术的类。

      Kotlin

      techListsArray = arrayOf(arrayOf<String>(NfcF::class.java.name))
      

      Java

      techListsArray = new String[][] { new String[] { NfcF.class.getName() } };
      
  2. 覆盖以下活动生命周期回调,并添加逻辑以在活动失去焦点 (onPause()) 和重新获得焦点 (onResume()) 时启用和禁用前台分发。 enableForegroundDispatch() 必须从主线程调用,并且仅在活动处于前台时调用(在 onResume() 中调用可以保证这一点)。您还需要实现 onNewIntent 回调以处理来自扫描的 NFC 标签的数据。
  3. 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
    }