最近需要将先前项目的芯片识别功能优化,先前的项目使用的是RFID高频芯片识别+二维码扫描处理,后续设备大概率不会上RFID设备,因此需要接入NFC识别。
什么是NFC
NFC是近场通信(Near Field Communication,NFC),是一种短距高频的无线电技术。
由非接触式射频识别(RFID)演变而来。NFC工作频率为13.56Hz,有效范围为20cm以内,其传输速度有106 Kbit/秒、212 Kbit/秒或者424 Kbit/秒三种。NFC采用主动和被动两种读取模式。
如何接入
Android系统自带NFC,在手机存在NFC硬件时就可以使用了。早在Android2.3时,我们就能使用Android Beam在同步的设备间通过NFC传递图片等数据。通过查阅源码我们得知,NFC属于系统服务,在使用时自然是需要通过context.getSystemService(Context.NFC_SERVICE);来获取系统服务
1 | /** |
我们需要构建一个NfcManager来使用NFC,而NfcManager中只有一个主角,那就是NfcAdapter!
想要获得NfcAdapter,只需要使用静态方法
1 | NfcAdapter.getDefaultAdapter(this); |
其中的context用来启动NFC服务,等价于:
1 | NfcManager manager =(NfcManager)context.getSystemService(Context.NFC_SERVICE); |
通过NfcAdapter的enableForegroundDispatch()方法启用对给定Activity的前台分发,即最高优先级的分发(优先级这个后面再说)。
1 | This method must be called from the main thread, and only when the activity is in the foreground (resumed). Also, activities must call disableForegroundDispatch before the completion of their Activity.onPause callback to disable foreground dispatch after it has been enabled. |
方法的注释也写的很清楚,必须在主线程调用,同时必须当前activity可见/在前台,同样的,我们需要在activity不可见的时候调用disableForegroundDispatch()方法。
1 |
|
那么基本的就是这个成对出现的方法了。
enableForegroundDispatch()方法中需要一个PendingIntent,下面是我们构建的PendingIntent
1 | //一旦截获NFC消息,就会通过PendingIntent调用窗口 |
构建PendingIntent时我们传入的Intent直接使用了getClass(),因此,回调的数据会到当前的activity中,我们需要重载onNewIntent()方法来处理我们接收到的数据。
如何读取
上面说道在onNewIntent()方法中就能处理我们收到的NFC数据了,这就不得不说另外一个重要的对象Tag了
1 | //1.获取Tag对象 |
通过Intent获取Tag对象

通过注释可知:
我们可以通过Tag的getId()方法获取ID,通过getTechList()方法获取其技术集,技术集代表了当前这个芯片支持的技术,通俗点就是可以支持的格式,或者说是实现某些高级功能的基础支撑。
每次发现标记(进入范围)时都会创建一个新的标记对象,当发现一个标记时,将创建一个tag对象,并通过NfcAdapter传递给activity。没错,就是PendingIntent申明的那个。
接下来Tag会经过四个阶段的分发:
前台活动
activity分发调用了
NfcAdapter.enableForegroundDispatch()方法的activity,这个会被优先触发,比如我们平时打开系统NFC,贴上NFC卡片后,系统会提示“检测到NFC卡片”,然后会给我们一些推荐的操作,诸如打开微信,支付宝充值公交卡等等。但是当我们在调用了NfcAdapter.enableForegroundDispatch()方法的activity可见时,系统的处理就会因为优先级不够而不被触发,转而在当前activity回调数据NDEF数据分发
如果标签中包含NDEF数据,系统会在第一个NdefMessage中检查第一个NdefRecord,
NdefRecord可以有很多种类型,其构造方法如下
1 | NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload) |
具体参数说明可以查看源码。
我们可以在这个地方构建Android Application Record,
通过createApplicationRecord()方法,例如:
1 | NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord |
我们将这样的NdefRecord写入标签后,下次我们识别标签就会自动打开短信应用。
1 | NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{createUriRecord("http://www.baidu.com")}); |
或者
1 | NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord.createUri(Uri.parse("http://www.baidu.com"))}); |
构建Uri的Record,我们可以打开网页。
当然,我们也可以直接写入文本数据
1 | NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{createTextRecord("Hello World")}); |
标签技术分发
当
activity注册时,添加了相应的intent-filter,例如:1
2
3
4
5
6
7
8
9
10<activity android:name=".nfc.TechFilter" android:label="NFC/TechFilter">
<!-- Add a technology filter -->
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/filter_nfc"
/>
</activity>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- capture anything using NfcF -->
<tech-list>
<tech>android.nfc.tech.NfcF</tech>
</tech-list>
<!-- OR -->
<!-- capture all MIFARE Classics with NDEF payloads -->
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.MifareClassic</tech>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
</resources>在
intent-filter中添加对应的action后,我们就能通过action获取数据了备用分发
如果所有的分发条件都不满足,就会走
NfcAdapter.ACTION_TAG_DISCOVERED的action来分发数据
读取数据我们可以根据当前的techType来读取,例如:

当前芯片的TechList中有3条数据,分别是NFCA,MifareUltralight和NEDF,因此我们就能针对性的取数据了。
NEDF通过Ndef ndef = Ndef.get(detectedTag);来获取NEDF实例;
MifareUltralight通过MifareUltralight ultralight = MifareUltralight.get(tag);来获取MifareUltralight实例;
…
以此类推
拿到相应的实例对象后,先判断是否为空,因为数据写入的格式会决定数据写入成哪种TechType的数据,后面取值就简单了,拿到对应的NdefRecord解析就行了.
读取NDEF:
1 | private String readNdefTag(Intent intent) { |
读取MifareUltralight:
1 | public String readTag(Tag tag) { |
MifareUltralight读取涉及到NFC标签的分区,前4个分区(索引0-3)为固有分区,后面的为数据区,我们要读,要写都要写到数据区中,并且在写的时候需要注意长度。
如何写入
下面是MifareUltralight写入的方法
1 | public void writeTag(Tag tag,String data) { |
汉字的编码长度在不同的编码下会有不同的占位。
M1卡有从0到15共16个扇区,每个扇区配备了从0到3共4个段,每个段可以保存16字节的内容,我们一般可以存1024字节的数据,根据我们写入的数据长度,要确认下面的数据要写到哪个扇区里,还是比较麻烦的,另外,MifareUltralight读取时会把后面扇区的空数据也读出来,生成乱码,所以不太推荐使用MifareUltralight读取。
写入NDEF数据就比较方便了
1 | //首先构建NdefMessage对象 |
直接拿到Ndef对象调用writeNdefMessage()就可以了。