Mifare DESFire NFC标签UID与CSV数据库比对功能异常排查
问题:Android应用无法读取Mifare DESFire NFC标签UID
我的Android应用启动后,贴近Mifare DESFire(ISO14443-4 Type A, IsoDep)NFC标签时无任何反应。该标签仅开放UID读取权限,需求是读取标签UID并与CSV数据库中的数据比对,返回授权/未授权结果。我借助AI编写了代码但始终无法实现功能,请求排查解决。
原代码参考
MainActivity.kt
package com.example.simplicims import android.nfc.NfcAdapter import android.Manifest import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.nfc.Tag import android.nfc.tech.NfcA import android.os.Bundle import android.util.Log import android.widget.Button import android.widget.TextView import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import java.io.BufferedReader import java.io.File import java.io.FileInputStream import java.io.FileReader import java.io.IOException import java.io.InputStream import org.apache.poi.ss.usermodel.WorkbookFactory class MainActivity : Activity() { private lateinit var nfcAdapter: NfcAdapter private lateinit var uidTextView: TextView private lateinit var statusTextView: TextView private lateinit var scanButton: Button private val validUids = mutableListOf<String>() // List to store valid UIDs from CSV override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) uidTextView = findViewById(R.id.uidTextView) statusTextView = findViewById(R.id.statusTextView) scanButton = findViewById(R.id.scanButton) nfcAdapter = NfcAdapter.getDefaultAdapter(this) // Check for permissions and load CSV file checkPermissions() scanButton.setOnClickListener { // Trigger NFC scan manually if needed // Optionally, start an activity for NFC scan manually } } override fun onResume() { super.onResume() val nfcAdapter = NfcAdapter.getDefaultAdapter(this) if (NfcAdapter.getDefaultAdapter(this) == null) { statusTextView.text = "NFC non dispo" return } else { if (nfcAdapter.isEnabled) { Log.e("NFC", "NFC is disabled") } else { nfcAdapter.enableForegroundDispatch(this, null, null, null) } } } override fun onPause() { super.onPause() nfcAdapter.disableForegroundDispatch(this) } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent?.action) { val tag: Tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)!! val uid = tag.id.joinToString("") { it.toString(16).padStart(2, '0') } uidTextView.text = "UID: $uid" checkUidInDatabase(uid) } } private fun loadCsvDatabase() { try { val csvFile = File("/storage/emulated/0/Download/database.csv") // CSV file path if (!csvFile.exists()) { statusTextView.text = "CSV file not found" return } val reader = BufferedReader(FileReader(csvFile)) var line: String? while (reader.readLine().also { line = it } != null) { // Assuming each line contains a single UID val uid = line?.trim() // Remove any unnecessary whitespace if (!uid.isNullOrEmpty()) { validUids.add(uid) } } reader.close() } catch (e: IOException) { statusTextView.text = "Error reading CSV file" } } private fun checkUidInDatabase(uid: String) { if (validUids.contains(uid)) { statusTextView.text = "Entrée autorisée" statusTextView.setTextColor(getColor(android.R.color.holo_green_dark)) } else { statusTextView.text = "Entrée non autorisée - voir CSB" statusTextView.setTextColor(getColor(android.R.color.holo_red_dark)) } } // Check for permission to read external storage private fun checkPermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 1) } else { // If permission is granted, load the CSV loadCsvDatabase() } } // Handle the result of permission request override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == 1) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Permission granted, load the CSV loadCsvDatabase() } else { // Permission denied, handle accordingly statusTextView.text = "Permission denied to read external storage" } } } }
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.simplicims"> <uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-feature android:name="android.hardware.nfc" android:required="true" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="simpliCIMS" android:theme="@style/Theme.SimpliCIMS"> <activity android:name=".MainActivity" android:label="simpliCIMS" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <meta-data android:name="android.nfc.action.NDEF_DISCOVERED" android:value="text/plain" /> </activity> </application> </manifest>
问题排查与修复方案
核心问题分析
- Intent监听范围过窄:原代码仅监听
NDEF_DISCOVERED事件,但Mifare DESFire标签未写入NDEF数据时,系统不会触发该事件,需监听通用的TECH_DISCOVERED或TAG_DISCOVERED。 - 前台调度逻辑错误:
onResume中NFC启用状态的判断写反,导致NFC开启时未启动前台调度,且调度参数为空无法过滤目标标签类型。 - Manifest配置缺失:未添加
TECH_DISCOVERED对应的Intent过滤和技术规则,系统无法将标签事件分发给应用。 - UID大小写不统一:代码生成的UID为小写,若CSV中为大写会导致比对失败。
修复后代码
修改后的MainActivity.kt
package com.example.simplicims import android.Manifest import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.nfc.NfcAdapter import android.nfc.Tag import android.nfc.tech.IsoDep import android.nfc.tech.NfcA import android.os.Bundle import android.util.Log import android.widget.Button import android.widget.TextView import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import java.io.BufferedReader import java.io.File import java.io.FileReader import java.io.IOException class MainActivity : Activity() { private lateinit var nfcAdapter: NfcAdapter private lateinit var uidTextView: TextView private lateinit var statusTextView: TextView private lateinit var scanButton: Button private val validUids = mutableListOf<String>() // 存储CSV中的有效UID override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) uidTextView = findViewById(R.id.uidTextView) statusTextView = findViewById(R.id.statusTextView) scanButton = findViewById(R.id.scanButton) nfcAdapter = NfcAdapter.getDefaultAdapter(this) // 检查权限并加载CSV checkPermissions() scanButton.setOnClickListener { statusTextView.text = "请贴近NFC标签" } } override fun onResume() { super.onResume() if (nfcAdapter == null) { statusTextView.text = "设备不支持NFC" return } if (!nfcAdapter.isEnabled) { statusTextView.text = "请开启NFC功能" Log.e("NFC", "NFC is disabled") return } // 配置前台调度,监听目标NFC技术类型 val techLists = arrayOf( arrayOf(NfcA::class.java.name), arrayOf(IsoDep::class.java.name) ) val intent = Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) val pendingIntent = android.app.PendingIntent.getActivity(this, 0, intent, android.app.PendingIntent.FLAG_MUTABLE) nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, techLists) } override fun onPause() { super.onPause() try { nfcAdapter.disableForegroundDispatch(this) } catch (e: Exception) { Log.e("NFC", "关闭前台调度失败", e) } } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) // 处理所有NFC标签发现事件 when (intent?.action) { NfcAdapter.ACTION_TECH_DISCOVERED, NfcAdapter.ACTION_TAG_DISCOVERED, NfcAdapter.ACTION_NDEF_DISCOVERED -> { val tag: Tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) ?: return // UID统一转为大写,避免大小写不匹配 val uid = tag.id.joinToString("") { it.toString(16).padStart(2, '0').uppercase() } uidTextView.text = "UID: $uid" checkUidInDatabase(uid) } } } private fun loadCsvDatabase() { try { val csvFile = File("/storage/emulated/0/Download/database.csv") if (!csvFile.exists()) { statusTextView.text = "未找到CSV数据库文件" return } BufferedReader(FileReader(csvFile)).use { reader -> var line: String? while (reader.readLine().also { line = it } != null) { val uid = line?.trim()?.uppercase() // CSV中UID统一转为大写 if (!uid.isNullOrEmpty()) { validUids.add(uid) } } } statusTextView.text = "数据库加载完成,共${validUids.size}个有效UID" } catch (e: IOException) { statusTextView.text = "读取CSV文件出错" Log.e("CSV", "读取失败", e) } } private fun checkUidInDatabase(uid: String) { if (validUids.contains(uid)) { statusTextView.text = "授权通过" statusTextView.setTextColor(getColor(android.R.color.holo_green_dark)) } else { statusTextView.text = "未授权,请检查数据库" statusTextView.setTextColor(getColor(android.R.color.holo_red_dark)) } } private fun checkPermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 1) } else { loadCsvDatabase() } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == 1) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { loadCsvDatabase() } else { statusTextView.text = "读取存储权限被拒绝,无法加载数据库" } } } }
修改后的AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.simplicims"> <uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-feature android:name="android.hardware.nfc" android:required="true" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="simpliCIMS" android:theme="@style/Theme.SimpliCIMS"> <activity android:name=".MainActivity" android:label="simpliCIMS" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <!-- 添加TECH_DISCOVERED事件监听 --> <intent-filter> <action android:name="android.nfc.action.TECH_DISCOVERED" /> </intent-filter> <!-- 指定支持的NFC技术类型 --> <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" /> </activity> </application> </manifest>
新增res/xml/nfc_tech_filter.xml
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:android="http://schemas.android.com/apk/res/android"> <tech-list> <tech>android.nfc.tech.NfcA</tech> <tech>android.nfc.tech.IsoDep</tech> </tech-list> </resources>
修复说明
- 扩展Intent监听:同时监听三种NFC事件类型,确保无NDEF数据的标签也能被识别。
- 修正前台调度:修复NFC启用状态判断逻辑,配置正确的PendingIntent和技术过滤列表,确保应用前台优先接收NFC事件。
- 统一UID格式:读取UID和加载CSV时均转为大写,避免大小写差异导致比对失败。
- 补充Manifest配置:添加
TECH_DISCOVERED的Intent过滤和技术规则文件,让系统正确匹配目标标签。 - 优化代码健壮性:使用
use语法自动关闭流,添加异常日志便于排查问题。
内容的提问来源于stack exchange,提问作者Kevinevic
相关产品推荐
相关产品推荐

