NFC 读写实战
本文展示如何用 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.ktsGradle 配置
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 标签类型详解
常见标签类型对比
| 标签类型 | 内存大小 | 安全特性 | 兼容性 | 应用场景 |
|---|---|---|---|---|
| NTAG213 | 144字节 | 密码保护 | ⭐⭐⭐⭐⭐ | 名片、海报、产品标签 |
| NTAG215 | 504字节 | 密码保护、计数器 | ⭐⭐⭐⭐⭐ | Amiibo、智能卡 |
| NTAG216 | 888字节 | 密码保护、计数器 | ⭐⭐⭐⭐⭐ | 大容量应用 |
| Mifare Classic 1K | 1KB | Crypto-1 加密 | ⭐⭐⭐ | 门禁卡、交通卡 |
| Mifare Classic 4K | 4KB | Crypto-1 加密 | ⭐⭐⭐ | 复杂门禁系统 |
| Mifare Ultralight | 64字节 | 基础锁定 | ⭐⭐⭐⭐ | 一次性票券 |
| Mifare Ultralight EV1 | 128字节 | 密码+计数器 | ⭐⭐⭐⭐ | 电子票务 |
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 消息和二进制数据,实现了零拷贝操作和更高的数据安全性,同时提供了完整的标签类型识别和实战应用示例。