搞懂Nfc刷卡看这篇就够了

1,769 阅读12分钟

NFC在我们生活中出现的场景越来越多了,如蓝牙耳机的连接、刷交通卡、智能锁开锁等,相信在未来还会有越来越多的场景会用到NFC,所以作为开发者,掌握NFC的知识及NFC的开发技能、就显得尤为必要

这里放出Android官方文档,官方的文档讲的大而全,本文是对官方文档的抽丝剥茧,相对官方文档来说会更容易理解,但是会比官方文档少一些内容,如果本文没有你想了解的,可以自己查阅官方文档。

什么是NFC

NFC是Near Field Communication缩写,即近距离无线通讯技术。由飞利浦公司和索尼公司共同开发的NFC是一种非接触式识别和互联技术,可以在移动设备、消费类电子产品、PC 和智能控件工具间进行近距离无线通信。

简单的说,NFC提供了一种简单、触控式的解决方案,可以让消费者简单直观地交换信息、访问内容与服务。

NFC的工作模式

NFC的工作模式有三种,分别是读卡器模式(Reader/writer mode)、仿真卡模式(Card Emulation Mode)、点对点模式(P2P mode)。

  • 读卡器模式

    数据在NFC芯片中,可以简单理解成“刷标签”。本质上就是通过支持NFC的手机或其它电子设备从带有NFC芯片的标签、贴纸、名片等媒介中读写信息。通常NFC标签是不需要外部供电的。当支持NFC的外设向NFC读写数据时,它会发送某种磁场,而这个磁场会自动的向NFC标签供电。

  • 仿真卡模式

    数据在支持NFC的手机或其它电子设备中,可以简单理解成“刷手机”。本质上就是将支持NFC的手机或其它电子设备当成借记卡、公交卡、门禁卡等IC卡使用。基本原理是将相应IC卡中的信息凭证封装成数据包存储在支持NFC的外设中 。

    在使用时还需要一个NFC射频器(相当于刷卡器)。将手机靠近NFC射频器,手机就会接收到NFC射频器发过来的信号,在通过一系列复杂的验证后,将IC卡的相应信息传入NFC射频器,最后这些IC卡数据会传入NFC射频器连接的电脑,并进行相应的处理(如电子转帐、开门等操作)。

  • 点对点模式

    该模式与蓝牙、红外差不多,用于不同NFC设备之间进行数据交换,不过这个模式已经没有有“刷”的感觉了。其有效距离一般不能超过4厘米,但传输建立速度要比红外和蓝牙技术快很多,传输速度比红外块得多,如过双方都使用Android4.2,NFC会直接利用蓝牙传输。这种技术被称为Android Beam。所以使用Android Beam传输数据的两部设备不再限于4厘米之内。

    点对点模式的典型应用是两部支持NFC的手机或平板电脑实现数据的点对点传输,例如,交换图片或同步设备联系人。因此,通过NFC,多个设备如数字相机,计算机,手机之间,都可以快速连接,并交换资料或者服务。

本文会主要讲解读卡器模式, 相信理解了这个模式,其他的两种模式你也会触类旁通的。在讲解读卡器模式前,有一些知识是需要提前掌握的。

卡片的知识

  本文主要讲解的是NFC的读卡器模式,要想读到卡片的内容,我们要对卡片有一些基本的了解,比如卡片的分类,每种卡片内部的数据结构等。

卡片的分类

  现在市面上的卡片分类有IC卡、ID卡、M1卡和CPU卡,简单的了解一下这些卡的区别和用途,

  • IC卡

    IC卡又称集成电路卡,通常是在塑料卡片内嵌入一个或多个集成电路构成的PVC卡。集成电路芯片可以是存储器或微处理器。带有存储器的IC卡又称为记忆卡或存储卡,带有微处理器的IC卡又称为智能卡或智慧卡。记忆卡可以存储大量信息;智能卡则不仅具有记忆能力,而且还具有处理信息的功能。

    IC卡可以十分方便地存汽车费、电话费、地铁乘车费、食堂就餐费、公路付费以及购物旅游、贸易服务等。

  • ID卡

    ID卡又叫身份识别卡,是一种不可写入的感应式卡,拥有一个固定卡号编号。卡号在封卡前写入后不可再更改,绝对确保卡号的唯一性和安全性。

    ID卡可以作为一般的门禁或停车场系统的使用者身份识别,因ID卡无密钥安全认证机制,且不能写卡,很难实现一卡通功能,同时也不合适做消费系统。

  • M1卡

    M1是菲利浦下属子公司恩智浦出品的芯片缩写,目前该公司的M1芯片与国产芯片相兼容,其实M1卡也属于非接触式IC卡

    M1卡,优点是可读可写的多功能卡,缺点是:价格稍贵,感应距离短,适合非定额消费系统、停车场系统、门禁考勤系统等。

  • CPU卡

    CPU卡芯片是一个微处理器,它的功能相当于一台微型计算机。CPU卡可适用于金融、保险、交警、政府行业等多个领域,CPU卡的优点是存储空间大、读取速度快、支持一卡多用功能等特点,CPU卡从外型上与普通IC卡,射频卡并没有太大差异,但是性能上却有巨大提升,安全性和普通IC卡比,提高很多,通常CPU卡内含有随机数发生器,硬件DES,3DES加密算法等,配合CPU卡芯片上的COS操作系统,可以达到金融级的安全级别。

