Tổng quan nâng cao về NFC

Tài liệu này mô tả các chủ đề nâng cao về NFC, chẳng hạn như làm việc với nhiều công nghệ thẻ, ghi vào thẻ NFC và điều phối trên nền trước, cho phép một ứng dụng ở nền trước xử lý ý định ngay cả khi các ứng dụng khác lọc ý định tương tự.

Làm việc với các công nghệ thẻ được hỗ trợ

Khi làm việc với thẻ NFC và thiết bị chạy Android, định dạng chính mà bạn dùng để đọc và ghi dữ liệu trên thẻ là NDEF. Khi thiết bị quét một thẻ có dữ liệu NDEF, Android sẽ hỗ trợ phân tích cú pháp thông báo và gửi thông báo đó trong NdefMessage khi có thể. Tuy nhiên, có những trường hợp như khi bạn quét một thẻ không chứa dữ liệu NDEF hoặc khi không thể ánh xạ dữ liệu NDEF tới một loại MIME hoặc URI. Trong những trường hợp này, bạn cần trực tiếp mở giao tiếp với thẻ, đồng thời đọc và ghi vào thẻ bằng giao thức của riêng mình (tính bằng byte thô). Android hỗ trợ chung cho những trường hợp sử dụng này bằng gói android.nfc.tech, như mô tả trong Bảng 1. Bạn có thể sử dụng phương thức getTechList() để xác định các công nghệ mà thẻ hỗ trợ và tạo đối tượng TagTechnology tương ứng với một trong các lớp do android.nfc.tech cung cấp

Bảng 1. Công nghệ thẻ được hỗ trợ

Lớp Nội dung mô tả
TagTechnology Giao diện mà tất cả các lớp công nghệ thẻ phải triển khai.
NfcA Cấp quyền truy cập vào các thuộc tính NFC-A (ISO 14443-3A) và các hoạt động I/O.
NfcB Cấp quyền truy cập vào các thuộc tính NFC-B (ISO 14443-3B) và các hoạt động I/O.
NfcF Cung cấp quyền truy cập vào các thuộc tính NFC-F (JIS 6319-4) và các hoạt động I/O.
NfcV Cấp quyền truy cập vào các thuộc tính NFC-V (ISO 15693) và các hoạt động I/O.
IsoDep Cấp quyền truy cập vào các thuộc tính ISO-DEP (ISO 14443-4) và các hoạt động I/O.
Ndef Cấp quyền truy cập vào dữ liệu và các thao tác NDEF trên thẻ NFC đã được định dạng là NDEF.
NdefFormatable Cung cấp một thao tác định dạng cho những thẻ có thể có định dạng NDEF.

Các công nghệ thẻ sau đây không bắt buộc được hỗ trợ trên thiết bị chạy Android.

Bảng 2. Công nghệ thẻ được hỗ trợ (không bắt buộc)

Lớp Nội dung mô tả
MifareClassic Cấp quyền truy cập vào các thuộc tính MIFARE phiên bản cũ và thao tác I/O nếu thiết bị Android này hỗ trợ MIFARE.
MifareUltralight Cấp quyền truy cập vào các thuộc tính MIFARE Ultralight và thao tác I/O nếu thiết bị Android này hỗ trợ MIFARE.

Làm việc với các công nghệ thẻ và ý định ACTION_TECH_discoverED

