고급 NFC 개요

이 문서에서는 다양한 태그 기술 사용, NFC 태그에 쓰기, 포그라운드 디스패치(다른 애플리케이션이 동일한 인텐트를 필터링하더라도 포그라운드 애플리케이션이 인텐트를 처리할 수 있도록 함)와 같은 고급 NFC 주제를 설명합니다.

지원되는 태그 기술 사용

NFC 태그 및 Android 지원 기기를 사용할 때 태그에서 데이터를 읽고 쓰는 데 사용하는 기본 형식은 NDEF입니다. 기기가 NDEF 데이터로 태그를 검사할 때 Android는 메시지를 파싱하고 가능한 경우 NdefMessage에서 전달할 수 있도록 지원합니다. 하지만 NDEF 데이터가 포함되지 않은 태그를 검사하거나 NDEF 데이터를 MIME 유형 또는 URI에 매핑할 수 없는 경우가 있습니다. 이러한 경우 태그와 직접 통신을 열고 자체 프로토콜 (원시 바이트)으로 태그를 읽고 써야 합니다. Android는 표 1에 설명된 android.nfc.tech 패키지를 사용하여 이러한 사용 사례에 관한 일반적인 지원을 제공합니다. 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 인텐트로 활동을 시작하려고 시도합니다. ACTION_TECH_DISCOVERED는 비 NDEF 데이터가 포함된 태그를 스캔할 때도 사용됩니다. 이 대체를 사용하면 태그 디스패치 시스템에서 태그를 파싱할 수 없는 경우 태그의 데이터를 직접 사용할 수 있습니다. 태그 기술을 사용할 때의 기본 단계는 다음과 같습니다.

  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()에서 호출하면 보장됨). 스캔된 NFC 태그에서 데이터를 처리하려면 onNewIntent 콜백도 구현해야 합니다.
  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
    }