Loading... > 最近需要将先前项目的芯片识别功能优化,先前的项目使用的是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);`来获取系统服务 ```java /** * Use with {@link #getSystemService(String)} to retrieve a * {@link android.nfc.NfcManager} for using NFC. * * @see #getSystemService(String) */ public static final String NFC_SERVICE = "nfc"; ``` 我们需要构建一个`NfcManager`来使用NFC,而`NfcManager`中只有一个主角,那就是`NfcAdapter`! 想要获得`NfcAdapter`,只需要使用静态方法 ```java NfcAdapter.getDefaultAdapter(this); ``` 其中的`context`用来启动NFC服务,等价于: ```java NfcManager manager =(NfcManager)context.getSystemService(Context.NFC_SERVICE); NfcAdapter adapter = manager.getDefaultAdapter(); ``` 通过`NfcAdapter`的`enableForegroundDispatch()`方法启用对给定Activity的前台分发,即最高优先级的分发(优先级这个后面再说)。 ```tex 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()`方法。 ```java @Override public void onResume() { super.onResume(); //设置处理优于所有其他NFC的处理 if (mNfcAdapter != null) mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null); } @Override public void onPause() { super.onPause(); //恢复默认状态 if (mNfcAdapter != null) mNfcAdapter.disableForegroundDispatch(this); } ``` 那么基本的就是这个成对出现的方法了。 `enableForegroundDispatch()`方法中需要一个`PendingIntent`,下面是我们构建的`PendingIntent` ```java //一旦截获NFC消息,就会通过PendingIntent调用窗口 mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0); ``` 构建`PendingIntent`时我们传入的`Intent`直接使用了`getClass()`,因此,回调的数据会到当前的`activity`中,我们需要重载`onNewIntent()`方法来处理我们接收到的数据。 #### 如何读取 上面说道在`onNewIntent()`方法中就能处理我们收到的NFC数据了,这就不得不说另外一个重要的对象`Tag`了 ```java //1.获取Tag对象 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); ``` 通过`Intent`获取`Tag`对象  通过注释可知: 我们可以通过`Tag`的`getId()`方法获取ID,通过`getTechList()`方法获取其技术集,技术集代表了当前这个芯片支持的技术,通俗点就是可以支持的格式,或者说是实现某些高级功能的基础支撑。 每次发现标记(进入范围)时都会创建一个新的标记对象,当发现一个标记时,将创建一个tag对象,并通过NfcAdapter传递给`activity`。没错,就是`PendingIntent`申明的那个。 接下来`Tag`会经过四个阶段的分发: 1. 前台活动`activity`分发 调用了`NfcAdapter.enableForegroundDispatch()`方法的`activity`,这个会被优先触发,比如我们平时打开系统NFC,贴上NFC卡片后,系统会提示“检测到NFC卡片”,然后会给我们一些推荐的操作,诸如打开微信,支付宝充值公交卡等等。但是当我们在调用了`NfcAdapter.enableForegroundDispatch()`方法的`activity`可见时,系统的处理就会因为优先级不够而不被触发,转而在当前`activity`回调数据 2. NDEF数据分发 如果标签中包含NDEF数据,系统会在第一个NdefMessage中检查第一个NdefRecord, NdefRecord可以有很多种类型,其构造方法如下 ```java NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload) ``` 具体参数说明可以查看源码。 我们可以在这个地方构建Android Application Record, 通过`createApplicationRecord()`方法,例如: ```java NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord .createApplicationRecord("com.android.mms")}); ``` 我们将这样的NdefRecord写入标签后,下次我们识别标签就会自动打开短信应用。 ```java NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{createUriRecord("http://www.baidu.com")}); ``` 或者 ```java NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord.createUri(Uri.parse("http://www.baidu.com"))}); ``` 构建Uri的Record,我们可以打开网页。 当然,我们也可以直接写入文本数据 ```java NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{createTextRecord("Hello World")}); ``` 3. 标签技术分发 当`activity`注册时,添加了相应的`intent-filter`,例如: ```xml <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> ``` ```xml <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`获取数据了 4. 备用分发 如果所有的分发条件都不满足,就会走`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: ```java private String readNdefTag(Intent intent) { if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) { Parcelable[] rawMsgs = intent.getParcelableArrayExtra( NfcAdapter.EXTRA_NDEF_MESSAGES); NdefMessage msgs[] = null; int contentSize = 0; if (rawMsgs != null) { msgs = new NdefMessage[rawMsgs.length]; for (int i = 0; i < rawMsgs.length; i++) { msgs[i] = (NdefMessage) rawMsgs[i]; contentSize += msgs[i].toByteArray().length; } } try { if (msgs != null) { NdefRecord record = msgs[0].getRecords()[0]; String textRecord = NFCUtils.parseTextRecord(record); return textRecord; } } catch (Exception e) { e.printStackTrace(); } } return null; } ``` 读取MifareUltralight: ```java public String readTag(Tag tag) { MifareUltralight ultralight = MifareUltralight.get(tag); try { ultralight.connect(); //从第4索引开始读,前4索引对应的为标签固化区 byte[] data = ultralight.readPages(4); return new String(data, Charset.forName("GB2312")); } catch (Exception e) { } finally { try { ultralight.close(); } catch (Exception e) { } } return null; } ``` MifareUltralight读取涉及到NFC标签的分区,前4个分区(索引0-3)为固有分区,后面的为数据区,我们要读,要写都要写到数据区中,并且在写的时候需要注意长度。 #### 如何写入 下面是MifareUltralight写入的方法 ```java public void writeTag(Tag tag,String data) { //向nfc标签写数据第1步,从标签中得到MifareUltralight MifareUltralight ultralight = MifareUltralight.get(tag); if (TextUtils.isEmpty(data)) { return; } try { //向nfc标签写数据第2步, connect ultralight.connect(); /** * 向nfc标签写数据第3步, 正式写数据.前4页(0至3)存储了NFC标签相关的信息 * 注意 Charset.forName("GB2312")), * 不用utf-8因为一个汉字有可能用3个字节编码汉字,那么2个汉字有可能是6个字节. * 而GB2312始终用2个字节.而每页最多4个字节, */ ultralight.writePage(4, data.substring(0,4).getBytes(Charset.forName("GB2312")));//第4页,页从0开始. ultralight.writePage(5, data.substring(4,8).getBytes(Charset.forName("GB2312"))); ToastUtils.showShort( "成功写入MifareUltralight格式数据"); } catch (Exception e) { e.printStackTrace(); } finally { try { ultralight.close(); } catch (Exception e) { e.printStackTrace(); } } } ``` 汉字的编码长度在不同的编码下会有不同的占位。 M1卡有从0到15共16个扇区,每个扇区配备了从0到3共4个段,每个段可以保存16字节的内容,我们一般可以存1024字节的数据,根据我们写入的数据长度,要确认下面的数据要写到哪个扇区里,还是比较麻烦的,另外,MifareUltralight读取时会把后面扇区的空数据也读出来,生成乱码,所以不太推荐使用MifareUltralight读取。 写入NDEF数据就比较方便了 ```java //首先构建NdefMessage对象 NdefMessage ndefMessage = new NdefMessage( new NdefRecord[]{createTextRecord(mText)}); public static boolean writeTag(NdefMessage ndefMessage, Tag tag) { try { Ndef ndef = Ndef.get(tag); ndef.connect(); ndef.writeNdefMessage(ndefMessage); return true; } catch (Exception e) { } return false; } ``` 直接拿到`Ndef`对象调用`writeNdefMessage()`就可以了。 最后修改:2023 年 06 月 02 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,请随意赞赏