Общие сведения о расширенных функциях NFC

В этом документе описываются расширенные темы NFC, такие как работа с различными технологиями тегов, запись в теги NFC и диспетчеризация переднего плана, которая позволяет приложению на переднем плане обрабатывать намерения, даже если другие приложения фильтруют те же самые.

Поддерживаемые технологии тегов

При работе с NFC-тегами и устройствами Android основным форматом, который вы используете для чтения и записи данных в тегах, является NDEF. Когда устройство сканирует тег с данными NDEF, Android помогает проанализировать сообщение и по возможности передать его в NdefMessage. Однако в некоторых случаях тег не содержит данных NDEF или их невозможно сопоставить с MIME-типом или URI. В таких случаях вам нужно установить связь непосредственно с тегом и считывать и записывать данные в него с помощью собственного протокола (в необработанных байтах). Android предоставляет общую поддержку для этих сценариев использования с помощью пакета android.nfc.tech, который описан в таблице 1. Вы можете использовать метод getTechList(), чтобы определить, какие технологии поддерживает тег, и создать соответствующий объект TagTechnology с помощью одного из классов, предоставляемых android.nfc.tech.

Таблица 1. Поддерживаемые технологии тегов

Класс Описание
TagTechnology Интерфейс, который должны реализовывать все классы технологий тегов.
NfcA Предоставляет доступ к свойствам NFC-A (ISO 14443-3A) и операциям ввода-вывода.
NfcB Предоставляет доступ к свойствам NFC-B (ISO 14443-3B) и операциям ввода-вывода.
NfcF Предоставляет доступ к свойствам NFC-F (JIS 6319-4) и операциям ввода-вывода.
NfcV Предоставляет доступ к свойствам NFC-V (ISO 15693) и операциям ввода-вывода.
IsoDep Предоставляет доступ к свойствам ISO-DEP (ISO 14443-4) и операциям ввода-вывода.
Ndef Предоставляет доступ к данным NDEF и операциям с NFC-тегами, отформатированными как NDEF.
NdefFormatable Предоставляет операции форматирования для тегов, которые могут быть отформатированы в NDEF.

Устройства Android не обязаны поддерживать следующие технологии тегов:

Таблица 2. Поддерживаемые технологии тегов (необязательно)

Класс Описание
MifareClassic Предоставляет доступ к свойствам MIFARE Classic и операциям ввода-вывода, если устройство Android поддерживает MIFARE.
MifareUltralight Предоставляет доступ к свойствам MIFARE Ultralight и операциям ввода-вывода, если это устройство Android поддерживает MIFARE.

Как работать с технологиями тегов и намерением ACTION_TECH_DISCOVERED

Когда устройство сканирует тег с данными NDEF, которые не удается сопоставить с MIME или URI, система отправки тегов пытается запустить действие с намерением ACTION_TECH_DISCOVERED. ACTION_TECH_DISCOVERED также используется при сканировании тега с данными, не относящимися к NDEF. Это позволяет работать с данными тега напрямую, если система отправки тегов не смогла их обработать. Основные этапы работы с технологиями тегов:

  1. Фильтр для намерения ACTION_TECH_DISCOVERED, указывающий технологии тегов, которые вы хотите обрабатывать. Подробнее о фильтрации намерений NFC… Как правило, система диспетчеризации тегов пытается запустить намерение ACTION_TECH_DISCOVERED, когда сообщение NDEF не удается сопоставить с MIME-типом или URI или если отсканированный тег не содержит данных NDEF. Подробнее о том, как это определяется, читайте в разделе Система отправки тегов.
  2. Когда приложение получит намерение, извлеките из него объект Tag:

    Kotlin

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

    Java

    Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
  3. Получите экземпляр TagTechnology, вызвав один из фабричных методов get классов в пакете android.nfc.tech. Вы можете перечислить поддерживаемые технологии тега, вызвав getTechList() перед вызовом фабричного метода get. Например, чтобы получить экземпляр MifareUltralight из Tag, выполните следующие действия:

    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() своего класса activity:
    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. Приведенный ниже фрагмент кода обрабатывает все MIME-типы для NDEF_DISCOVERED. Вам нужно обрабатывать только те, которые вам необходимы.

      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() необходимо вызывать из основного потока и только когда действие находится на переднем плане (вызов в 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
    }