Skip to content

NFC 读写实战

源:Android NFC Basics

本文展示如何用 Kotlin/Native 编写 JNI 代码实现 Android NFC 标签的读写操作,涵盖 NDEF 消息解析、标签类型识别和高性能字节流处理。通过 Native 层处理 NFC 数据,实现零拷贝操作和更高的安全性。

项目背景

为什么用 Native 处理 NFC 数据

传统的 Kotlin/Java NFC 处理在复杂场景下存在性能和安全性问题:

kotlin
// ❌ 传统 Kotlin 代码 - 存在问题
class NfcHandler {
    fun parseNdefMessage(rawData: ByteArray): String {
        // 1. 字节数组频繁复制
        // 2. 敏感数据在 JVM 堆中停留时间长
        // 3. 复杂二进制解析性能差
        // 4. 无法使用加密硬件加速
        
        val message = NdefMessage(rawData)
        return processMessage(message) // 慢且不安全
    }
}

使用 Kotlin/Native JNI 直接处理:

kotlin
// ✅ Kotlin/Native JNI - 高性能+安全
@CName("Java_com_example_nfc_NfcProcessor_parseNdefMessageNative")
fun parseNdefMessageNative(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    rawData: jbyteArray
): jobject {
    // 直接访问 native 内存
    // 零拷贝、即时清除敏感数据
    // 可调用硬件加密 API
    return parseNdefOptimized(env, rawData)
}

完整项目架构

项目结构

NfcJNIDemo/
├── app/
│   ├── src/
│   │   ├── androidNativeMain/kotlin/
│   │   │   └── com/example/nfc/
│   │   │       ├── NfcJNI.kt              # JNI 实现
│   │   │       ├── NdefParser.kt          # NDEF 解析
│   │   │       ├── TagIdentifier.kt       # 标签识别
│   │   │       └── CryptoHelper.kt        # 加密辅助
│   │   └── main/
│   │       ├── java/com/example/nfc/
│   │       │   ├── NfcActivity.kt         # Activity
│   │       │   ├── NfcManager.kt          # NFC 管理
│   │       │   └── NfcProcessor.kt        # Native 接口
│   │       └── AndroidManifest.xml
│   └── build.gradle.kts

Gradle 配置

kotlin
// app/build.gradle.kts
plugins {
    id("com.android.application")
    kotlin("android")
    kotlin("multiplatform")
}

kotlin {
    androidNativeArm64()
    androidNativeX64()
    
    sourceSets {
        val androidNativeMain by creating {
            dependencies {
                // 可选:加密库
                // implementation("org.jetbrains.kotlinx:kotlinx-io:0.3.1")
            }
        }
    }
    
    targets.withType<KotlinNativeTarget> {
        binaries {
            sharedLib {
                baseName = "nfc-processor"
            }
        }
    }
}

android {
    namespace = "com.example.nfc"
    compileSdk = 34
    
    defaultConfig {
        minSdk = 24  // NFC API 完整支持
        targetSdk = 34
    }
}

权限配置

xml
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    
    <!-- NFC 权限 -->
    <uses-permission android:name="android.permission.NFC" />
    
    <!-- 声明 NFC 硬件需求(可选) -->
    <uses-feature 
        android:name="android.hardware.nfc"
        android:required="true" />
    
    <application>
        <activity android:name=".NfcActivity">
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <!-- 处理特定 MIME 类型 -->
                <data android:mimeType="text/plain" />
            </intent-filter>
            
            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

Kotlin 侧 NFC 接口

NfcProcessor - Native 接口类

kotlin
// src/main/java/com/example/nfc/NfcProcessor.kt
package com.example.nfc

import android.nfc.NdefMessage

class NfcProcessor {
    companion object {
        init {
            System.loadLibrary("nfc-processor")
        }
    }
    
    /**
     * 解析 NDEF 消息
     * @param rawData NDEF 原始字节数据
     * @return 解析后的记录数组 [type, id, payload]
     */
    external fun parseNdefMessage(rawData: ByteArray): Array<NdefRecordInfo>
    
