news 2026/10/15 16:26:45

Android NFC读写实战:从NDEF到MIFARE的避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android NFC读写实战:从NDEF到MIFARE的避坑指南

简介:面向Android NFC初学者的完整读写教程资源包,基于近场通信(NFC)技术,既有工作原理、设备角色、NDEF数据交换格式等基础概念,也系统讲解了android.nfc包中NfcAdapter、Tag、NdefMessage、NdefRecord等核心API的调用方式。资源内附NFCDemo项目,包含Java源码、XML布局与Manifest配置,以及可直接安装的APK安装包,能帮助学习者快速掌握NFC标签的读取、写入和记录解析流程。包体共36个文件,以class、java、xml、png为主要类型,附带的dex、properties等文件可用于工程调试或代码分析,整体压缩包仅72KB,轻量便携。目前已有416人学习下载,适合想入门Android NFC开发或需要参考示例代码的开发者。通过阅读源码与运行Demo,可直观理解NFC读写机制,并进一步扩展自定义NDEF记录、处理MIFARE等不同标签技术。

1. Android NFC 读写到底难在哪:先别急着贴卡

如果你以为 Android NFC 读写就是手机贴上卡、回调里拿一个字符串,那你大概率会在第一批真机测试里翻车。NFC 读写并不是一套统一的读卡 API,它实际要解决三类完全不同的需求:读取一张已经写好内容的 NDEF 标签、向一张空白卡写入业务数据、直接操作 MIFARE 家族的底层扇区。同一个功能页面里,三者的启动路径、鉴权方式和失败表现都不同,不把机制理清就开工,后面全是返工。

这篇文章会把 Android NFC 读写的完整链路拆开讲:先从系统的事件分发机制讲起,再落到 NDEF 读写和 MIFARE 底层读写的最小可运行代码,中间穿插容量、格式、只读这类写卡前必须确认的参数,最后集中讲我踩过的高频坑。适合的人群是正在做巡检打卡、门禁、设备配网、会员卡这类功能的 Android 工程师,或者准备把 NFC 能力加进现有 App 的团队。读完你至少能回答三个问题:为什么读不到、怎么写不进去、写进去之后怎么保证不废。

2. 先摸清事件分发机制:贴卡之后到底谁在响应

2.1 从 Tag 检测到 Activity 回调,中间发生了什么

Android 检测到卡片进入射频场之后,不会直接把事件丢给你写的某个页面。它先和卡完成一轮对话,拿到标签的技术列表(例如 NFC-A、NFC-B、NFC-V)、UID、厂商数据,然后按照固定优先级做分发。这个优先级从高到低是:NDEF_DISCOVERED、TECH_DISCOVERED、TAG_DISCOVERED。如果系统发现这是一张带 NDEF 数据的卡,并且你的 App 注册了匹配的 intent-filter,事件会优先走 NDEF 通道;如果卡没有标准 NDEF 结构,但技术列表能被你的过滤器命中,就走 TECH_DISCOVERED;最后才是兜底的 TAG_DISCOVERED。

这解释了第一个常见困惑:明明贴了卡,你的页面没反应。因为事件被系统标签解析器吃掉了,或者被另一个注册了相同过滤器的应用抢走。在实际项目里,我不推荐只靠清单文件注册 intent-filter 来响应 NFC,原因有二:一是优先级受系统控制,二是 App 不在前端时也会被拉起,体验不可控。更可靠的做法有两种:前台调度(enableForegroundDispatch)和 ReaderMode 回调。两者都需要页面处于前台或可见状态,区别在于前台调度走 Intent 通道,ReaderMode 走同步回调通道。

这里有一个选型标准,我一般这样判断:如果页面只需要“贴一下、读一下、关掉”,用前台调度就够了,代码直观;如果业务涉及连续贴卡、快速读多张卡、或者需要在读卡瞬间同时处理 UI 刷新,我会用 ReaderMode,因为它绕过了 Intent 的构造和派发开销,回调更稳定。两者不要同时开启,否则会出现事件被重复处理或者被互相覆盖的奇怪现象。