M1卡的数据结构

  为什么要介绍卡结构呢?因为用NFC读取卡片,获取卡片的内容的时候,你要知道卡片的数据结构,才能拿到自己想要的知识,这里就以M1卡进行讲解。

  M1卡有从0到15共16个扇区,每个扇区配备了从0到3共4个段,每个段可以保存16字节的内容。见下图

扇区0和扇区1

要想读取对应扇区的数据,需要知道对应扇区的秘钥,否则读取不到数据。

Android的NFC标签调度系统

  当手机发现外部NFC的标签(指含有NFC功能的设备)时,Android系统会寻找可以处理这个标签的Activity,那怎么知道哪个Activity能处理这条NFC消息呢?答案是清单文件,我们需要在清单文件中设置intent-filter。系统会分发NFC消息到设置intent-filter的Activity中,当然,接收NFC消息也有优先级之分,也是通过设置intent-filter来设置接收NFC消息的优先级的。

  NFC的标签调度系统绑定了3中intent,按优先级的高低列出,如下

  1. ACTION_NDEF_DISCOVERED:如果扫描到包含此Intent的Activity,并且可识别其类型,则使用此 Intent 启动 Activity。这是优先级最高的 Intent,NFC标签调度系统会尽可能尝试使用此 Intent 启动 Activity,在找不到这个Intent时才会尝试使用其他 Intent。
  2. ACTION_TECH_DISCOVERED:如果没有登记要处理 ACTION_NDEF_DISCOVERED Intent 的 Activity,则标签调度系统会尝试使用此 Intent 来启动应用。此外,如果扫描到的标签包含无法映射到 MIME 类型或 URI 的 NDEF 数据,或者该标签不包含 NDEF 数据,但它使用了已知的标签技术,那么也会直接启动此 Intent(无需先启动 ACTION_NDEF_DISCOVERED)。
  3. ACTION_TAG_DISCOVERED:如果没有处理 ACTION_NDEF_DISCOVERED 或者 ACTION_TECH_DISCOVERED Intent 的 Activity,则使用此 Intent 启动 Activity。

它们3者关系如下图所示

NFC-TAG

如果想处理所有的NFC标签,上面3个可以在清单文件中都进行设置。

实战演练

  前文我们已经知道了什么是NFC以及NFC的几种工作模式,也了解了市面上卡片的分类和M1卡的数据结构,基础知识已经掌握了,下面就开始进入实战演练,用Android的NFC来获取M1卡的唯一代码。

在 Android 清单中请求 NFC 访问权限

先在 AndroidManifest.xml 文件中声明以下内容,然后才能访问设备的 NFC 硬件并正确处理 NFC Intent:

  • 设置用于访问 NFC 硬件的 NFC <uses-permission>
    <uses-permission android:name="android.permission.NFC" />
  • 设置uses-feature 元素,以便您的应用仅在那些具备 NFC 硬件的设备的 Google Play 中显示

        <uses-feature android:name="android.hardware.nfc" android:required="true" />
    

    如果你的App不是必须要NFC功能,则uses-feature可以省略,然后再代码中通过getDefaultAdapter() 是否为 null 来判断 NFC 的可用性。

设置过滤 NFC Intent

以下示例展示了如何过滤 MIME 类型为 text/plainACTION_NDEF_DISCOVERED Intent:

    <intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain" />
    </intent-filter>

另外两种intent-filter的使用,可以查阅官方文档

使用前台调度系统

