Android NFC 实战:从权限配置到地铁卡数据解析
1. Android NFC开发入门:从零开始配置权限
第一次接触Android NFC开发时,我完全被各种技术术语搞晕了。NFC到底是什么?简单来说,它就像手机和卡片之间的"悄悄话"通道,距离必须在4厘米内才能交流。想象一下,你用手机刷公交卡时那种"碰一下"的感觉,就是NFC在工作。
要在Android应用中启用NFC功能,首先得在AndroidManifest.xml文件中进行配置。这里有个坑我踩过:如果你忘记声明NFC权限,应用会直接崩溃。正确的配置应该是这样的:
<uses-permission android:name="android.permission.NFC" /> <uses-feature android:name="android.hardware.nfc" android:required="true" />特别注意第二个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:name="android.nfc.action.TECH_DISCOVERED" /> </intent-filter> <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" />3. 创建NFC技术过滤器
nfc_tech_filter.xml文件就像是NFC的"通行证",告诉系统你的应用能处理哪些类型的NFC标签。这个文件需要放在res/xml目录下。我建议新手开发者一开始就配置支持所有类型,避免漏掉某些特殊卡片:
<resources> <tech-list> <tech>android.nfc.tech.IsoDep</tech> </tech-list> <tech-list> <tech>android.nfc.tech.NfcA</tech> </tech-list> <!-- 其他技术类型... --> </resources>记得我在处理某款门禁卡时,因为没有包含NfcF技术类型,导致应用完全检测不到卡片。后来通过日志排查才发现问题所在,所以建议新手尽量包含所有常见技术类型。
4. 实现NFC数据读取功能
现在来到最核心的部分 - 实际读取NFC卡片数据。以北京地铁卡为例,我们需要在Activity中处理NFC Intent:
private fun processIntent(intent: Intent) { val tag = intent.getParcelableExtra<Tag>(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秒内。