    /**
     * 创建 URI NDEF 记录
     * @param uri URI 字符串
     * @return NDEF 记录字节数组
     */
    external fun createUriRecord(uri: String): ByteArray
    
    /**
     * 创建文本 NDEF 记录
     * @param text 文本内容
     * @param languageCode 语言代码(如 "en", "zh")
     * @return NDEF 记录字节数组
     */
    external fun createTextRecord(text: String, languageCode: String): ByteArray
    
    /**
     * 创建自定义外部类型记录
     * @param domain 域名(如 "example.com")
     * @param type 类型名称
     * @param payload 负载数据
     * @return NDEF 记录字节数组
     */
    external fun createExternalRecord(
        domain: String,
        type: String,
        payload: ByteArray
    ): ByteArray
    
    /**
     * 识别标签类型
     * @param techList 技术列表
     * @param atqa ATQA 值
     * @param sak SAK 值
     * @return 标签类型字符串
     */
    external fun identifyTagType(
        techList: Array<String>,
        atqa: ByteArray?,
        sak: Byte?
    ): String
    
    /**
     * 验证 Mifare Classic 扇区
     * @param sectorData 扇区数据
     * @param key 密钥(6字节)
     * @return 是否验证成功
     */
    external fun verifyMifareClassicSector(
        sectorData: ByteArray,
        key: ByteArray
    ): Boolean
}

/**
 * NDEF 记录信息
 */
data class NdefRecordInfo(
    val tnf: Int,           // TNF (Type Name Format)
    val type: String,       // 类型
    val id: String,         // ID
    val payload: ByteArray  // 负载数据
)

NfcManager - NFC 管理类

kotlin
// src/main/java/com/example/nfc/NfcManager.kt
package com.example.nfc

import android.app.Activity
import android.app.PendingIntent
import android.content.Intent
import android.content.IntentFilter
import android.nfc.NdefMessage
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.nfc.tech.*
import android.os.Build
import android.util.Log

class NfcManager(private val activity: Activity) {
    private var nfcAdapter: NfcAdapter? = null
    private val processor = NfcProcessor()
    
    var onTagDiscovered: ((Tag) -> Unit)? = null
    var onNdefMessageRead: ((Array<NdefRecordInfo>) -> Unit)? = null
    
    init {
        nfcAdapter = NfcAdapter.getDefaultAdapter(activity)
        if (nfcAdapter == null) {
            Log.e("NFC", "设备不支持 NFC")
        }
    }
    
    /**
     * 启用前台分发
     */
    fun enableForegroundDispatch() {
        val adapter = nfcAdapter ?: return
        
        val intent = Intent(activity, activity.javaClass).apply {
            addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
        }
        
        val pendingIntent = PendingIntent.getActivity(
            activity,
            0,
            intent,
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                PendingIntent.FLAG_MUTABLE
            } else {
                0
            }
        )
        
