NFC的全称是“Near Field Communication”,意思是近场通信、与邻近的区域通信。大众所熟知的NFC技术应用,主要是智能手机的刷卡支付功能。别看智能手机是近十年前才出现的,NFC的历史可比智能手机要悠久得多,它脱胎于上世纪的RFID无线射频识别技术。
所谓RFID是“Radio Frequency Identification”的缩写,它通过无线电信号便可识别特定目标并读写数据,而无需自身与该目标之间建立任何机械或者光学接触。像日常生活中的门禁卡、公交卡,乃至二代身份证,都是采用了RFID技术的卡片。若想读写这些RFID卡片,则需相应的读卡器,只要用户把卡片靠近,读卡器就会产生感应动作。
既然RFID已经广泛使用,那么何苦又要另外制定NFC标准呢?其实正是因为RFID用的地方太多了,导致随意性较大,反而不便于更好地管控。所以业界重新定义了NFC规范,试图在两个方面弥补RFID的固有缺憾:
1、RFID的信号传播距离较远,致使位于远处的设备也可能获取卡片信息,这对安全性较高的场合是不可接受的。而NFC的有效工作距离在十厘米之内,即可避免卡片信息被窃取的风险。
2、RFID的读写操作是单向的,也就是说,只有读卡器能读写卡片,卡片不能拿读卡器怎么样。现在NFC不再沿用“读卡器——卡片”的模式,取而代之的是只有NFC设备的概念,两个NFC设备允许互相读写,既可以由设备A读写设备B,也可以由设备B读写设备A。
改进之后的NFC技术既提高了安全性,又拓宽了应用场合,同时还兼容现有的大部分RFID卡片,因此在智能手机上运用NFC而非RFID也就不足为怪了。

带有NFC功能的手机,在实际生活中主要有三项应用:读卡、写卡、分享内容(两部手机之间传输数据)。为了能更迅速地了解NFC技术在Android中的开发流程,下面通过相对简单的读卡功能,来介绍如何进行手机App的NFC开发。

首先App工程要在AndroidManifest.xml中声明NFC的操作权限,下面是配置声明的例子:

    <!-- NFC --><uses-permission android:name="android.permission.NFC" /><!-- 仅在支持NFC的设备上运行 --><uses-feature android:name="android.hardware.nfc" android:required="true" />

其次还要对活动页面声明NFC过滤器,目前Android支持NDEF_DISCOVERED、TAG_DISCOVERED、TECH_DISCOVERED这三种过滤器,最好把它们都加入到过滤器列表中,示例如下:

        <activity android:name=".NfcActivity"><intent-filter><action android:name="android.nfc.action.NDEF_DISCOVERED" /></intent-filter><intent-filter><action android:name="android.nfc.action.TAG_DISCOVERED" /><category android:name="android.intent.category.DEFAULT" /></intent-filter><intent-filter><action android:name="android.nfc.action.TECH_DISCOVERED" /></intent-filter><meta-dataandroid:name="android.nfc.action.TECH_DISCOVERED"android:resource="@xml/nfc_tech_filter" /></activity>

其中TECH_DISCOVERED类型另外指定了过滤器的来源是@xml/nfc_tech_filter,该文件的实际路径为xml/nfc_tech_filter.xml,文件内容如下所示:

<resources><!-- 可以处理所有Android支持的NFC类型 --><tech-list><tech>android.nfc.tech.NfcA</tech><tech>android.nfc.tech.NfcB</tech><tech>android.nfc.tech.NfcF</tech><tech>android.nfc.tech.NfcV</tech><tech>android.nfc.tech.IsoDep</tech><tech>android.nfc.tech.Ndef</tech><tech>android.nfc.tech.NdefFormatable</tech><tech>android.nfc.tech.MifareClassic</tech><tech>android.nfc.tech.MifareUltralight</tech></tech-list>
</resources>

