You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

问题排查与修复方案

核心问题分析

  1. Intent监听范围过窄:原代码仅监听NDEF_DISCOVERED事件,但Mifare DESFire标签未写入NDEF数据时,系统不会触发该事件,需监听通用的TECH_DISCOVERED或TAG_DISCOVERED。
  2. 前台调度逻辑错误:onResume中NFC启用状态的判断写反,导致NFC开启时未启动前台调度,且调度参数为空无法过滤目标标签类型。
  3. Manifest配置缺失:未添加TECH_DISCOVERED对应的Intent过滤和技术规则,系统无法将标签事件分发给应用。
  4. 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>

修复说明

  1. 扩展Intent监听:同时监听三种NFC事件类型,确保无NDEF数据的标签也能被识别。
  2. 修正前台调度:修复NFC启用状态判断逻辑,配置正确的PendingIntent和技术过滤列表,确保应用前台优先接收NFC事件。
  3. 统一UID格式:读取UID和加载CSV时均转为大写,避免大小写差异导致比对失败。
  4. 补充Manifest配置:添加TECH_DISCOVERED的Intent过滤和技术规则文件,让系统正确匹配目标标签。
  5. 优化代码健壮性:使用use语法自动关闭流,添加异常日志便于排查问题。

内容的提问来源于stack exchange,提问作者Kevinevic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 21:05:54