        val filters = arrayOf(
            IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED),
            IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
        )
        
        adapter.enableForegroundDispatch(activity, pendingIntent, filters, null)
        Log.d("NFC", "前台分发已启用")
    }
    
    /**
     * 禁用前台分发
     */
    fun disableForegroundDispatch() {
        nfcAdapter?.disableForegroundDispatch(activity)
        Log.d("NFC", "前台分发已禁用")
    }
    
    /**
     * 处理 NFC Intent
     */
    fun handleIntent(intent: Intent) {
        val action = intent.action
        val tag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java)
        } else {
            @Suppress("DEPRECATION")
            intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
        }
        
        tag?.let {
            onTagDiscovered?.invoke(it)
            
            when (action) {
                NfcAdapter.ACTION_NDEF_DISCOVERED -> readNdefMessage(tag)
                NfcAdapter.ACTION_TAG_DISCOVERED -> identifyTag(tag)
            }
        }
    }
    
    /**
     * 读取 NDEF 消息
     */
    private fun readNdefMessage(tag: Tag) {
        val ndef = Ndef.get(tag) ?: run {
            Log.w("NFC", "标签不支持 NDEF")
            return
        }
        
        try {
            ndef.connect()
            val ndefMessage = ndef.ndefMessage
            
            if (ndefMessage != null) {
                // ✅ 调用 Native 解析
                val rawData = ndefMessage.toByteArray()
                val records = processor.parseNdefMessage(rawData)
                
                onNdefMessageRead?.invoke(records)
                
                records.forEach { record ->
                    Log.d("NFC", "Record: TNF=${record.tnf}, Type=${record.type}")
                }
            }
        } catch (e: Exception) {
            Log.e("NFC", "读取 NDEF 失败", e)
        } finally {
            ndef.close()
        }
    }
    
    /**
     * 识别标签类型
     */
    private fun identifyTag(tag: Tag) {
        val techList = tag.techList
        
        // 获取 NfcA 信息(如果可用)
        val nfcA = NfcA.get(tag)
        val atqa = nfcA?.atqa
        val sak = nfcA?.sak
        
        // ✅ 调用 Native 识别
        val tagType = processor.identifyTagType(techList, atqa, sak)
        
        Log.d("NFC", "标签类型: $tagType")
        Log.d("NFC", "技术列表: ${techList.joinToString()}")
    }
    
    /**
     * 写入 NDEF 消息
     */
    fun writeNdefMessage(tag: Tag, text: String): Boolean {
        val ndef = Ndef.get(tag) ?: NdefFormatable.get(tag)?.let { formatable ->
            // 格式化标签
            val record = processor.createTextRecord(text, "zh")
            val message = NdefMessage(record)
            
            try {
                formatable.connect()
                formatable.format(message)
                formatable.close()
                return true
            } catch (e: Exception) {
                Log.e("NFC", "格式化失败", e)
                return false
            }
        } ?: run {
            Log.w("NFC", "标签不支持 NDEF")
            return false
        }
        
        if (!ndef.isWritable) {
            Log.w("NFC", "标签不可写")
            return false
        }
        
        try {
            ndef.connect()
            
            // ✅ 使用 Native 创建记录
            val record = processor.createTextRecord(text, "zh")
            val message = NdefMessage(record)
            
            ndef.writeNdefMessage(message)
            Log.d("NFC", "写入成功")
            return true
        } catch (e: Exception) {
            Log.e("NFC", "写入失败", e)
            return false
        } finally {
            ndef.close()
        }
    }
}

Native 侧实现

NfcJNI.kt - JNI 入口

kotlin
// src/androidNativeMain/kotlin/com/example/nfc/NfcJNI.kt
@file:OptIn(ExperimentalForeignApi::class)

package com.example.nfc

import kotlinx.cinterop.*
import platform.android.*

// TNF 常量
const val TNF_EMPTY = 0x00
const val TNF_WELL_KNOWN = 0x01
const val TNF_MIME_MEDIA = 0x02
const val TNF_ABSOLUTE_URI = 0x03
const val TNF_EXTERNAL_TYPE = 0x04
const val TNF_UNKNOWN = 0x05
const val TNF_UNCHANGED = 0x06

// 辅助函数:获取字节数组
fun getByteArray(env: CPointer<JNIEnvVar>, array: jbyteArray): Pair<CPointer<ByteVar>?, Int> {
    val jniEnv = env.pointed.pointed!!
    val length = jniEnv.GetArrayLength!!(env, array)
    val elements = jniEnv.GetByteArrayElements!!(env, array, null)
    return elements to length
}

// 辅助函数:释放字节数组
fun releaseByteArray(
    env: CPointer<JNIEnvVar>,
    array: jbyteArray,
    elements: CPointer<ByteVar>?,
    mode: Int = JNI_ABORT
) {
    val jniEnv = env.pointed.pointed!!
    jniEnv.ReleaseByteArrayElements!!(env, array, elements, mode)
}

// 辅助函数:创建 Java 字节数组
fun createJByteArray(env: CPointer<JNIEnvVar>, data: CPointer<ByteVar>, size: Int): jbyteArray? {
    val jniEnv = env.pointed.pointed!!
    val array = jniEnv.NewByteArray!!(env, size) ?: return null
    jniEnv.SetByteArrayRegion!!(env, array, 0, size, data)
    return array
}

