高级 NFC 概览

本文档介绍了高级 NFC 主题,例如如何使用各种标签技术、 写入 NFC 标签以及前台调度,这使得前台的应用可以 即使其他应用过滤相同的 intent,也会处理这些 intent。

使用支持的标签技术

在使用 NFC 标签和 Android 设备时,读取文件中 标签数据是 NDEF。当设备扫描包含 NDEF 数据的标签时,Android 会提供支持 在解析消息并在 NdefMessage 中传递它时, 不过,在某些情况下,您扫描的代码不包含 NDEF 数据,或者 NDEF 数据无法映射到 MIME 类型或 URI 时。 在这些情况下,您需要直接与代码进行通信,并使用 (以原始字节为单位)。Android 通过 android.nfc.tech 软件包(如表 1 中所述)。您可以 使用 getTechList() 方法确定技术 创建相应的TagTechnology 对象与 android.nfc.tech 提供的某个类相关联

表 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 提供对以下格式的 NFC 标签上 NDEF 数据和操作的访问权限: NDEF。
NdefFormatable 为可设置为 NDEF 格式的标签提供格式化操作。

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

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

说明
MifareClassic 提供对 MIFARE Classic 属性和 I/O 操作的访问权限(如果此 Android 设备) 支持 MIFARE。
MifareUltralight 提供对 MIFARE Ultralight 属性和 I/O 操作的访问权限(如果此 Android 设备支持 MIFARE。

将标签技术和 ACTION_TECH_DISCOVERED Intent 结合使用

如果设备扫描的标签包含 NDEF 数据,但无法映射到 MIME 或 URI, 标签调度系统尝试使用 ACTION_TECH_DISCOVERED 启动 activity intent。ACTION_TECH_DISCOVERED 也可用于标记 包含非 NDEF 数据的数据。有了此后备广告,您就可以处理代码中的数据 。使用 标签技术的说明如下:

  1. 过滤指定 ACTION_TECH_DISCOVERED intent 您要处理的代码技术请参阅过滤 NFC intent。一般情况下,标签调度系统会在收到 NDEF 消息时尝试启动 ACTION_TECH_DISCOVERED intent 无法映射到 MIME 类型或 URI,或者扫描的标记不包含 NDEF 数据。对于 如需详细了解如何确定,请参阅代码调度系统
  2. 当应用收到 intent 时,从Tag intent:

    Kotlin

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

    Java

    Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    
  3. 通过调用以下其中一个方法获取 TagTechnology 的实例: android.nfc.tech 软件包中类的 get 工厂方法。您可以 通过在调用 get 工厂方法之前调用 getTechList(),枚举支持的标签技术。例如,如需获取 (从 Tag 获取 MifareUltralight),请执行以下操作:

    Kotlin

    MifareUltralight.get(intent.getParcelableExtra(NfcAdapter.EXTRA_TAG))
    

    Java

    MifareUltralight.get(intent.getParcelableExtra(NfcAdapter.EXTRA_TAG));
    

对标签执行读写操作

对 NFC 标签执行读写操作涉及从 intent 中获取标签, 以便与代码进行通信您必须定义自己的协议栈才能读取和写入数据 添加到代码中。不过请注意,在工作时,您仍然可以读取和写入 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;
    }
}

使用前台调度系统

前台调度系统允许 activity 拦截 intent 和声明 相较于处理同一 intent 的其他 activity 的优先级。使用此系统涉及 构造一些数据结构,以便 Android 系统能够发送相应的 intent。要启用前台调度系统,请执行以下操作:

  1. 在您的 activity 的 onCreate() 方法中添加以下代码: <ph type="x-smartling-placeholder">
      </ph>
    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. 声明 Intent 过滤器,以处理您要拦截的 Intent。前景 调度系统会根据以下情况检查指定的 intent 过滤器: 设备会扫描标签如果匹配,那么应用会处理该 Intent。如果是 如果不匹配,前台调度系统会回退到 intent 调度系统。 指定由 intent 过滤器和技术过滤器组成的 null 数组, 您要过滤出所有回退到 TAG_DISCOVERED 的代码 intent。以下代码段会处理 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. 替换以下 activity 生命周期回调,并添加逻辑以启用和停用 activity 丢失时前台调度 (onPause()) 并重新获得 (onResume()) 专注模式。必须从以下来源调用 enableForegroundDispatch(): 主线程;仅当 activity 位于前台时(在 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
    }