用Kotlin在Android上玩转NFC手把手实现一个简易门禁卡信息读取器你是否曾经好奇过每天进出小区或办公室时刷的那张门禁卡里到底藏着什么秘密作为一个Android开发者我们可以用Kotlin和NFC技术轻松揭开这个谜题。本文将带你从零开始构建一个能够读取常见门禁卡信息的实用工具。1. 准备工作理解NFC与门禁卡NFC近场通信技术在现代智能手机中已经相当普及它允许设备在极短距离内通常4厘米以内进行无线数据交换。门禁卡大多采用RFID技术而支持NFC的Android设备可以读取这些卡片的信息。1.1 常见门禁卡类型Mifare Classic最常见的门禁卡类型使用13.56MHz频率Mifare Ultralight更简单的卡片通常用于一次性票证ISO 14443 Type A/B另一种广泛使用的标准提示大多数小区门禁卡使用的是Mifare Classic 1K它有16个扇区每个扇区有4个块共1KB存储空间。2. 项目搭建与权限配置首先创建一个新的Android项目确保你的build.gradle中配置了Kotlin支持plugins { id com.android.application id kotlin-android id kotlin-android-extensions }2.1 AndroidManifest.xml配置uses-permission android:nameandroid.permission.NFC / uses-feature android:nameandroid.hardware.nfc android:requiredtrue / activity android:name.MainActivity intent-filter action android:nameandroid.nfc.action.TECH_DISCOVERED / /intent-filter meta-data android:nameandroid.nfc.action.TECH_DISCOVERED android:resourcexml/nfc_tech_filter / /activity2.2 创建nfc_tech_filter.xml在res/xml目录下创建nfc_tech_filter.xml文件resources tech-list techandroid.nfc.tech.MifareClassic/tech /tech-list tech-list techandroid.nfc.tech.NfcA/tech /tech-list /resources3. 核心NFC读取逻辑实现3.1 初始化NFC适配器在MainActivity中添加以下代码class MainActivity : AppCompatActivity() { private lateinit var nfcAdapter: NfcAdapter private lateinit var textView: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) textView findViewById(R.id.cardInfoTextView) nfcAdapter NfcAdapter.getDefaultAdapter(this) ?: run { Toast.makeText(this, 该设备不支持NFC, Toast.LENGTH_LONG).show() finish() return } } }3.2 处理NFC Intentoverride fun onNewIntent(intent: Intent) { super.onNewIntent(intent) handleNfcIntent(intent) } private fun handleNfcIntent(intent: Intent) { if (NfcAdapter.ACTION_TECH_DISCOVERED intent.action) { val tag intent.getParcelableExtraTag(NfcAdapter.EXTRA_TAG) tag?.let { val mifare MifareClassic.get(it) try { mifare.connect() readCardData(mifare) } catch (e: Exception) { textView.text 读取卡片失败: ${e.message} } finally { mifare.close() } } } }4. 读取并解析门禁卡数据4.1 读取卡片基本信息private fun readCardData(mifare: MifareClassic) { val sb StringBuilder() // 获取卡片类型 val type when(mifare.type) { MifareClassic.TYPE_CLASSIC - Mifare Classic MifareClassic.TYPE_PLUS - Mifare Plus MifareClassic.TYPE_PRO - Mifare Pro else - 未知类型 } sb.append(卡片类型: $type\n) sb.append(扇区数: ${mifare.sectorCount}\n) sb.append(块数: ${mifare.blockCount}\n) // 继续读取具体数据... textView.text sb.toString() }4.2 读取扇区数据for (sector in 0 until mifare.sectorCount) { if (mifare.authenticateSectorWithKeyA(sector, MifareClassic.KEY_DEFAULT)) { val blockIndex mifare.sectorToBlock(sector) val data mifare.readBlock(blockIndex) sb.append(\n扇区 $sector:\n) sb.append(bytesToHex(data)) } else { sb.append(\n扇区 $sector: 认证失败\n) } }4.3 字节数组转十六进制字符串工具函数private fun bytesToHex(bytes: ByteArray): String { val hexChars CharArray(bytes.size * 3) for (i in bytes.indices) { val v bytes[i].toInt() and 0xFF hexChars[i * 3] HEX_ARRAY[v ushr 4] hexChars[i * 3 1] HEX_ARRAY[v and 0x0F] hexChars[i * 3 2] } return String(hexChars) } companion object { private val HEX_ARRAY 0123456789ABCDEF.toCharArray() }5. 优化用户体验5.1 启用前台分发系统override fun onResume() { super.onResume() nfcAdapter.enableForegroundDispatch( this, PendingIntent.getActivity( this, 0, Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0 ), null, arrayOf(arrayOf(android.nfc.tech.MifareClassic)) ) } override fun onPause() { super.onPause() nfcAdapter.disableForegroundDispatch(this) }5.2 添加UI反馈在布局文件中添加一个简单的TextView来显示卡片信息TextView android:idid/cardInfoTextView android:layout_widthmatch_parent android:layout_heightwrap_content android:padding16dp android:textSize14sp android:text请将门禁卡靠近手机背面NFC区域/6. 安全注意事项与进阶方向6.1 安全注意事项不要尝试修改卡片数据这可能导致卡片失效某些卡片可能使用自定义密钥而非默认密钥读取员工卡或门禁卡前请确保获得授权6.2 进阶方向如果你想进一步探索可以考虑卡片克隆检测比较多个卡片的UID和扇区数据数据解析尝试解析特定门禁系统的数据结构写入功能在获得授权的情况下写入测试数据fun writeTestData(mifare: MifareClassic, sector: Int) { if (mifare.authenticateSectorWithKeyA(sector, MifareClassic.KEY_DEFAULT)) { val block mifare.sectorToBlock(sector) val data ByteArray(16) { 0x0F } mifare.writeBlock(block, data) } }7. 实际应用与调试技巧在测试过程中我发现不同品牌手机对NFC的支持有些差异。例如华为手机通常需要将卡片放在摄像头附近而三星手机则可能需要放在手机中部。如果遇到读取不稳定的情况可以尝试调整卡片与手机的接触位置确保手机屏幕已解锁检查是否开启了NFC功能尝试移除手机壳特别是金属材质的对于更复杂的门禁系统可能需要分析多个扇区的数据模式。我曾经遇到过一种情况门禁系统的有效信息存储在扇区1的块1中而其他扇区都是空的或者包含厂商信息。