// 辅助函数:创建 Java 字符串
fun createJString(env: CPointer<JNIEnvVar>, text: String): jstring? {
    val jniEnv = env.pointed.pointed!!
    return jniEnv.NewStringUTF!!(env, text.cstr.ptr)
}

// 辅助函数:从 Java 字符串获取 UTF-8
fun getStringUTF(env: CPointer<JNIEnvVar>, jstr: jstring): String? {
    val jniEnv = env.pointed.pointed!!
    val chars = jniEnv.GetStringUTFChars!!(env, jstr, null) ?: return null
    val result = chars.toKString()
    jniEnv.ReleaseStringUTFChars!!(env, jstr, chars)
    return result
}

@CName("Java_com_example_nfc_NfcProcessor_parseNdefMessage")
fun parseNdefMessage(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    rawData: jbyteArray
): jobjectArray? {
    val jniEnv = env.pointed.pointed!!
    val (dataPtr, length) = getByteArray(env, rawData)
    
    if (dataPtr == null || length < 3) {
        dataPtr?.let { releaseByteArray(env, rawData, it) }
        return null
    }
    
    // 解析 NDEF 消息
    val records = mutableListOf<NdefRecordData>()
    var offset = 0
    
    while (offset < length) {
        // 读取标志字节
        val flags = dataPtr[offset].toUByte().toInt()
        offset++
        
        if (offset >= length) break
        
        // 提取标志位
        val mb = (flags and 0x80) != 0  // Message Begin
        val me = (flags and 0x40) != 0  // Message End
        val cf = (flags and 0x20) != 0  // Chunk Flag
        val sr = (flags and 0x10) != 0  // Short Record
        val il = (flags and 0x08) != 0  // ID Length present
        val tnf = flags and 0x07        // TNF
        
        // 读取类型长度
        val typeLength = dataPtr[offset].toUByte().toInt()
        offset++
        
        if (offset >= length) break
        
        // 读取负载长度
        val payloadLength = if (sr) {
            dataPtr[offset].toUByte().toInt().also { offset++ }
        } else {
            if (offset + 3 >= length) break
            val len = ((dataPtr[offset].toUByte().toInt() shl 24) or
                      (dataPtr[offset + 1].toUByte().toInt() shl 16) or
                      (dataPtr[offset + 2].toUByte().toInt() shl 8) or
                      dataPtr[offset + 3].toUByte().toInt())
            offset += 4
            len
        }
        
        // 读取 ID 长度
        val idLength = if (il && offset < length) {
            dataPtr[offset].toUByte().toInt().also { offset++ }
        } else {
            0
        }
        
        // 读取类型
        val type = ByteArray(typeLength)
        if (offset + typeLength <= length) {
            for (i in 0 until typeLength) {
                type[i] = dataPtr[offset + i]
            }
            offset += typeLength
        }
        
        // 读取 ID
        val id = ByteArray(idLength)
        if (idLength > 0 && offset + idLength <= length) {
            for (i in 0 until idLength) {
                id[i] = dataPtr[offset + i]
            }
            offset += idLength
        }
        
        // 读取负载
        val payload = ByteArray(payloadLength)
        if (offset + payloadLength <= length) {
            for (i in 0 until payloadLength) {
                payload[i] = dataPtr[offset + i]
            }
            offset += payloadLength
        }
        
        records.add(NdefRecordData(tnf, type, id, payload))
        
        if (me) break
    }
    
    releaseByteArray(env, rawData, dataPtr)
    
    // 创建 Java 对象数组
    val recordClass = jniEnv.FindClass!!(env, "com/example/nfc/NdefRecordInfo")
        ?: return null
    
    val recordArray = jniEnv.NewObjectArray!!(env, records.size, recordClass, null)
        ?: return null
    
    val constructor = jniEnv.GetMethodID!!(
        env, recordClass, "<init>",
        "(ILjava/lang/String;Ljava/lang/String;[B)V"
    ) ?: return null
    
    records.forEachIndexed { index, record ->
        val typeStr = createJString(env, record.type.decodeToString())
        val idStr = createJString(env, record.id.decodeToString())
        val payloadArr = createJByteArray(env, record.payload.refTo(0), record.payload.size)
        
        val obj = jniEnv.NewObject!!(
            env, recordClass, constructor,
            record.tnf, typeStr, idStr, payloadArr
        )
        
        jniEnv.SetObjectArrayElement!!(env, recordArray, index, obj)
    }
    
    return recordArray
}

