根据 Android 官方文档所写,支持 NFC 的 Android 设备有以下三种操作模式:
- 读卡器模式:读取和写入 NFC 卡片(公交卡充值)
- 仿真卡模式:将设备模拟成一张 NFC 卡片,可以通过其他 NFC 读卡器访问设备模拟的 NFC 卡(饭卡模拟)
- 点对点模式:与 NFC 设备或其他支持非接触式射频传输的设备交换数据(Android Beam)
不同 NFC 卡片之间的差异也很大,有些只支持一次性写入,有些则可以支持读写操作,还有些 NFC 卡片内部支持加密功能。Android 对 NFC 卡片格式的支持主要为 NFC Forum 定义的 NDEF (NFC Data Exchange Format) 标准。
支持 NFC 的 Android 设备在设置内打开了 NFC 功能开关,设备就会在屏幕解锁后,在可以支持的范围内扫描 NFC 卡片。如果发现了一个可识别的 NFC 卡片,会通过 Intent 打开可以处理 NFC 操作的应用。如果设备中有多个可以处理该 NFC 卡片格式的应用,则会弹出选框由用户选择应使用哪个应用。
0x1 读卡器模式
作为一个 NFC 处理应用,第一步先要在 Manifest 中声明自己可以处理 NFC 操作。
1 | <!-- NFC 硬件支持 --> |
根据前文所述,Android 会通过 Intent 打开可以处理 NFC 操作的应用,所以应用也要添加 IntentFilter 来匹配想要过滤的数据类型。
由于对 NDEF 标准的支持最为完善,所以推荐使用 ACTION_NDEF_DISCOVERED;但是 Android 同样也对其他卡片格式有部分支持,可以使用 ACTION_TECH_DISCOVERED,使用此 action 需要在 xml 文件夹下指定 tech-list 来过滤想要使用的 NFC 技术标准;如果前两种 action 都无法匹配,就需要使用到 ACTION_TAG_DISCOVERED,但是此 action 过于笼统,要小心使用。
在 Manifest 中正确声明之后,当扫描到 NFC 卡片时,系统就会打开处理 NFC 操作的 Activity 了:
1 | override fun onCreate(savedInstanceState: Bundle?) { |
需要注意的是,在 onResume() 和 onPause() 中分别调用的 NfcAdapter 的两个方法,会使系统在扫描到新的 NFC 卡片时优先使用当前 Activity 进行处理,不再弹出 Intent 选择,以免降低用户的体验。
1 | override fun onNewIntent(intent: Intent?) { |
在 onNewIntent() 中,即可通过 Intent 来获取卡片的信息了,输出:
1 | I/System.out: TAG: Tech [android.nfc.tech.NfcA, android.nfc.tech.MifareClassic, android.nfc.tech.NdefFormatable] |
此处使用的是 Tag 格式的卡片实体类来获取,如果已经确定卡片是 NDEF 格式,则可以使用 NfcAdapter.EXTRA_NDEF_MESSAGES,来获取一个 NDEF 格式的卡片实体类。
对于非 NDEF 格式的卡片,就需要使用 Android 提供的其他实体类来进行操作,可以使用 Tag::getTechList() 来查看卡片所支持的标准。
0x2 仿真卡模式
Android 上对 NFC 卡片的模拟的主要是通过 HCE(基于主机的卡模拟) 来实现的 (Android 4.4+),如果使用安全元件进行卡片模拟,则需要部分安全元件的支持,例如 NFC-SIM 卡。
1 | <!-- HCE 支持 --> |
1 | <host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android" |
在 Manifest 的 Service 声明中,引用到了一个 xml 文件夹下的 apduservice.xml 文件,其中 android:requireDeviceUnlock=false 可以让设备在亮屏但不解锁的情况下启动 HCE 服务;aid-filter 是必要的,Android 会根据 aid-filter 所定义的 AID 来选择合适的 HCE 服务,具体的 AID 数值参照要模拟的 NFC 卡片为准,须为十六进制格式,并且是偶数位。
1 | class CardService : HostApduService() { |
在 HostApduService::processCommandApdu 中,设备将作为一张模拟的 NFC 卡片,Android 系统会将其接收到的 APDU 数据传入进来,此方法运行在 UI 线程,需要返回一个 ByteArray 作为响应数据发送回 NFC 读取设备。
0x3 点对点模式
NFC 卡片有 ID 卡与 IC 卡之分。传统的 ID 卡片只有数据存储功能,能轻易的被读写,卡片本身只是记录一个 ID 数值;IC 卡则不同,内部具有微型 CPU,数据的读写会经过卡片的 CPU 进行处理,支持加密功能,更加安全。
以笔者的卡片为例,卡片所使用的 IC 为 NXP MF1S50,ISO-14443 标准,数据的读写入需要与卡片进行 APDU(应用协议数据单元) 交互,具体的指令可以从 IC 的 datasheet 中获得。
1 | val isoDep: IsoDep = IsoDep.get(tag) |
这里使用 IsoDep::transceive() 向卡片发送指令,卡片会根据指令返回相应的数据,至于数据如何解析,就要参照不同卡片的技术规范了,可参考《中国金融集成电路 (IC卡) 规范》和《中国银联 IC 卡技术规范》。