Android通过APDU读取NFC Type A卡NDEF文本数据异常排查
Android NFC Type A卡NDEF文本读取异常问题
我正在开发一款Android应用,需通过基于Android的POS系统读取NFC Type A卡中的NDEF文本消息,该系统要求使用PICC和APDU命令,因此无法使用Android默认NDEF库。我用Google Play商店的NFC Tools向标签写入了“123456789”,但自行编写的代码读出的却是“1234567123456789123456789”。希望有人能指出问题所在,感谢!
代码
private fun extractNdef(tag: Tag): String? { val nfcA = NfcA.get(tag) ?: return null nfcA.use { nfcA.connect() // Read blocks 4-8 val allData = StringBuilder() for (block in 4..8) { val response = try { val rawResponse = nfcA.transceive(byteArrayOf(0x30.toByte(), block.toByte())) rawResponse.joinToString("", { "%02x".format(it) }) } catch (e: IOException) { Log.e("NFC", "Failed to read block $block", e) return null } allData.append(response) Log.d("NFC", "Block $block Data: $response") } val ndefData = allData.toString() Log.d("NFC", "Combined NDEF Data: $ndefData") return parseNdefData(ndefData) } } fun parseNdefData(hexData: String): String { // Convert hex data to bytes val bytes = hexData.chunked(2).map { it.toInt(16).toByte() }.toByteArray() // Search for the text record identifier and language code prefix val textRecordType = 0x54.toByte() // "T" for text records in NDEF val langCodeLength = 3 // Length of 'T' + length of "en" (2 bytes) // Locate and decode the text content val contentBuilder = StringBuilder() for (i in bytes.indices) { if (bytes[i] == textRecordType && i + langCodeLength < bytes.size) { // Start extracting bytes after 'T' and "en" prefix (language code) val textContentBytes = bytes.drop(i + langCodeLength + 1) .takeWhile { it != 0xFE.toByte() && it != 0x00.toByte() } // Stop at `FE` or padding `00` // Convert to string and filter only ASCII digits contentBuilder.append( textContentBytes .toByteArray() .toString(Charsets.UTF_8) .filter { it.isDigit() } ) } } return contentBuilder.toString() }
日志
2024-10-29 12:27:25.226 27321-27339 NFC com.passentry.nfc_reader_libraries D Block 4 Data: 0310d1010c5402656e31323334353637 2024-10-29 12:27:25.238 27321-27339 NFC com.passentry.nfc_reader_libraries D Block 5 Data: 0c5402656e313233343536373839fe00 2024-10-29 12:27:25.253 27321-27339 NFC com.passentry.nfc_reader_libraries D Block 6 Data: 6e313233343536373839fe0000000000 2024-10-29 12:27:25.266 27321-27339 NFC com.passentry.nfc_reader_libraries D Block 7 Data: 343536373839fe000000000000000000 2024-10-29 12:27:25.284 27321-27339 NFC com.passentry.nfc_reader_libraries D Block 8 Data: 3839fe00000000000000000000000000 2024-10-29 12:27:25.285 27321-27339 NFC com.passentry.nfc_reader_libraries D Combined NDEF Data: 0310d1010c5402656e313233343536370c5402656e313233343536373839fe006e313233343536373839fe0000000000343536373839fe0000000000000000003839fe00000000000000000000000000 2024-10-29 12:27:25.349 27321-27339 MainActivity com.passentry.nfc_reader_libraries D Pass ID: 1234567123456789123456789
NFC标签截图
- NFC Tools截图1
- NFC Tools截图2
问题分析与修复方案
核心问题
- 读取了超出NDEF消息范围的块:MIFARE Classic的NDEF数据遵循TLV结构,块4的前两个字节
0310表示NDEF消息标签(03)和长度(10即16字节)。你的代码读取了块4到8,包含了大量标签内的残留/填充数据,这些数据里有重复的文本记录片段。 - 解析逻辑错误:
- 硬编码
langCodeLength=3完全错误,语言码长度是NDEF文本记录有效载荷的第一个字节(比如日志里的02,对应2字节的en),不是固定值。 - 循环遍历所有
0x54('T')的位置,导致把残留数据里的重复片段多次提取拼接,最终得到重复的文本内容。
- 硬编码
修复代码
1. 修正NDEF数据读取逻辑(只读取必要的块)
private fun extractNdef(tag: Tag): String? { val nfcA = NfcA.get(tag) ?: return null nfcA.use { nfcA.connect() // 先读取块4获取NDEF消息长度 val block4 = try { nfcA.transceive(byteArrayOf(0x30.toByte(), 4.toByte())) } catch (e: IOException) { Log.e("NFC", "Failed to read block 4", e) return null } // 解析TLV结构:Tag(0x03) + Length if (block4[0] != 0x03.toByte()) { Log.e("NFC", "Not an NDEF tag") return null } val ndefLength = block4[1].toInt() and 0xFF Log.d("NFC", "NDEF Message Length: $ndefLength") // 计算需要读取的块数:块4开始,每个块16字节,前2字节是TLV头部 val totalBytesNeeded = 2 + ndefLength // TLV头部2字节 + NDEF消息长度 val blocksToRead = (totalBytesNeeded + 15) / 16 // 向上取整 val allData = StringBuilder() allData.append(block4.joinToString("", { "%02x".format(it) })) // 读取剩余的块(如果需要) for (block in 5 until 4 + blocksToRead) { val response = try { val rawResponse = nfcA.transceive(byteArrayOf(0x30.toByte(), block.toByte())) rawResponse.joinToString("", { "%02x".format(it) }) } catch (e: IOException) { Log.e("NFC", "Failed to read block $block", e) return null } allData.append(response) Log.d("NFC", "Block $block Data: $response") } // 截取有效NDEF数据(去掉TLV头部后的ndefLength字节) val ndefHex = allData.substring(4, 4 + ndefLength * 2) // 前4个字符是0310(2字节) Log.d("NFC", "Filtered NDEF Data: $ndefHex") return parseNdefData(ndefHex) } }
2. 修正NDEF文本解析逻辑
fun parseNdefData(hexData: String): String { val bytes = hexData.chunked(2).map { it.toInt(16).toByte() }.toByteArray() if (bytes.isEmpty()) return "" // 解析第一个NDEF记录(单记录场景) val recordHeader = bytes[0] val isShortRecord = (recordHeader and 0x10.toByte()) != 0.toByte() val typeLength = bytes[1].toInt() and 0xFF val payloadLength = if (isShortRecord) { bytes[2].toInt() and 0xFF } else { // 长记录场景,这里简化处理单记录短场景 (bytes[2].toInt() and 0xFF shl 24) or (bytes[3].toInt() and 0xFF shl 16) or (bytes[4].toInt() and 0xFF shl 8) or (bytes[5].toInt() and 0xFF) } // 找到文本记录类型('T') val typeStart = if (isShortRecord) 3 else 6 if (typeLength != 1 || bytes[typeStart] != 0x54.toByte()) { Log.e("NFC", "Not an NDEF text record") return "" } // 解析文本有效载荷 val payloadStart = typeStart + typeLength val langCodeLength = bytes[payloadStart].toInt() and 0xFF val textStart = payloadStart + 1 + langCodeLength val textBytes = bytes.copyOfRange(textStart, payloadStart + payloadLength) // 转换为字符串并过滤数字 return textBytes.toString(Charsets.UTF_8).filter { it.isDigit() } }
修复说明
- 读取逻辑:先解析块4的TLV头部获取NDEF消息长度,只读取必要的块,避免引入残留数据。
- 解析逻辑:严格按照NDEF记录结构解析,只处理第一个(也是唯一的)文本记录,正确读取语言码长度,避免重复提取。
内容的提问来源于stack exchange,提问作者Tom Rowbotham
相关产品推荐
相关产品推荐