@CName("Java_com_example_nfc_NfcProcessor_createTextRecord")
fun createTextRecord(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    text: jstring,
    languageCode: jstring
): jbyteArray? {
    val textStr = getStringUTF(env, text) ?: return null
    val langStr = getStringUTF(env, languageCode) ?: "en"
    
    val textBytes = textStr.encodeToByteArray()
    val langBytes = langStr.encodeToByteArray()
    
    // 构建 NDEF 文本记录
    // 标志: MB=1, ME=1, SR=1, TNF=0x01
    val flags: Byte = 0xD1.toByte()
    
    // 类型: "T" (0x54)
    val type = byteArrayOf(0x54)
    val typeLength: Byte = 1
    
    // 状态字节: 编码(UTF-8) + 语言代码长度
    val statusByte = (langBytes.size and 0x3F).toByte()
    
    // 负载 = 状态字节 + 语言代码 + 文本
    val payloadLength = 1 + langBytes.size + textBytes.size
    val payload = ByteArray(payloadLength)
    payload[0] = statusByte
    langBytes.copyInto(payload, 1)
    textBytes.copyInto(payload, 1 + langBytes.size)
    
    // 完整记录
    val record = ByteArray(3 + typeLength + payloadLength)
    record[0] = flags
    record[1] = typeLength
    record[2] = payloadLength.toByte()
    type.copyInto(record, 3)
    payload.copyInto(record, 3 + typeLength)
    
    return createJByteArray(env, record.refTo(0), record.size)
}

@CName("Java_com_example_nfc_NfcProcessor_createUriRecord")
fun createUriRecord(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    uri: jstring
): jbyteArray? {
    val uriStr = getStringUTF(env, uri) ?: return null
    
    // URI 缩写前缀
    val prefixes = mapOf(
        "http://www." to 0x01.toByte(),
        "https://www." to 0x02.toByte(),
        "http://" to 0x03.toByte(),
        "https://" to 0x04.toByte()
    )
    
    var identifierCode: Byte = 0x00
    var uriContent = uriStr
    
    for ((prefix, code) in prefixes) {
        if (uriStr.startsWith(prefix)) {
            identifierCode = code
            uriContent = uriStr.substring(prefix.length)
            break
        }
    }
    
    val uriBytes = uriContent.encodeToByteArray()
    
    // 标志: MB=1, ME=1, SR=1, TNF=0x01
    val flags: Byte = 0xD1.toByte()
    
    // 类型: "U" (0x55)
    val type = byteArrayOf(0x55)
    val typeLength: Byte = 1
    
    // 负载 = 标识符代码 + URI
    val payloadLength = 1 + uriBytes.size
    val payload = ByteArray(payloadLength)
    payload[0] = identifierCode
    uriBytes.copyInto(payload, 1)
    
    // 完整记录
    val record = ByteArray(3 + typeLength + payloadLength)
    record[0] = flags
    record[1] = typeLength
    record[2] = payloadLength.toByte()
    type.copyInto(record, 3)
    payload.copyInto(record, 3 + typeLength)
    
    return createJByteArray(env, record.refTo(0), record.size)
}