上面的过滤器列表乍看过去真是令人大吃一惊,这都是些什么东东,它们之间有哪些区别呢?倘若认真对这几个专业术语追根溯源,势必要一番长篇大论才能理清其中的历史脉络,因此不妨将事情简单化,这些NFC类型只不过是一个大家族内部的兄弟姐妹罢了。譬如说中国近代史上显赫的宋氏三姐妹,原是同一对父母,然后分别嫁给三个人罢了。NFC类型虽多,常见的NfcA、NfcB、IsoDep三个系出ISO14443标准(即RFID卡标准),它们仨各自用于生活中的几种场合,说明如下:
1、NfcA遵循ISO14443-3A标准,常用于门禁卡;
2、NfcB遵循ISO14443-3B标准,常用于二代身份证;
3、IsoDep遵循ISO14443-4标准,常用于公交卡;

好不容易把AndroidManifest.xml的相关配置弄完,接着便是代码方面的处理逻辑了。NFC编码主要有三个步骤:初始化适配器、启用感应/禁用感应、接收到感应消息并对消息解码,下面分别进行介绍:

一、初始化NFC适配器
这里的初始化动作又可分解为三部分:
1、调用NfcAdapter类的getDefaultAdapter方法,获取系统当前默认的NFC适配器。这个NfcAdapter与列表适配器的概念不一样,它其实是Android的NFC管理工具。
2、声明一个延迟意图,告诉系统一旦接收到NFC感应,则应当启动哪个页面进行处理。
3、定义一个NFC消息的过滤器,这个过滤器是AndroidManifest.xml所配置过滤器的子集。因为接下来要读取的卡片兼容RFID标准(ISO14443家族),所以过滤器的动作名称为NfcAdapter.ACTION_TECH_DISCOVERED,并且设置该动作包含了两项卡片标准,分别是NfcA(用于门禁卡)和IsoDep(用于公交卡)。
详细的NFC初始化代码示例如下:

    private void initNfc() {// 获取默认的NFC适配器nfcAdapter = NfcAdapter.getDefaultAdapter(this);if (nfcAdapter == null) {tv_nfc_result.setText("当前手机不支持NFC");return;} else if (!nfcAdapter.isEnabled()) {tv_nfc_result.setText("请先在系统设置中启用NFC功能");return;}// 探测到NFC卡片后,必须以FLAG_ACTIVITY_SINGLE_TOP方式启动Activity,// 或者在AndroidManifest.xml中设置launchMode属性为singleTop或者singleTask,// 保证无论NFC标签靠近手机多少次,Activity实例都只有一个。Intent intent = new Intent(this, NfcActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);// 声明一个NFC卡片探测事件的相应动作mPendingIntent = PendingIntent.getActivity(this, 0,intent, PendingIntent.FLAG_UPDATE_CURRENT);try {// 定义一个过滤器(检测到NFC卡片)mFilters = new IntentFilter[]{new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED, "*/*")};} catch (Exception e) {e.printStackTrace();}// 读标签之前先确定标签类型mTechLists = new String[][]{new String[]{NfcA.class.getName()}, {IsoDep.class.getName()}};}

二、启用NFC感应/禁用NFC感应
为了让测试App能够接收NFC的感应动作,需要重载Activity的onResume函数,在该函数中调用NFC适配器的enableForegroundDispatch方法,指定启用NFC功能时的响应动作以及过滤条件。另外也需重载onPause函数,在该函数中调用NFC适配器的disableForegroundDispatch方法,表示当前页面在暂停状态之时不再接收NFC感应消息。具体的NFC启用和禁用代码如下所示:

    @Overrideprotected void onResume() {super.onResume();if (nfcAdapter!=null && nfcAdapter.isEnabled()) {// 为本App启用NFC感应nfcAdapter.enableForegroundDispatch(this, mPendingIntent, mFilters, mTechLists);}}@Overridepublic void onPause() {super.onPause();if (nfcAdapter!=null && nfcAdapter.isEnabled()) {// 禁用本App的NFC感应nfcAdapter.disableForegroundDispatch(this);}}

三、接收到感应消息并对消息解码
通过前面的第二步启用NFC感应之后,一旦App接收到感应消息,就会回调Activity的onNewIntent函数,因此开发者可以重写该函数来处理NFC的消息内容。以NFC技术常见的小区门禁卡为例,门禁卡采取的子标准为NfcA,对应的数据格式则为MifareClassic。于是利用MifareClassic类的相关方法即可获取卡片数据,下面是MifareClassic类的方法说明:
get : 从Tag对象中获取卡片对象的信息。该方法为静态方法。
connect : 连接卡片数据。
close : 释放卡片数据。
getType : 获取卡片的类型。TYPE_CLASSIC表示传统类型,TYPE_PLUS表示增强类型,TYPE_PRO表示专业类型。
getSectorCount : 获取卡片的扇区数量。
getBlockCount : 获取卡片的分块个数。
getSize : 获取卡片的存储空间大小,单位字节。
使用MifareClassic工具查询卡片数据的流程很常规,先调用connect方法建立连接,然后调用各个get方法获取详细信息,最后调用close方法关闭连接。具体的门禁卡读取代码示例如下:

    @Overrideprotected void onNewIntent(Intent intent) {super.onNewIntent(intent);String card_info = "";String action = intent.getAction(); // 获取到本次启动的actionif (action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED) // NDEF类型|| action.equals(NfcAdapter.ACTION_TECH_DISCOVERED) // 其他类型|| action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)) { // 未知类型// 从intent中读取NFC卡片内容Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);// 获取NFC卡片的序列号byte[] ids = tag.getId();card_info = String.format("卡片的序列号为: %s", ByteArrayChange.ByteArrayToHexString(ids));if (rb_guard_card.isChecked()) {String result = readGuardCard(tag);card_info = String.format("%s\n详细信息如下:\n%s", card_info, result);tv_nfc_result.setText(card_info);}}}// 读取小区门禁卡信息public String readGuardCard(Tag tag) {MifareClassic classic = MifareClassic.get(tag);String info;try {classic.connect(); // 连接卡片数据int type = classic.getType(); //获取TAG的类型String typeDesc;if (type == MifareClassic.TYPE_CLASSIC) {typeDesc = "传统类型";} else if (type == MifareClassic.TYPE_PLUS) {typeDesc = "增强类型";} else if (type == MifareClassic.TYPE_PRO) {typeDesc = "专业类型";} else {typeDesc = "未知类型";}info = String.format("\t卡片类型:%s\n\t扇区数量:%d\n\t分块个数:%d\n\t存储空间:%d字节",typeDesc, classic.getSectorCount(), classic.getBlockCount(), classic.getSize());} catch (Exception e) {e.printStackTrace();info = e.getMessage();} finally { // 无论是否发生异常,都要释放资源try {classic.close(); // 释放卡片数据} catch (Exception e) {e.printStackTrace();info = e.getMessage();}}return info;}

编码完毕,找一台支持NFC的手机安装测试App,启动应用前注意开启手机的NFC功能。然后进入App的测试页面,拿一张门禁卡靠近手机背面(门禁卡不一定是卡片,也可能是钥匙扣模样),稍等片刻便会读取并显示门禁卡的基本信息,卡片信息截图如下所示:

点此查看Android开发笔记的完整目录

__________________________________________________________________________
本文现已同步发布到微信公众号“老欧说安卓”,打开微信扫一扫下面的二维码,或者直接搜索公众号“老欧说安卓”添加关注,更快更方便地阅读技术干货。

Android开发笔记(一百六十一)NFC近场通信相关推荐

  1. Android开发笔记(六十一)文件下载管理DownloadManager

    下载管理DownloadManager 文件下载其实是网络数据访问的一种特殊形式,使用普通的http请求也能完成,就是实现起来会繁琐一些.因为下载功能比较常用,而且业务功能相对统一,所以从Androi ...

  2. Android开发笔记(八十一)屏幕规格适配

    Configuration 适配各种屏幕规格,首先要取到系统对于屏幕的配置信息,这些配置可从工具类Configuration获得.Configuration对象在Activity中通过调用getRes ...

  3. Android开发笔记(五十一)通过Messenger实现进程间通信

    进程间通信IPC IPC是"Inter-Process Communication"的缩写,即进程间通信.Android为APP提供了多进程工作模式,这是因为多线程存在若干局限: ...

  4. Android开发笔记(六十二)HTTP数据格式的解析

    json解析 android有两种主流的json解析方案,一种是sdk自带的由Google提供的json(包名前缀为org.json),另一种是Alibaba提供的第三方jar包fastjson(包名 ...

  5. Android开发笔记(七十一)区分开发模式和上线模式

    为什么要区分两种模式 许多开发者(包括博主在内)都是闷骚的程序员,为了开发调试方便,常常在代码里加上日志,还经常在页面上各种弹窗提示.这固然有利于发现bug.提高软件质量,但过多的调试信息往往容易泄露 ...

  6. Android开发笔记(六十九)JNI实战

    NDK NDK的用途 NDK全称为Native Development Kit,意即原生的开发工具,NDK允许开发者在APP中通过C/C++代码执行部分程序.它是Android提供的方便开发者通过JN ...

  7. Android开发笔记(六十八)工程库打包

    写好一个Android模块,比如说一个自定义控件或某个功能的sdk,然后开放出来给别人使用,就得通过某种方式把源码提供给对方.常见的打包方式有: 一.直接给源码,由开发者把代码加入到自己的工程中 该方 ...

  8. Android开发笔记(六十六)自定义对话框

    AlertDialog Android中最常用的对话框是AlertDialog,它可以完成常见的交互操作,如提示.确认.选择等等,然后就是进度对话框ProgressDialog(参见< Andr ...

  9. Android开发笔记(六十五)多样的菜单

    菜单Menu Android的菜单分为两类:选项菜单和上下文菜单,默认使用选项菜单.菜单的布局文件存放在res/menu目录下,使用ADT新建一个Android工程,首页代码MainActivity中 ...

  10. Android开发笔记(六十四)网页加载与JS调用

    内置浏览器 网页视图WebView 如果一个网站已经有现成的网页及业务逻辑,那么使用WebView将其内嵌到app中,省去了app重画页面与http通信的事情,无疑是更经济的做法.WebView就是A ...

最新文章

  1. 工业环境中对机器学习的行业视角
  2. 计算机的桌面教案,《认识计算机桌面》教案-20210608141312.pdf-原创力文档
  3. Python 字符串大小写转换,值域范围
  4. Acronis True Image无法卸载或者卸载导致无法开机解决办法
  5. 获取android应用签名证书(打包APK用到的那个文件)的SHA1,MD5,SHA256值
  6. hdu 4292 Food 最大流
  7. 【转】Tag的创建和组织
  8. Windows 8 Directx 开发学习笔记(五)山峰河谷模型的简单实现
  9. 递归法:求n个元素的全排列
  10. Android ListView示例教程
  11. redis通用key操作
  12. Mysql数据库的mysql Schema 究竟有哪些东西 手工注入的基础要领
  13. 【库】JavaScript——滚动条( 不是很完善 )
  14. Atitit springboot mybatis spring 集成 Springboot1.4 mybatis3.4.6 /springbootMybatis 目录 1.1. 设置map
  15. BCH码(BCH code)详细分析
  16. 计算机常用的采样频率,采样频率
  17. 直播丨上海传智播客-黑马程序员/黑马设计师-封箱之作-“大神季”
  18. xshell 运行python脚本
  19. 转【面向代码】学习 Deep Learning(二)Deep Belief Nets(DBNs)
  20. 红光光浴一次能排多少湿气?-红光光浴/种光光学

热门文章

  1. Leetcode每日一题:402.remove-k-digits(移掉k位数字)
  2. Algorithm:递归思想及实例分析
  3. CCF2015-12-2 消除类游戏
  4. Docker Jenkins Node(一):初步构建
  5. 35.FFmpeg+OpenGLES+OpenSLES播放器实现(九.OpenGLES播放视频)
  6. 解决Linux下DNS配置重启失效问题
  7. c语言 一个矩阵的乘积,c语言矩阵相乘
  8. php对接银行接口,php 银行接口开发写法
  9. 学报格式和论文格式一样吗_社科类学报字数要求及投稿注意事项
  10. Windows 控制台cmd中文乱码的解决办法