進階 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 格式的 NFC 標記提供 NDEF 資料和作業的存取權。
NdefFormatable 提供可能是 NDEF 格式的代碼格式作業。

Android 裝置不需要支援下列代碼技術。

表 2. 選用支援的代碼技術

類別 說明
MifareClassic 提供 MIFARE 傳統屬性和 I/O 作業的存取權 (如果這個 Android 裝置支援 MIFARE)。
MifareUltralight 提供 MIFARE Ultralight 屬性和 I/O 作業的權限 (如果這個 Android 裝置支援 MIFARE)。

使用代碼技術和 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
    }