@CName("Java_com_example_nfc_NfcProcessor_identifyTagType")
fun identifyTagType(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    techList: jobjectArray,
    atqa: jbyteArray?,
    sak: jbyte?
): jstring? {
    val jniEnv = env.pointed.pointed!!
    val techCount = jniEnv.GetArrayLength!!(env, techList)
    
    val techs = mutableListOf<String>()
    for (i in 0 until techCount) {
        val tech = jniEnv.GetObjectArrayElement!!(env, techList, i) as jstring
        getStringUTF(env, tech)?.let { techs.add(it) }
    }
    
    // 根据技术列表和特征识别
    val tagType = when {
        techs.contains("android.nfc.tech.MifareClassic") -> "MIFARE Classic"
        techs.contains("android.nfc.tech.MifareUltralight") -> {
            // 进一步区分 Ultralight 类型
            when {
                atqa != null -> {
                    val (atqaPtr, _) = getByteArray(env, atqa)
                    val result = if (atqaPtr != null && atqaPtr[0] == 0x44.toByte()) {
                        "MIFARE Ultralight EV1"
                    } else {
                        "MIFARE Ultralight"
                    }
                    atqaPtr?.let { releaseByteArray(env, atqa, it) }
                    result
                }
                else -> "MIFARE Ultralight"
            }
        }
        techs.contains("android.nfc.tech.NfcA") && sak != null -> {
            when (sak.toInt() and 0xFF) {
                0x00 -> "NTAG"
                0x08 -> "MIFARE Classic 1K"
                0x18 -> "MIFARE Classic 4K"
                0x20 -> "MIFARE DESFire"
                else -> "ISO 14443-3A"
            }
        }
        techs.contains("android.nfc.tech.IsoDep") -> "ISO-DEP (Type 4)"
        else -> "Unknown"
    }
    
    return createJString(env, tagType)
}

// 数据类
data class NdefRecordData(
    val tnf: Int,
    val type: ByteArray,
    val id: ByteArray,
    val payload: ByteArray
)

NFC 标签类型详解

常见标签类型对比

标签类型内存大小安全特性兼容性应用场景
NTAG213144字节密码保护⭐⭐⭐⭐⭐名片、海报、产品标签
NTAG215504字节密码保护、计数器⭐⭐⭐⭐⭐Amiibo、智能卡
NTAG216888字节密码保护、计数器⭐⭐⭐⭐⭐大容量应用
Mifare Classic 1K1KBCrypto-1 加密⭐⭐⭐门禁卡、交通卡
Mifare Classic 4K4KBCrypto-1 加密⭐⭐⭐复杂门禁系统
Mifare Ultralight64字节基础锁定⭐⭐⭐⭐一次性票券
Mifare Ultralight EV1128字节密码+计数器⭐⭐⭐⭐电子票务

Mifare Classic 兼容性

并非所有 Android 设备完全支持 Mifare Classic,部分设备只能读取但无法模拟。

NDEF 消息结构

TNF 类型详解

kotlin
// TNF (Type Name Format) 定义
const val TNF_EMPTY = 0x00         // 空记录
const val TNF_WELL_KNOWN = 0x01    // NFC Forum 定义类型(URI, Text)
const val TNF_MIME_MEDIA = 0x02    // MIME 类型
const val TNF_ABSOLUTE_URI = 0x03  // 绝对 URI
const val TNF_EXTERNAL_TYPE = 0x04 // 自定义外部类型
const val TNF_UNKNOWN = 0x05       // 未知类型
const val TNF_UNCHANGED = 0x06     // 分块记录

记录结构示例

+--------+--------+--------+--------+--------+--------+--------+
| Header | TNF    | Type   | ID     | Payload                 |
|  Flags |        | Length | Length | Length | ... Payload ... |
+--------+--------+--------+--------+--------+--------+--------+

使用示例

Activity 集成

