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ư cách làm việc với nhiều công nghệ thẻ, ghi vào thẻ NFC và gử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 cùng các ứng dụng đó.

Tương thích với các công nghệ thẻ được hỗ trợ

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

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

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

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

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

Lớp Mô tả
MifareClassic Cấp quyền truy cập vào các thuộc tính MIFARE Classic và các 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à các 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 thiết bị quét một thẻ có chứa dữ liệu NDEF nhưng không ánh xạ được tới MIME hoặc URI, hệ thống phân phối thẻ cố gắng bắt đầu một hoạt động bằng ACTION_TECH_DISCOVERED ý định. ACTION_TECH_DISCOVERED cũng được dùng khi một thẻ có dữ liệu không phải NDEF sẽ được quét. Việc có dự phòng này cho phép bạn làm việc với dữ liệu trên thẻ nếu hệ thống phân phối thẻ không thể phân tích cú pháp cho bạn. Các bước cơ bản khi làm việc với công nghệ thẻ như sau:

  1. Lọc cho ý định ACTION_TECH_DISCOVERED chỉ định công nghệ thẻ mà bạn muốn xử lý. Xem Lọc cho 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 ý định ACTION_TECH_DISCOVERED khi thông báo NDEF không thể ánh xạ tới loại MIME hay URI, hoặc nếu thẻ được quét không chứa dữ liệu NDEF. Cho để biết thêm thông tin về cách xác định điều này, hãy xem 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 qua ý định:

    Kotlin

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

    Java

    Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    
  3. Lấy một thực thể của TagTechnology bằng cách gọi một trong các phương thức Phương thức nhà máy 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 của get. Ví dụ: để lấy một thực thể của MifareUltralight qua 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 cho thẻ. Tuy nhiên, hãy 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 bằng thẻ. Bạn sẽ tuỳ thuộc vào cách bạn muốn sắp xếp mọi thứ. Chiến lược phát hành đĩa đơn ví dụ sau cho biết cách làm việc với thẻ 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;
    }
}

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 quyền sở hữu mức độ ưu tiên so với các hoạt động khác xử lý cùng một ý định. Việc sử dụng hệ thống này liên quan đến xây dựng một vài cấu trúc dữ liệu để hệ thống Android có thể gửi các dữ liệu thích hợp ý định cho ứng dụng của mình. Cách bật hệ thống điều phối trên nền trước:

  1. Hãy 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 vào đối tượng đó cùng với 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 chặn. Nền trước hệ thống điều phối sẽ kiểm tra bộ lọc ý định được 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 của bạn sẽ xử lý ý định. Nếu có 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. Chỉ định một mảng null gồm bộ lọc ý định và bộ lọc công nghệ, sẽ chỉ định mà bạn muốn lọc cho tất cả các thẻ dự phòng cho TAG_DISCOVERED ý định. Đoạn mã dưới đây xử lý tất cả các loại MIME cho NDEF_DISCOVERED. Bạn chỉ nên xử lý những gì bạn 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, đồng thời thêm logic để bật và tắt điều phối trên nền trước khi hoạt động bị mất (onPause()) và lấy lại tiêu điểm (onResume()). Phải gọi enableForegroundDispatch() từ luồng chính và chỉ khi hoạt động chạy ở 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ừ 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
    }