Android NFC 实战:从权限配置到地铁卡数据解析

Android NFC 实战:从权限配置到地铁卡数据解析 1. Android NFC开发入门从零开始配置权限第一次接触Android NFC开发时我完全被各种技术术语搞晕了。NFC到底是什么简单来说它就像手机和卡片之间的悄悄话通道距离必须在4厘米内才能交流。想象一下你用手机刷公交卡时那种碰一下的感觉就是NFC在工作。要在Android应用中启用NFC功能首先得在AndroidManifest.xml文件中进行配置。这里有个坑我踩过如果你忘记声明NFC权限应用会直接崩溃。正确的配置应该是这样的uses-permission android:nameandroid.permission.NFC / uses-feature android:nameandroid.hardware.nfc android:requiredtrue /特别注意第二个uses-feature标签它确保你的应用只会出现在支持NFC的设备上。我在一个项目中曾经漏掉这个配置结果用户在不支持NFC的手机上安装后各种报错教训深刻啊2. 理解NFC标签调度系统NFC标签调度系统就像个尽职的邮递员当手机检测到NFC标签时它会决定哪个应用应该处理这个标签。这个系统定义了三种Intent按优先级排序ACTION_NDEF_DISCOVERED最高优先级处理包含NDEF数据的标签ACTION_TECH_DISCOVERED中等优先级处理已知技术类型的标签ACTION_TAG_DISCOVERED最低优先级作为最后的备选方案实际开发中我发现大多数公交卡、门禁卡都会触发ACTION_TECH_DISCOVERED。为了捕获这些Intent需要在AndroidManifest.xml中这样配置intent-filter action android:nameandroid.nfc.action.TECH_DISCOVERED / /intent-filter meta-data android:nameandroid.nfc.action.TECH_DISCOVERED android:resourcexml/nfc_tech_filter /3. 创建NFC技术过滤器nfc_tech_filter.xml文件就像是NFC的通行证告诉系统你的应用能处理哪些类型的NFC标签。这个文件需要放在res/xml目录下。我建议新手开发者一开始就配置支持所有类型避免漏掉某些特殊卡片resources tech-list techandroid.nfc.tech.IsoDep/tech /tech-list tech-list techandroid.nfc.tech.NfcA/tech /tech-list !-- 其他技术类型... -- /resources记得我在处理某款门禁卡时因为没有包含NfcF技术类型导致应用完全检测不到卡片。后来通过日志排查才发现问题所在所以建议新手尽量包含所有常见技术类型。4. 实现NFC数据读取功能现在来到最核心的部分 - 实际读取NFC卡片数据。以北京地铁卡为例我们需要在Activity中处理NFC Intentprivate fun processIntent(intent: Intent) { val tag intent.getParcelableExtraTag(NfcAdapter.EXTRA_TAG) tag?.let { val cardId it.id.toHexString() binding.tvContent.text 卡片ID: $cardId Toast.makeText(this, 读取到卡片ID: $cardId, Toast.LENGTH_LONG).show() } } fun ByteArray.toHexString() joinToString() { %02x.format(it) }这段代码做了几件事从Intent中获取Tag对象提取卡片的唯一ID字节数组将字节数组转换为16进制字符串显示我在实际测试中发现不同厂家的卡片ID格式可能不同。有些是4字节有些是7字节这个需要根据具体业务需求处理。5. 处理Activity生命周期NFC开发中最容易忽视的就是Activity的生命周期管理。如果没有正确处理可能会导致各种奇怪的问题。这是我的经验之谈override fun onResume() { super.onResume() // 启用前台分发系统 nfcAdapter?.enableForegroundDispatch(this, pendingIntent, null, null) } override fun onPause() { super.onPause() // 禁用前台分发系统 nfcAdapter?.disableForegroundDispatch(this) }特别注意要在onPause中禁用前台分发否则可能会导致资源浪费和潜在的内存泄漏。我在早期项目中就因为这个疏忽导致应用在后台时仍然响应NFC事件消耗了大量电量。6. 解析特定卡片数据北京地铁卡使用的是Mifare Classic技术要读取更多信息需要更深入的操作fun readMifareClassic(tag: Tag) { val mifare MifareClassic.get(tag) try { mifare.connect() // 读取扇区0的数据 val auth mifare.authenticateSectorWithKeyA(0, MifareClassic.KEY_DEFAULT) if (auth) { val blockData mifare.readBlock(0) val dataStr blockData.toHexString() // 处理读取到的数据... } } catch (e: Exception) { Log.e(NFC, 读取卡片失败, e) } finally { mifare.close() } }这里有几个关键点必须先验证扇区密钥才能读取数据不同扇区可能有不同的密钥读取操作可能会抛出异常必须做好错误处理我曾经遇到过因为没处理异常导致应用崩溃的情况特别是在用户快速移开卡片时。所以务必添加try-catch块。7. 适配不同卡片类型实际项目中你可能会遇到各种类型的卡片。以下是一些常见卡片的处理方式ISO-DEP卡片如银行卡val isoDep IsoDep.get(tag) isoDep.connect() val command byteArrayOf(...) // APDU命令 val response isoDep.transceive(command)NFC-A卡片val nfcA NfcA.get(tag) nfcA.connect() val atqa nfcA.atqa // 获取ATQA值NFC-B卡片val nfcB NfcB.get(tag) nfcB.connect() val appData nfcB.applicationData // 获取应用数据每种卡片类型都有其特定的API和操作方式建议在开发前先确定目标卡片的类型和技术规格。8. 调试技巧与常见问题在NFC开发过程中我总结了一些实用的调试技巧获取卡片技术列表val techList tag.techList Log.d(NFC, 支持的技术: ${techList.joinToString()})这会告诉你检测到的卡片支持哪些技术对排查问题很有帮助。处理卡片超时 NFC操作通常都有超时限制我建议添加超时处理nfcA.timeout 3000 // 设置3秒超时多卡片冲突 当同时有多张卡片在感应范围内时可能会读取到错误数据。解决方法是在UI上提示用户一次只放一张卡。低电量模式影响 有些手机在省电模式下会限制NFC功能这点需要告知用户。我在一个商业项目中就遇到过用户反馈NFC时好时坏的问题最后发现是因为他的手机开启了超级省电模式。现在我们的应用会在检测到NFC异常时主动检查这些系统设置。9. 进阶功能实现掌握了基础读取功能后你可能还想实现更复杂的功能写入数据到NFC标签fun writeToTag(tag: Tag, data: String) { val ndef Ndef.get(tag) ndef.connect() val message NdefMessage(NdefRecord.createTextRecord(en, data)) ndef.writeNdefMessage(message) ndef.close() }Android Beam点对点传输nfcAdapter.setNdefPushMessageCallback({ event - NdefMessage(NdefRecord.createTextRecord(en, Hello NFC!)) }, this)卡模拟模式 这个需要手机硬件支持且通常需要系统级权限普通应用很难实现。我在开发一个门禁系统时就实现了通过NFC标签写入用户权限信息的功能。不过要注意不是所有标签都可写有些是只读的。10. 性能优化与最佳实践经过多个NFC项目的磨练我总结出以下优化建议减少连接时间 NFC操作要快尽量在最短时间内完成读写操作。我通常会把业务逻辑处理放到读取完成后进行。缓存技术实例 如果需要多次访问同一标签可以缓存技术实例而不是每次都重新获取。合理处理UI线程 NFC回调可能在非UI线程执行记得用runOnUiThread更新界面。电量优化 在后台时禁用NFC监听只在需要时才启用。错误恢复机制 实现自动重试逻辑处理卡片短暂离开感应区的情况。记得有次做公交卡余额查询功能时因为没有优化读取流程导致用户需要把卡片贴在手机上好几秒才能完成操作体验很差。后来通过优化代码结构将处理时间缩短到了1秒内。