公司使用uniapp在android手机端要增加一个nfc识别的功能。在此记录一下实现的过程。
我的代码逻辑主要来源于找到的这篇文章:
uniapp-安卓NFC读取 - 我要找到我的全世界 - 博客园
文章内附有代码,为防止文章失效代码消失,在这篇文章里面也记录一下。
- var NfcAdapter;
- var NdefRecord;
- var NdefMessage;
- var _getCardNo;
-
- export default {
- initNFC() {
- if (uni.getSystemInfoSync().platform == 'android') {
- listenNFCStatus()
- }
- },
- readNFC(callback) {
- if (uni.getSystemInfoSync().platform == 'android') {
- readData(callback);
- }
- },
- closeNFC() {
- if (uni.getSystemInfoSync().platform == 'android') {
- closeReadAndWrite();
- }
- }
- }
-
- function listenNFCStatus() {
- try {
- var main = plus.android.runtimeMainActivity();
- var Intent = plus.android.importClass('android.content.Intent');
- var Activity = plus.android.importClass('android.app.Activity');
- var PendingIntent = plus.android.importClass('android.app.PendingIntent');
- var IntentFilter = plus.android.importClass('android.content.IntentFilter');
- NfcAdapter = plus.android.importClass('android.nfc.NfcAdapter');
- var nfcAdapter = NfcAdapter.getDefaultAdapter(main);
-
- if (nfcAdapter == null) {
- uni.showToast({
- title: '设备不支持NFC!',
- icon: 'none'
- })
- return;
- }
-
- if (!nfcAdapter.isEnabled()) {
- uni.showToast({
- title: '请在系统设置中先启用NFC功能!',
- icon: 'none'
- });
- return;
- }
-
- var intent = new Intent(main, main.getClass());
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- var pendingIntent = PendingIntent.getActivity(main, 0, intent, 0);
- var ndef = new IntentFilter("android.nfc.action.TECH_DISCOVERED");
- ndef.addDataType("*/*");
- var intentFiltersArray = [ndef];
- var techListsArray = [
- ["android.nfc.tech.IsoDep"],
- ["android.nfc.tech.NfcA"],
- ["android.nfc.tech.NfcB"],
- ["android.nfc.tech.NfcF"],
- ["android.nfc.tech.Nfcf"],
- ["android.nfc.tech.NfcV"],
- ["android.nfc.tech.NdefFormatable"],
- ["android.nfc.tech.MifareClassic"],
- ["android.nfc.tech.MifareUltralight"]
- ];
- plus.globalEvent.addEventListener("newintent",
- function() {
- setTimeout(handle_nfc_data1, 1000);
- }, false);
- plus.globalEvent.addEventListener("pause", function(e) {
- if (nfcAdapter) {
- nfcAdapter.disableForegroundDispatch(main);
- }
- }, false);
- plus.globalEvent.addEventListener("resume", function(e) {
- if (nfcAdapter) {
- //console.log('resume');
- nfcAdapter.enableForegroundDispatch(main, pendingIntent, intentFiltersArray, techListsArray);
- }
- }, false);
- nfcAdapter.enableForegroundDispatch(main, pendingIntent, intentFiltersArray, techListsArray);
- } catch (e) {
- console.error(e);
- }
- }
-
- function handle_nfc_data1() {
- NdefRecord = plus.android.importClass("android.nfc.NdefRecord");
- NdefMessage = plus.android.importClass("android.nfc.NdefMessage");
- var main = plus.android.runtimeMainActivity();
- var intent = main.getIntent();
- //console.log("action type:" + intent.getAction());
- if ("android.nfc.action.TECH_DISCOVERED" == intent.getAction()) {
- if (readyWriteData) {
- //__write(intent);
- readyWriteData = false;
- } else if (readyRead) {
- __read(intent);
- readyRead = false;
- }
- }
- }
-
- function showToast(msg) {
- plus.nativeUI.toast(msg);
- }
-
- // function __write(intent) {
- // try {
- // waiting.setTitle('请勿移开标签\n正在写入...');
- // var text = document.getElementById('text').value;
- // console.log("text=" + text);
- // var textBytes = plus.android.invoke(text, "getBytes");
- // var textRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA,
- // plus.android.invoke("text/plain", "getBytes"), plus.android.invoke("", "getBytes"), textBytes);
- // var message = new NdefMessage([textRecord]);
- // var Ndef = plus.android.importClass('android.nfc.tech.Ndef');
- // var NdefFormatable = plus.android.importClass('android.nfc.tech.NdefFormatable');
- // var tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
- // var ndef = Ndef.get(tag);
- // if (ndef != null) {
- // var size = message.toByteArray().length;
- // console.log("size=" + size);
- // ndef.connect();
- // if (!ndef.isWritable()) {
- // showToast("tag不允许写入");
- // waiting.close();
- // return;
- // }
- // if (ndef.getMaxSize() < size) {
- // showToast("文件大小超出容量");
- // waiting.close();
- // return;
- // }
-
- // ndef.writeNdefMessage(message);
- // waiting.close();
- // showToast("写入数据成功.");
- // return;
- // } else {
- // var format = NdefFormatable.get(tag);
- // if (format != null) {
- // try {
- // format.connect();
- // format.format(message);
- // showToast("格式化tag并且写入message");
- // waiting.close();
- // return;
- // } catch (e) {
- // showToast("格式化tag失败.");
- // waiting.close();
- // return;
- // }
- // } else {
- // showToast("Tag不支持NDEF");
- // waiting.close();
- // return;
- // }
- // }
- // } catch (e) {
- // console.log("error=" + e);
- // waiting.close();
- // alert('写入失败');
- // }
-
- // }
-
- function __read(intent) {
- try {
- var content = "";
- waiting.setTitle('请勿移开标签\n正在读取数据...');
- var tag = plus.android.importClass("android.nfc.Tag");
- tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
- var bytesId = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID);
- waiting.close();
- var tagid = bytesToHexString(tag.getId())
- if (typeof _getCardNo === 'function') {
- _getCardNo(tagid);
- }
- } catch (e) {
- uni.showToast({
- title: e,
- icon: 'none'
- });
- }
- }
-
- function bytesToHexString(inarray) {
- var i, j, x;
- var hex = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A",
- "B", "C", "D", "E", "F"
- ];
- var out = "";
- for (j = 0; j < inarray.length; ++j) {
- x = parseInt(inarray[j]) & 0xff;
- i = (x >> 4) & 0x0f;
- out += hex[i];
- i = x & 0x0f;
- out += hex[i];
- }
- return out;
- }
-
- function reverseTwo(str) {
-
- var str1 = "";
- for (var i = 1; i <= str.length; i++) {
- str1 += str[i - 1];
- if (i % 2 == 0) {
- if (i == str.length) {
- break;
- }
- str1 += ":";
- }
- }
- var str2 = "";
- for (var i = str1.split(":").length - 1; i >= 0; i--) {
- str2 += str1.split(":")[i];
- }
- return str2;
- }
-
- if (uni.getSystemInfoSync().platform == 'android') {
- //plus.globalEvent.addEventListener('plusready', listenNFCStatus, false);
- }
-
- var waiting;
- var readyWriteData = false;
- var readyRead = false;
-
- function writeData() {
- var textEle = plus.globalEvent.getElementById('text');
- if (!textEle.value) {
- uni.showToast({
- title: '请输入要写入的内容!',
- icon: 'none'
- });
- return;
- }
- readyWriteData = true;
- waiting = plus.nativeUI.showWaiting("请将NFC标签靠近!");
- }
-
- function readData(getCardNo) {
- readyRead = true;
- _getCardNo = getCardNo
- waiting = plus.nativeUI.showWaiting("请将NFC标签靠近!", {
- modal: false
- });
- }
-
- function closeReadAndWrite() {
- readyWriteData = false;
- readyRead = false;
-
- if (waiting) {
- waiting.close();
- }
- }
代码内部是存在着写入功能的,被注释掉了,单单使用读取功能。
读取到的NFC类型有比较多种,这是我找到的比较好的一篇解释,大部分文章都有提到的4种类型。
文章里面附带了NFC相关的android官方文档,因为解析代码逻辑需要用到,我就把官网的链接也贴在下面。
NFC 基础知识 | Android 开发者 | Android Developers
NfcAdapter | Android Developers
第一步:新建一个js文件,复制代码进去。
第二步:在涉及到使用nfc识别的页面,引入这个js文件。主要三个方法:初始化,关闭识别,识别成功回调方法
onload里面执行初始化initNFC,在离开页面的时候closeNFC(博主本人放在unload),点击某个按钮BUTTON的时候开始识别readNFC。
开启识别的时候会一直等待识别,建议添加一个setTimeout定时器,在固定时间调用closeNFC关闭识别,点击BUTTON的时候先清除再调用,识别成功的回调方法里面也要清理这个定时器。
uniapp需要在manifest.json中开启手机的NFC识别权限。
代码中也添加了判断手机是否开启了NFC识别,NFC的识别功能是调用了手机自身的一个intent,然后跳转回来。
NfcAdapter适配器里面包含了许多的识别到的信息,这些信息都是要经过序列化才能使用的,如果不确定该调用哪个序列化方法,或者想知道还有哪些方法,可以去官方文档 里面找。
代码中就有使用到这两个值。
从启动开始讲起,调用手机自身的nfc识别,就意味着当前的intent跳转到另外一个intent,读取到数据之后就跳转回来。
- var main = plus.android.runtimeMainActivity();
- var Intent = plus.android.importClass('android.content.Intent');
- var Activity = plus.android.importClass('android.app.Activity');
- var PendingIntent = plus.android.importClass('android.app.PendingIntent');
- var IntentFilter = plus.android.importClass('android.content.IntentFilter');
- NfcAdapter = plus.android.importClass('android.nfc.NfcAdapter');
- var nfcAdapter = NfcAdapter.getDefaultAdapter(main);
-
-
-
- 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
-
-
- var intent = new Intent(main, main.getClass());
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- var pendingIntent = PendingIntent.getActivity(main, 0, intent, 0);
- var ndef = new IntentFilter("android.nfc.action.TECH_DISCOVERED");
- ndef.addDataType("*/*");
- var intentFiltersArray = [ndef];
- var techListsArray = [
- ["android.nfc.tech.IsoDep"],
- ["android.nfc.tech.NfcA"],
- ["android.nfc.tech.NfcB"],
- ["android.nfc.tech.NfcF"],
- ["android.nfc.tech.Nfcf"],
- ["android.nfc.tech.NfcV"],
- ["android.nfc.tech.NdefFormatable"],
- ["android.nfc.tech.MifareClassic"],
- ["android.nfc.tech.MifareUltralight"]
- ];
- nfcAdapter.enableForegroundDispatch(main, pendingIntent, intentFiltersArray, techListsArray);
这些就是一些跳转的设置,下半部分设置了跳转的启动方式为Intent.FLAG_ACTIVITY_SINGLE_TOP,并且设置了intent过滤器。
下一步就是监听这些跳转,并处理放回的数据。于是在代码中可以看到下面的这些监听方法。
- plus.globalEvent.addEventListener("newintent",
- function() {
- setTimeout(handle_nfc_data1, 1000);
- }, false);
- plus.globalEvent.addEventListener("pause", function(e) {
- if (nfcAdapter) {
- nfcAdapter.disableForegroundDispatch(main);
- }
- }, false);
- plus.globalEvent.addEventListener("resume", function(e) {
- if (nfcAdapter) {
- //console.log('resume');
- nfcAdapter.enableForegroundDispatch(main, pendingIntent, intentFiltersArray, techListsArray);
- }
- }, false);
主要是“newintent"的监听,里面实现了主要的逻辑。
手机识别到nfc跳转回到应用的时候,intent会携带返回的信息。进行判断是否是"android.nfc.action.TECH_DISCOVERED"。意味着识别到了信息。
__read方法就是主要的处理信息的内容了,也是最重要的。
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);将数据进行序列化。应用与应用直接传递数据,都要进行序列化和反序列化处理的。
bytesToHexString将数据转换。
上面内容是读取NFC标签卡的TAG标签ID值,如果是要读取NFC写入的MESSAGE信息。使用下面的方法进行替换。
- function __read(intent) {
- try {
- var content = "";
- waiting.setTitle('正在读取数据...');
- var tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
- var messages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
- console.log(messages)
- for (var i = 0; i < messages.length; i++) {
- var message = messages[i];
- if (message != null) {
- var records = message.getRecords();
-
- // Loop through all NDEF records in the current NDEF message
- for (var j = 0; j < records.length; j++) {
- var recordType = String(records[j].getType());
- var payload = String(records[j].getPayload());
-
- // Decode payload to readable text
- payload = decodeURIComponent(escape(payload));
- // TPIM00010141
- // Print out result
- console.log("Record type: " + recordType);
- // String.fromCharCode(parseInt(payload[i]))
- // var Realpayload = payload.split(',').map(strItem => String.fromCharCode(parseInt(strItem))).join('')
- let textEncoding = (payload[0] & 0x80) === 0 ? "UTF-8" : "UTF-16";
- let languageCodeLength = payload[0] & 0x3F;
- let textStartIndex = 1 + languageCodeLength;
- let text = payload.split(',').slice(textStartIndex).map(strItem => String.fromCharCode(parseInt(strItem))).join('')
- content = text
- }
- }
- }
- waiting.close();
- var tagid = String(content)
- if (typeof _getCardNo === 'function') {
- _getCardNo(tagid);
- }
- } catch (e) {
- console.log(e)
- uni.showToast({
- title: e,
- icon: 'none'
- });
- }
- }
NFC的EXTRA_NDEF_MESSAGES有一个写入规则,payload中的真实数据,要从第三个字符开始读取。
除了写入的内容外,前面两个的字符包含了内容的编码格式,每个字符的长度这些信息。