拦截方式回调形式推荐场景注意点
清单 intent-filterActivity 被系统拉起外部点击卡片图标启动 App优先级不可控,容易被系统解析器抢占
前台调度onNewIntent页面在前台时贴卡读取需要正确配置 IntentFilter
ReaderModeonTagDiscovered 同步回传连续读卡、业务逻辑复杂的页面需要控制轮询参数,注意功耗

2.2 最小可用的 ReaderMode 读写 Demo

先给一套我常用的最小模板。这个模板解决“贴卡后立刻拿到 Tag 对象”的问题,后续所有读卡写卡逻辑都从这里开始。

class NfcReadActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { private var nfcAdapter: NfcAdapter? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_nfc_read) nfcAdapter = NfcAdapter.getDefaultAdapter(this) if (nfcAdapter == null) { // 设备没有 NFC 硬件,直接提示并退出 return } if (!nfcAdapter!!.isEnabled) { // 系统 NFC 开关未打开,通常做法是引导用户去设置页开启 startActivity(Intent(Settings.ACTION_NFC_SETTINGS)) } } override fun onResume() { super.onResume() // 核心入口:开启 ReaderMode,注册回调 nfcAdapter?.enableReaderMode( this, this, NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_NFC_B or NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS, null ) } override fun onPause() { super.onPause() // 离开页面必须关闭,否则会持续占用射频资源 nfcAdapter?.disableReaderMode(this) } override fun onTagDiscovered(tag: Tag) { // 贴卡之后的第一入口,所有业务逻辑从这里开始 handleTag(tag) } }

这段代码里最需要注意的是 flags 参数。FLAG_READER_NFC_A覆盖了最常见的 MIFARE 系列和 NTAG 系列卡片,FLAG_READER_NFC_B覆盖部分门禁卡和银行卡。如果你的设备只支持其中一种,同时开启两个也不会报错,只是会增加一点点无效轮询。FLAG_READER_NO_PLATFORM_SOUNDS是去掉系统默认的“滴”声提示,适合在产品里自定义提示音的场景;如果不想去掉,这个 flag 不要传。

建议不要轻易加FLAG_READER_SKIP_NDEF_CHECK。这个 flag 的意思是“跳过 NDEF 解析,直接回调 Tag 对象”,加了之后确实更快,但你会失去系统帮你做的基础解析,后续每张卡都要自己判断是不是 NDEF 格式,复杂度会上一个台阶。我的习惯是先用系统解析结果,遇到性能瓶颈再考虑跳过。

2.3 前台调度与清单配置:两种被低估的替换方案

前台调度适合页面在前台时临时抢占 NFC 事件。它的优点是优先级高于系统解析器,缺点是每次都要构造 IntentFilter,代码量略多。

override fun onResume() { super.onResume() val adapter = NfcAdapter.getDefaultAdapter(this) ?: return val intent = Intent(this, javaClass).apply { addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) } val pendingIntent = PendingIntent.getActivity( this, 0, intent, PendingIntent.FLAG_MUTABLE ) val filters = arrayOf( IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED).apply { addDataType("text/plain") }, IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED) ) val techLists = arrayOf( arrayOf("android.nfc.tech.Ndef"), arrayOf("android.nfc.tech.MifareClassic") ) adapter.enableForegroundDispatch(this, pendingIntent, filters, techLists) } override fun onPause() { super.onPause() NfcAdapter.getDefaultAdapter(this)?.disableForegroundDispatch(this) } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) val tag = intent?.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG) ?: return handleTag(tag) }

这段代码里有一个容易踩坑的点:addDataType("text/plain")意味着只有 NDEF 类型为 text/plain 的卡才会触发这个过滤器。实际项目中,卡片上的数据可能是application/json、text/plain、也可能是纯 URI,你必须在过滤器和业务解析之间做两层判断。我建议这里不要图省事只写一个text/plain,最好把业务里所有可能出现的 NDEF 类型都列出来,或者干脆用ACTION_TECH_DISCOVERED搭配技术列表,让所有具备 Ndef 技术的卡都进到你的回调里。