kotlin
// src/main/java/com/example/nfc/NfcActivity.kt
class NfcActivity : AppCompatActivity() {
    private lateinit var nfcManager: NfcManager
    private lateinit var tvTagInfo: TextView
    private lateinit var tvRecords: TextView
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_nfc)
        
        tvTagInfo = findViewById(R.id.tv_tag_info)
        tvRecords = findViewById(R.id.tv_records)
        
        nfcManager = NfcManager(this).apply {
            onTagDiscovered = { tag ->
                runOnUiThread {
                    tvTagInfo.text = "标签 ID: ${tag.id.toHexString()}"
                }
            }
            
            onNdefMessageRead = { records ->
                runOnUiThread {
                    val text = buildString {
                        appendLine("发现 ${records.size} 条记录:\n")
                        records.forEachIndexed { index, record ->
                            appendLine("记录 ${index + 1}:")
                            appendLine("  TNF: ${record.tnf}")
                            appendLine("  类型: ${record.type}")
                            appendLine("  负载: ${record.payload.decodeToString()}")
                            appendLine()
                        }
                    }
                    tvRecords.text = text
                }
            }
        }
        
        // 处理启动 Intent
        handleIntent(intent)
    }
    
    override fun onResume() {
        super.onResume()
        nfcManager.enableForegroundDispatch()
    }
    
    override fun onPause() {
        super.onPause()
        nfcManager.disableForegroundDispatch()
    }
    
    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        handleIntent(intent)
    }
    
    private fun handleIntent(intent: Intent) {
        if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action ||
            NfcAdapter.ACTION_TAG_DISCOVERED == intent.action) {
            nfcManager.handleIntent(intent)
        }
    }
}

// 扩展函数:字节数组转十六进制
fun ByteArray.toHexString(): String =
    joinToString("") { "%02X".format(it) }

实战案例 - 读取交通卡余额

kotlin
class TransportCardReader(private val processor: NfcProcessor) {
    
    /**
     * 读取 Mifare Classic 交通卡余额
     * 注意:不同城市的交通卡数据格式不同
     */
    fun readBalance(tag: Tag): Int? {
        val mifareClassic = MifareClassic.get(tag) ?: return null
        
        try {
            mifareClassic.connect()
            
            // 示例:某些交通卡余额存储在扇区 8
            val sectorIndex = 8
            val blockIndex = mifareClassic.sectorToBlock(sectorIndex)
            
            // 使用默认密钥认证(实际应用需要正确的密钥)
            val keyA = MifareClassic.KEY_DEFAULT
            
            if (!mifareClassic.authenticateSectorWithKeyA(sectorIndex, keyA)) {
                Log.w("NFC", "认证失败")
                return null
            }
            
            // 读取块数据
            val data = mifareClassic.readBlock(blockIndex)
            
            // 解析余额(小端序,单位:分)
            val balance = ((data[3].toInt() and 0xFF) shl 24) or
                         ((data[2].toInt() and 0xFF) shl 16) or
                         ((data[1].toInt() and 0xFF) shl 8) or
                         (data[0].toInt() and 0xFF)
            
            return balance / 100 // 转换为元
            
        } catch (e: Exception) {
            Log.e("NFC", "读取失败", e)
            return null
        } finally {
            mifareClassic.close()
        }
    }
}

最佳实践

前台分发优先级

kotlin
// ✅ 好的做法:使用前台分发
override fun onResume() {
    super.onResume()
    nfcManager.enableForegroundDispatch()
}

override fun onPause() {
    super.onPause()
    nfcManager.disableForegroundDispatch()
}

Intent Filter 配置

xml
<!-- 精确匹配 MIME 类型 -->
<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain" />
</intent-filter>

<!-- 匹配 URI -->
<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:scheme="https" android:host="example.com" />
</intent-filter>

安全注意事项

敏感数据处理

  • 不要在 NDEF 消息中存储明文敏感信息
  • 使用 Native 层加密可提高安全性
  • 及时清除 native 内存中的敏感数据
kotlin
// ✅ 安全的敏感数据处理
fun processSecureData(encryptedData: ByteArray) {
    memScoped {
        val decrypted = nativeHeap.allocArray<ByteVar>(encryptedData.size)
        
        try {
            // 解密操作
            decrypt(encryptedData, decrypted)
            
            // 处理数据
            processData(decrypted)
        } finally {
            // 立即清除内存
            memset(decrypted, 0, encryptedData.size.toULong())
            nativeHeap.free(decrypted)
        }
    }
}

这个完整案例展示了如何使用 Kotlin/Native JNI 实现 Android NFC 读写功能。通过 Native 层处理 NDEF 消息和二进制数据,实现了零拷贝操作和更高的数据安全性,同时提供了完整的标签类型识别和实战应用示例。