Khi một thiết bị quét một thẻ có dữ liệu NDEF, nhưng không thể ánh xạ tới một MIME hoặc URI, hệ thống điều phối thẻ sẽ cố gắng bắt đầu một hoạt động bằng ý định ACTION_TECH_DISCOVERED. ACTION_TECH_DISCOVERED cũng được dùng khi một thẻ có dữ liệu không phải NDEF được quét. Việc có phương án dự phòng này cho phép bạn làm việc trực tiếp với dữ liệu trên thẻ nếu hệ thống điều phối thẻ không thể phân tích cú pháp dữ liệu cho bạn. Sau đây là các bước cơ bản khi làm việc với công nghệ thẻ:

  1. Lọc ý định ACTION_TECH_DISCOVERED để chỉ định các công nghệ thẻ mà bạn muốn xử lý. Hãy xem bài viết Lọc theo ý định NFC để biết thêm thông tin. Nói chung, hệ thống điều phối thẻ sẽ cố gắng bắt đầu một ý định ACTION_TECH_DISCOVERED khi không thể ánh xạ thông báo NDEF tới một loại MIME hay URI, hoặc nếu thẻ được quét không chứa dữ liệu NDEF. Để biết thêm thông tin về cách xác định điều này, hãy xem phần Hệ thống điều phối thẻ.
  2. Khi ứng dụng của bạn nhận được ý định, hãy lấy đối tượng Tag từ ý định đó:

    Kotlin

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

    Java

    Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    
  3. Lấy thực thể của TagTechnology bằng cách gọi một trong các phương thức ban đầu get của các lớp trong gói android.nfc.tech. Bạn có thể liệt kê các công nghệ được hỗ trợ của thẻ bằng cách gọi getTechList() trước khi gọi phương thức ban đầu get. Ví dụ: để lấy thực thể của MifareUltralight từ Tag, hãy làm như sau:

    Kotlin

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

    Java

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

Đọc và ghi vào thẻ

Việc đọc và ghi vào thẻ NFC liên quan đến việc lấy thẻ từ ý định và mở giao tiếp với thẻ. Bạn phải xác định ngăn xếp giao thức của riêng mình để đọc và ghi dữ liệu vào thẻ. Tuy nhiên, xin lưu ý rằng bạn vẫn có thể đọc và ghi dữ liệu NDEF khi làm việc trực tiếp với một thẻ. Bạn muốn sắp xếp mọi thứ theo cách nào là tuỳ thuộc vào bạn. Ví dụ sau đây cho thấy cách làm việc với thẻ MIFARE Siêu nhẹ.

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;
    }
}

Dùng hệ thống điều phối trên nền trước

Hệ thống điều phối trên nền trước cho phép một hoạt động chặn một ý định và xác nhận mức độ ưu tiên so với các hoạt động khác xử lý cùng một ý định. Khi sử dụng hệ thống này, bạn cần xây dựng một số cấu trúc dữ liệu để hệ thống Android có thể gửi các ý định thích hợp đến ứng dụng của bạn. Cách bật hệ thống điều phối trên nền trước:

  1. Thêm mã sau vào phương thức onCreate() của hoạt động:
    1. Tạo một đối tượng PendingIntent có thể thay đổi để hệ thống Android có thể điền thông tin chi tiết về thẻ khi quét.

      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. Khai báo bộ lọc ý định để xử lý ý định mà bạn muốn giao cắt. Hệ thống điều phối trên nền trước sẽ kiểm tra các bộ lọc ý định đã chỉ định với ý định nhận được khi thiết bị quét một thẻ. Nếu khớp thì ứng dụng sẽ xử lý ý định. Nếu không khớp, hệ thống điều phối trên nền trước sẽ quay lại hệ thống điều phối ý định. Việc chỉ định một mảng null gồm các bộ lọc ý định và bộ lọc công nghệ sẽ chỉ định rằng bạn muốn lọc mọi thẻ dự phòng cho ý định TAG_DISCOVERED. Đoạn mã dưới đây xử lý tất cả loại MIME cho NDEF_DISCOVERED. Bạn chỉ nên xử lý những truy vấn mà mình cần.

      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. Thiết lập một loạt công nghệ thẻ mà ứng dụng của bạn muốn xử lý. Gọi phương thức Object.class.getName() để lấy lớp công nghệ mà bạn muốn hỗ trợ.

      Kotlin

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

      Java

      techListsArray = new String[][] { new String[] { NfcF.class.getName() } };
      
  2. Ghi đè các phương thức gọi lại trong vòng đời hoạt động sau đây và thêm logic để bật và tắt tính năng điều phối trên nền trước khi hoạt động mất (onPause()) và lấy lại tiêu điểm (onResume()). enableForegroundDispatch() phải được gọi từ luồng chính và chỉ khi hoạt động ở nền trước (việc gọi trong onResume() đảm bảo điều này). Bạn cũng cần triển khai lệnh gọi lại onNewIntent để xử lý dữ liệu từ thẻ NFC đã quét.
  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
    }