另外一个经验是:这两个机制不要混用。如果 onResume 里同时调用了 enableReaderMode 和 enableForegroundDispatch,某些设备上会出现事件被 ReaderMode 回调处理、但 onNewIntent 也被触发一次的重复现象。代码里做一层互斥判断,或者明确二选一,能省掉不少排查时间。

3. 读卡逻辑做厚:从 NDEF 文本读到自定义 MIME 数据

3.1 解析 NDEF 文本与 URI:不要把格式判断写死

拿到 Tag 之后,最常见的目标是读 NdefMessage。NdefMessage 由一组 NdefRecord 组成,一个卡片上可能有几条记录,比如一条文本加一条 URI。解析时不能只取第一条记录,否则遇到多记录卡片会漏数据。

fun readNdefText(tag: Tag): String? { val ndef = Ndef.get(tag) ?: return null return try { ndef.connect() val message = ndef.ndefMessage ?: return null for (record in message.records) { if (record.tnf == NdefRecord.TNF_WELL_KNOWN && record.type.contentEquals(NdefRecord.RTD_TEXT) ) { return parseTextRecord(record.payload) } } null } catch (e: IOException) { null } finally { try { ndef.close() } catch (e: IOException) { } } }

tnf是 Type Name Format,TNF_WELL_KNOWN表示这是一条预定义类型的记录。type字段则进一步说明记录类型,RTD_TEXT就是文本。这里不要用record.toString()去解析文本,因为那只是对象描述,不是实际内容。文本内容藏在 payload 的前几个字节里,需要手动解析。

