Расширенный обзор NFC

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

Работа с поддерживаемыми технологиями тегов

При работе с тегами 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) и операциям ввода-вывода.
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 из намерения:

    Котлин

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

    Ява

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

    Котлин

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

    Ява

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

Чтение и запись в теги

Чтение и запись метки NFC предполагает получение метки из намерения и открытие связи с меткой. Вы должны определить свой собственный стек протоколов для чтения и записи данных в тег. Однако имейте в виду, что вы по-прежнему можете читать и записывать данные NDEF при работе непосредственно с тегом. Вам решать, как вы хотите структурировать вещи. В следующем примере показано, как работать с тегом MIFARE Ultralight.

Котлин

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"))
        }
    }
}

Ява

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 могла заполнить его сведениями о теге при его сканировании.

      Котлин

      val intent = Intent(this, javaClass).apply {
          addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
      }
      var pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent,
              PendingIntent.FLAG_MUTABLE)
      

      Ява

      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 . Вы должны обрабатывать только те, которые вам нужны.

      Котлин

      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)
      

      Ява

      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() , чтобы получить класс технологии, которую вы хотите поддерживать.

      Котлин

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

      Ява

      techListsArray = new String[][] { new String[] { NfcF.class.getName() } };
      
  2. Переопределите следующие обратные вызовы жизненного цикла активности и добавьте логику для включения и отключения отправки переднего плана, когда активность теряет ( onPause() ) и восстанавливает ( onResume() ) фокус. enableForegroundDispatch() должен вызываться из основного потока и только тогда, когда активность находится на переднем плане (вызов onResume() гарантирует это). Вам также необходимо реализовать обратный вызов onNewIntent для обработки данных из отсканированного тега NFC.
  3. Котлин

    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
    }
    

    Ява

    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
    }