前台调度系统的作用就是,在你打开一个可以处理NFC标签的Activity时,NFC的消息会优先发送给当前的Activity,不论当前的Activity设置的是哪一个intent-filter。使用前台调度系统的步骤如下

  1. 在 Activity 的 onCreate() 方法中添加以下代码:

    a. 创建一个 PendingIntent 对象。

        val intent = Intent(this, javaClass).apply {
            addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
        }
        var pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
        
    

    b.声明 Intent 过滤器,用来处理您要拦截的 Intent。前台调度系统会对照设备扫描标签时所获得的 Intent 来检查所指定的 Intent 过滤器。如果匹配,那么应用会处理该 Intent。如果不匹配,那么前台调度系统会回退到 Intent 调度系统。指定 Intent 过滤器和技术过滤器的 null 数组,以指明要过滤所有回退到 TAG_DISCOVERED Intent 的标签。以下代码段会处理 NDEF_DISCOVERED 的所有 MIME 类型。

        val ndef = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED).apply {
            try {
                addDataType("*/*")    /* 处理所有 MIME 类型的标签 */
            } catch (e: IntentFilter.MalformedMimeTypeException) {
                throw RuntimeException("fail", e)
            }
        }
    
        intentFiltersArray = arrayOf(ndef)
        
    

    c.设置应用要处理的一组标签技术。调用 Object.class.getName() 方法以获取要支持的技术的类。

        techListsArray = arrayOf(arrayOf<String>(NfcF::class.java.name))
    
  2. 替换以下 Activity 生命周期回调,并添加相应逻辑,以分别在 Activity 失去 (onPause()) 焦点和重新获得 (onResume()) 焦点时启用和停用前台调度。enableForegroundDispatch() 必须从主线程调用,并且只能在 Activity 在前台运行时调用(在 onResume() 中调用可确保这一点)。您还需要实现 onNewIntent 回调以处理扫描到的 NFC 标签中的数据。

        public override fun onPause() {
            super.onPause()
            adapter.disableForegroundDispatch(this)
        }
    
        public override fun onResume() {
            super.onResume()
            adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray)
        }
    
        public override fun onNewIntent(intent: Intent) {
            val tagFromIntent: Tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
            //在这里处理NFC扫描到的内容
        }
        
    

代码示例

  下面展示核心代码,读取M1卡并解析卡号,代码如下

//解析实体卡号
fun resolveCardNumIntent(intent: Intent?): String {
    if (intent == null) {
        return ""
    }
    //拿来装读取出来的数据,key代表扇区数,后面list存放四个块的内容
    //intent就是onNewIntent方法返回的那个intent
    val tag: Tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
    val mfc = MifareClassic.get(tag)
    //如果当前IC卡不是这个格式的mfc就会为空
    if (null != mfc) {
        try {
            if (!mfc.isConnected) {
                //链接NFC
                mfc.connect()
            }
            //验证扇区密码,否则会报错(链接失败错误)
            val isOpen = mfc.authenticateSectorWithKeyA(0 , CARD_KEY_15)
            if (isOpen) {
                //获取扇区第一个块对应芯片存储器的位置
                //(我是这样理解的,因为第0扇区的这个值是4而不是0)
                val bIndex = mfc.sectorToBlock(0)
                val data = mfc.readBlock(bIndex)
                val byteArrToInt = DataUtils.byteArrToInt(data, 0, 4)
//                LogUtils.print("卡号: $byteArrToInt")
                return byteArrToInt
            }
        } catch (e: Exception) {
            e.printStackTrace()
            return ""
        } finally {
            try {
                mfc.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }
    return ""
}

上面的代码注释很清晰,重点看下这段代码

val tag: Tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
val mfc = MifareClassic.get(tag)//用于解析MifareClassic的实例

这里的Tag是获取卡片的Tag,NFC支持的Tag如下表

表 1. 支持的标签技术

说明
TagTechnology这是所有标签技术类都必须实现的接口。
NfcA提供对 NFC-A (ISO 14443-3A) 属性和 I/O 操作的访问权限。
NfcB提供对 NFC-B (ISO 14443-3B) 属性和 I/O 操作的访问权限。
NfcF提供对 NFC-F (JIS 6319-4) 属性和 I/O 操作的访问权限。
NfcV提供对 NFC-V (ISO 15693) 属性和 I/O 操作的访问权限。
IsoDep提供对 ISO-DEP (ISO 14443-4) 属性和 I/O 操作的访问权限。
Ndef提供对 NDEF 格式的 NFC 标签上的 NDEF 数据和操作的访问权限。
NdefFormatable为可设置为 NDEF 格式的标签提供格式化操作。

Android 设备还可以选择支持以下标签技术。

表 2. 可选择支持的标签技术

说明
MifareClassic提供对 MIFARE Classic 属性和 I/O 操作的访问权限(如果此 Android 设备支持 MIFARE)。
MifareUltralight提供对 MIFARE Ultralight 属性和 I/O 操作的访问权限(如果此 Android 设备支持 MIFARE)。

总结

  本文首先介绍了NFC是什么以及它的几种工作模式,然后让大家认识了一下市面上卡片的分类,也介绍了M1卡的数据结构,最后,演示了一下怎在Android中使用NFC来读取M1卡的卡号。 虽然文章没有把NFC的所有使用模式都进行讲解,但是挑了一个模式进行详细的讲解,相信阅读这篇文章后,你对Android的NFC不会那么的陌生了,其他的NFC的工作模式就由大家自行了解学习,这样才能加深印象和理解。

  最后放出本文的demo,大家可以点击这里下载

本文已由公众号“爱码者说”首发

欢迎关注我的公众号