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字节的内容。见下图
要想读取对应扇区的数据,需要知道对应扇区的秘钥,否则读取不到数据。
Android的NFC标签调度系统
当手机发现外部NFC的标签(指含有NFC功能的设备)时,Android系统会寻找可以处理这个标签的Activity,那怎么知道哪个Activity能处理这条NFC消息呢?答案是清单文件,我们需要在清单文件中设置intent-filter
。系统会分发NFC消息到设置intent-filter
的Activity中,当然,接收NFC消息也有优先级之分,也是通过设置intent-filter
来设置接收NFC消息的优先级的。
NFC的标签调度系统绑定了3中intent,按优先级的高低列出,如下
ACTION_NDEF_DISCOVERED
:如果扫描到包含此Intent的Activity,并且可识别其类型,则使用此 Intent 启动 Activity。这是优先级最高的 Intent,NFC标签调度系统会尽可能尝试使用此 Intent 启动 Activity,在找不到这个Intent时才会尝试使用其他 Intent。ACTION_TECH_DISCOVERED
:如果没有登记要处理ACTION_NDEF_DISCOVERED
Intent 的 Activity,则标签调度系统会尝试使用此 Intent 来启动应用。此外,如果扫描到的标签包含无法映射到 MIME 类型或 URI 的 NDEF 数据,或者该标签不包含 NDEF 数据,但它使用了已知的标签技术,那么也会直接启动此 Intent(无需先启动ACTION_NDEF_DISCOVERED
)。ACTION_TAG_DISCOVERED
:如果没有处理ACTION_NDEF_DISCOVERED
或者ACTION_TECH_DISCOVERED
Intent 的 Activity,则使用此 Intent 启动 Activity。
它们3者关系如下图所示
如果想处理所有的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/plain
的 ACTION_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,大家可以点击这里下载