fun parseTextRecord(payload: ByteArray): String { if (payload.isEmpty()) return "" // 第一个字节的高位表示编码格式:0 为 UTF-8,1 为 UTF-16 val status = payload[0].toInt() val utf16 = status and 0x80 == 0x80 // 低 6 位表示语言码长度 val languageLength = status and 0x3F val textBytes = payload.copyOfRange(languageLength + 1, payload.size) return if (utf16) { String(textBytes, Charsets.UTF_16) } else { String(textBytes, Charsets.UTF_8) } }

这段解析逻辑在原生的 NDEF 文本格式里是固定的,不需要额外依赖库。需要记住的是语言码长度不是一个定值,常见是 2 字节如en、zh,但有些写卡工具会写 3 字节,所以不能直接跳过固定偏移。

URI 记录的解析比文本简单一点,RTD_URI的 payload 第一个字节是 URI 前缀编码,后面才是地址。Android 提供了一个NdefRecord.createUri(String)用来创建,但读取时没有对应的逆向方法,只能自己拼。不要在生产环境里假设所有 URI 记录都有前缀码,遇到编码为 0x00 时表示无前缀,直接取后续字节即可。

3.2 自定义 MIME 数据:适合把业务数据塞进卡里

文本记录适合给人看的短信息,但业务系统里我更喜欢用自定义 MIME 记录。它的好处是:类型字段可以携带业务标识,比如application/vnd.company.checkin,解析时可以直接通过 type 判断数据用途,不需要猜内容结构。

fun createMimeRecord(mimeType: String, content: ByteArray): NdefRecord { return NdefRecord( NdefRecord.TNF_MIME_MEDIA, mimeType.toByteArray(Charsets.US_ASCII), ByteArray(0), // recordId,一般留空 content ) } fun buildMessage(vararg records: NdefRecord): NdefMessage { return NdefMessage(records) }

读取时对应判断:

fun readMimePayload(tag: Tag, mimeType: String): ByteArray? { val ndef = Ndef.get(tag) ?: return null return try { ndef.connect() val message = ndef.ndefMessage ?: return null val record = message.records.firstOrNull { it.tnf == NdefRecord.TNF_MIME_MEDIA && String(it.type, Charsets.US_ASCII) == mimeType } record?.payload?.copyOf() ?: ByteArray(0) } catch (e: IOException) { null } finally { try { ndef.close() } catch (e: IOException) { } } }

写业务数据时有两个习惯想重点强调。第一,payload 里的数据要带版本号,比如前两个字节定义成0x01判断数据格式版本,避免以后改协议导致旧卡无法解析。第二,不要存明文敏感信息,NFC 卡可以被任何支持 NFC 的手机读出来,卡上只存放业务 ID 或签名,具体明细放服务端查询,这是我能给到的最实在的建议。

3.3 读卡流程的四个必调参数与超时策略

读卡代码不难,难的是在复杂环境里稳定读出来。根据我自己的经验,以下四个参数直接影响成功率。

第一,轮询标志。ReaderMode 下通过 flags 控制监听哪些射频技术。只做普通标签读取时,FLAG_READER_NFC_A or FLAG_READER_NFC_B是稳妥组合。如果业务涉及 ISO 15693 的卡,还要加上FLAG_READER_NFC_V,不加就完全没反应。

第二,存在性检测间隔。enableReaderMode的最后可以传一个 Bundle,里面用NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY控制设备对卡片存在性检测的频率,单位是毫秒。这个值默认由设备决定,我一般在长连接读卡场景下调大到 500ms,减少射频探测对卡片的干扰;调小到 200ms 则适合快速识别卡是否离开。

第三,重复读卡的重试策略。NDEF 连接建立失败不一定是卡坏了,更多是卡在射频场中的位置太偏,或者读写头还没稳定。常见的做法是失败后延迟 100ms 重试,最多三次,每次递增间隔。重试时注意先 close 掉上一次的连接,否则底层驱动会报 “Tag lost” 之类的异常。

第四,超时。Ndef.connect()本身在 Android 上没有公开的超时参数,底层由驱动控制。应用层能控制的是“整体业务超时”,也就是从贴卡到拿到数据必须在多少秒内完成。我一般设置为 3 秒,超过就提示用户重新贴卡。注意别把业务超时设成 100ms 这种激进值,有些卡第一次读取需要额外的时间做加密握手,设短了反而制造不稳定。

4. 写卡不只是 writeNdefMessage:容量、只读与重复写的坑

4.1 MIFARE Classic 的写流程与扇区认证

MIFARE Classic 和 NTAG 这类卡的最大区别在于:它没有现成的 NDEF 抽象层,你得自己管理扇区和块。Classic 1K 有 16 个扇区,每个扇区 4 个块,每块 16 字节,最后一块是扇区尾部,存放密钥 A、访问位、密钥 B。读写数据块之前,必须先对这个扇区做认证。

fun writeMifareBlock(tag: Tag, blockIndex: Int, data: ByteArray): Boolean { val mfc = MifareClassic.get(tag) ?: return false return try { mfc.connect() // 从块号换算扇区号 val sector = mfc.blockToSector(blockIndex) // 出厂默认密钥 A,全 0xFF,共 6 字节 val key = byteArrayOf( 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte() ) // 认证成功后才能写块 if (!mfc.authenticateSectorWithKeyA(sector, key)) { return false } mfc.writeBlock(blockIndex, data) true } catch (e: IOException) { false } finally { try { mfc.close() } catch (e: IOException) { } } }

blockToSector是一个容易用错的方法。它把块号映射到扇区号,例如块 4 落在扇区 1。拿到扇区号之后用authenticateSectorWithKeyA认证。如果卡的密钥被改过,默认 0xFF 的密钥会认证失败,此时要么拿对密钥,要么换技术方案。不要尝试暴力枚举密钥,这个操作会让卡片进入防破解锁定状态。

写块时有一个红线:不要给扇区尾部那个块写入业务数据。扇区尾部的前半部分存访问位,它们和密钥一起决定了该扇区的读写权限。一旦被破坏,这个扇区可能变成不可读也不可写,卡直接报废。我见过有人图省事把整张卡按块顺序从头写到尾,结果没写几张就折腾废一块,这种“翻车”没有后悔药。

4.2 写卡前必须检查的三件事:类型、容量、可写状态

写卡前的检查,顺序不要乱。我见过不少代码先调writeNdefMessage然后捕获异常处理,但更稳的写法是先判断,再写。

fun prepareWrite(tag: Tag): Boolean { // 第一步:确认卡支持什么技术 val ndef = Ndef.get(tag) val formatable = NdefFormatable.get(tag) val mfc = MifareClassic.get(tag) return when { ndef != null -> { // 已格式化为 NDEF 的卡,检查可写状态 ndef.connect() val result = ndef.isWritable && !ndef.isReadOnly ndef.close() result } formatable != null -> { // 空卡,格式化后即可写入 true } mfc != null -> { // MIFARE 卡,底层写块前先检查容量与当前扇区状态 true } else -> false } }

这里有个关键点:Ndef.get(tag)返回 null 不代表卡坏了,只说明这张卡目前不是 NDEF 格式。如果它是一张未初始化的 MIFARE 卡,需要走NdefFormatable先格式化,或者走MifareClassic直接写块。这两种路径写出来的结果完全不同,前者是标准 NDEF 数据,后续别的手机也能读;后者是私有格式,通常只有你自己的代码能读。

容量判断不要拍脑袋。NDEF 技术里有一个运行时属性ndef.maxSize,不同品牌不同型号的卡容量差异很大。常见的小白卡可用空间从几十字节到八百多字节不等,写之前打印一下maxSize和payload.size,超了直接提示,不要等 IOException 抛出来再处理。

4.3 让写入变成可后悔的操作:备份与覆盖策略

NFC 卡的写入不像文件系统,没有回收站,没有版本历史,大多数卡一旦覆盖就是真的覆盖。所以在写卡功能里,我坚持先备份再覆盖。备份策略有两种:把原始 NdefMessage 序列化保存到服务端,或者保存到本地数据库关联业务 ID。

fun backupOriginalMessage(tag: Tag): ByteArray? { val ndef = Ndef.get(tag) ?: return null return try { ndef.connect() val msg = ndef.ndefMessage ?: return null return msg.toByteArray() } catch (e: IOException) { null } finally { try { ndef.close() } catch (e: IOException) { } } }

NdefMessage.toByteArray()是原生提供的方法,存下来的字节数组可以在以后用NdefMessage(ByteArray)重新构造成消息对象。虽然大多数场景下你不会真的拿这份备份去恢复,但遇到写卡中途断电、写了一半、卡状态异常时,这份备份就是唯一后悔药。

覆盖策略上还有一个人为容易犯的错:有些卡写入过程中不能中断,如果用户贴卡位置太偏导致写了一半掉线,卡上的 NDEF 结构可能不完整。我的经验是:写卡时让用户保持手机和卡相对静止,不要把卡挪开;同时把写卡动作设计成“点击按钮后才开始监听”,避免用户无意中连续触发多次写入。

另外一个必须提前和产品对齐的点是只读。Ndef.makeReadOnly()可以让卡变成只读状态,但这是单向操作,一旦执行就改不回来。有些业务场景想用只读卡防止用户篡改,却忽略了一个现实:测试阶段如果依然想改写内容,就只能换新卡。我建议把只读操作放到发布流程里,由专门工具在正式发卡时执行,App 里不要暴露只读入口,这是血泪教训换来的设计。

5. Android NFC 读写避坑指南:翻车最多的 5 个现场

5.1 贴上去没反应:ReaderMode 把系统弹窗挤掉了

现象:打开页面后贴卡,什么反馈都没有;关掉页面再贴,系统自带的通知反而弹出来了。

原因:页面在 onResume 里开启了 ReaderMode,事件被你的回调接收,但你的回调里没有任何 UI 提示,所以看起来像没反应。还有一个可能:同一个页面的 onResume 里同时调用了 enableReaderMode 和 enableForegroundDispatch,事件被两层逻辑处理,干扰了系统判断。

解决:先确认回调有没有被触发,在onTagDiscovered第一行打日志。确认会被触发后,在前端做一个“贴卡中”的界面状态提示,让用户知道系统正在读取。另外,把两种拦截方式中没用的那个彻底注释掉,只保留一种。

5.2 明明贴了卡,技术列表里全是未知技术

现象:读取tag.getTechList(),返回的数组里没有任何Ndef、NdefFormatable、MifareClassic这些常见技术,只有一两个看不懂的底层类型。

原因:这张卡不是市面上常见的 NTAG 或 MIFARE Classic,可能是 ISO 14443-B 的证件卡,或者 ISO 15693 的图书标签。前者多用于门禁和身份证件,后者多用于图书馆、档案管理。它们本身不是坏的,只是驱动层没有把它们抽象成 NDEF 技术。

解决:先判断业务是否需要支持这些卡。如果只做普通标签业务,直接在界面提示“不支持的卡类型”。如果确实需要支持,就按底层技术走,例如对 ISO 15693 使用 NfcV 技术直接发指令。不要试图把每种不认识的卡都强行读到字符串,那不是 Android 原生框架能定义的范畴。

5.3 写卡成功但再读回是空数据

现象:业务日志显示writeNdefMessage没抛异常,紧接着在同一段代码里读ndefMessage,结果返回 null 或者旧数据。

原因:写入后没有重新建立连接。Ndef 对象在连接状态下有自己的数据快照,写入操作后,当前连接读取到的可能仍是写入前的缓存。另外,有些卡在写完 NDEF 后需要重新 connect 才会把新数据暴露给上层。

解决:写入成功后先 close 当前 Ndef 连接,再重新Ndef.get(tag)并 connect,之后读取。如果写和读之间隔了几秒钟,更稳妥的做法是把读取放在下一次贴卡事件里,彻底避开缓存问题。

5.4 直接把 MIFARE 写报废了

现象:用MifareClassic.writeBlock按顺序写了几个块,之后这张卡再贴上去,系统提示未知标签,UID 还能读到,但数据块怎么读都是异常。

原因:写到了扇区尾部,把访问位和密钥区域破坏了。MIFARE Classic 的扇区尾部不是普通数据区,一旦写入错误值,扇区的访问权限会变成不可读也不可写,逻辑上等于报废。另一种情况是直接给未格式化卡调用 NDEF 写入接口失败,又转去底层写块,把已有数据布局搅乱。

解决:只对数据块(每扇区第 0、1、2 块)操作,绝不动第 3 块。要把访问位和密钥区排除在业务数据之外,写之前用blockToSector和块偏移做一次校验,块号对 4 取余等于 3 的直接拒绝。无向卡如果需要变成标准 NDEF,优先使用NdefFormatable.format(),而不是手工按块写。

5.5 makeReadOnly 之后想改内容,改不回来

现象:测试阶段调用了makeReadOnly(),之后想覆盖写新内容,代码报错提示卡只读,换了几张卡都一样。

原因:makeReadOnly()是永久性操作,它会改写卡内部的访问位,之后任何 App 都无法再写入。这不是调试开关,是发卡流程里的终态操作。

解决:把只读操作限制在独立的发卡工具里,不要在业务 App 中开放。如果确实需要支持“改内容”,用可重复写的卡,不设置只读位,而是通过业务逻辑做版本控制。我目前的习惯是:只有确定卡不再需要修改时才置只读,并且执行前二次确认输入“CONFIRM”字样才下发指令。

6. 把 NFC 读写做成产品能力:三个验证习惯

6.1 真机回归不能只测一次

NFC 功能在模拟器上跑不起来,真机测试时最容易被忽略的是设备差异。不同的手机天线位置不一样,有的在背面中间,有的偏上,有的在摄像头附近;贴卡角度稍微偏一点,读卡成功率立刻下滑。我的做法是维护一份真机回归清单:至少覆盖三种不同厂商的 Android 机型,每种机型分别测试正面贴、背面贴、横置贴、快速贴四类操作;同一张卡连续读写 20 次,观察是否有偶发失败。出现失败时把贴卡角度记下来,这往往是天线盲区而不是代码问题。

6.2 把“读不到”变成可排查的日志协议

NFC 的很多问题难以定位,是因为日志里只有“读取失败”四个字。我在项目里会强制记录一组结构化事件:触发方式、tech list、UID、NDEF 是否存在、错误堆栈、耗时。这样当线上反馈读不到时,靠日志就能判断是卡类型不支持、射频信号弱、还是数据格式解析失败。建议至少保留以下信息。

[NFC Event] source=ReaderMode [NFC Tag] techList=[Ndef,MifareClassic] [NFC Tag] uid=04A2B3C4D5E6F7 [NFC Tag] hasNdef=true maxSize=716 [NFC Read] result=success cost=118ms records=1

这套日志格式不需要额外依赖,直接在 logcat 里输出即可。上线后在统计后台里按设备、按卡类型聚合一下,很快就能看出哪些机型读卡成功率偏低,哪些卡类型返回“未知技术”的比例偏高。

6.3 写卡操作要设计成交互明确的动作

我最怕的不是读卡失败,而是用户把写卡操作误触成重复执行。NFC 写卡不像点击按钮,用户可能无意中把另一张卡贴上来,导致新卡被覆盖。现在的做法是:写卡页面设置一个显式的写卡状态,只有用户点击“开始写卡”后才打开 ReaderMode,写完立刻关闭;正在写入时页面显示进度,写入成功后提示用户拿开卡片。不要用一次性“贴卡自动写”的设计,因为同一张卡会因为用户手抖被写两次,数据被自己覆盖,这种事故上线后很难解释。

做过一个门禁模拟项目时,我最大的教训就是把只读入口放在业务 App 里,结果测试阶段有张卡被误置只读,内容改不了,项目进度白等两天。从那以后,所有不可逆操作都被锁在独立工具里,业务代码里只留读和写。NFC 读写这条路上没有玄学,把机制理解透、把参数记录下来、把日志留好,剩下的事情就是反复真机验证。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 16:26:26

Gabor滤波器人脸识别实战:嵌入式低算力场景下的特征提取与优化

1. 为什么在深度学习时代还要聊Gabor滤波器如果你最近几年才接触人脸识别&#xff0c;大概率第一反应是&#xff1a;现在都2025年了&#xff0c;人脸识别不都是ArcFace、CosFace这些基于深度卷积网络的方案吗&#xff1f;拿Gabor滤波器做人脸识别&#xff0c;是不是有点"考…

作者头像 李华
网站建设 2026/10/15 16:26:15

C语言银行系统实战:消息队列实现进程间通信与并发控制

前段时间我动手写了一个C语言的银行系统练手项目&#xff0c;功能清单看着很常规&#xff1a;开户、销户、存钱、取钱、转账、查询、保存用户信息。但标题里真正藏关键的是最后那句——使用消息队列达到两个不同进程之间的相互通信。这句话直接把项目的难度从“文件读写练习”拉…

作者头像 李华
网站建设 2026/10/15 16:24:49

如何 5 分钟上手 Echo Agent:从安装到首次对话的完整新手教程

人工智能AI Agent大模型Agent 记忆工具调用RAGMCP 服务MCP Clients 【免费下载链接】echo-agent 面向长期运行与桌面协作的开源 AI Agent&#xff0c;提供常驻 Runtime、桌面 Agent、长期记忆与持续学习能力&#xff0c;可用于个人助理、自动化任务与本地工作流。 项目地址&…

作者头像 李华
网站建设 2026/10/15 16:23:44

Altera非加密IPCore VHDL源码实战:从结构解析到参数化改造

简介&#xff1a;这份资源面向FPGA与数字逻辑设计的学习者及工程师&#xff0c;提供Altera最新IPCORE的VHDL源代码&#xff0c;且为非加密版本&#xff0c;可直接查看、学习与修改。IPCORE是经过预先设计与验证的功能模块&#xff0c;常用于数字信号处理、接口控制、内存控制等…

作者头像 李华
网站建设 2026/10/15 16:23:20

Android Binder机制深度解析:从驱动原理到实战避坑指南

1. 为什么Binder值得单独拎出来讲做Android系统开发的人&#xff0c;迟早会撞上Binder这堵墙。应用层写久了觉得AIDL就是全部&#xff0c;一旦往下走——系统服务启动流程、跨进程回调、死亡通知、权限校验——你会发现所有线索都指向同一个东西&#xff1a;Binder驱动。它不是…

作者头像 李华