vite-uni-dev-tool
Version:
vite-uni-dev-tool, debug, uni-app, 一处编写,到处调试
597 lines (523 loc) • 17.1 kB
text/typescript
import { onLoad, onUnload } from '@dcloudio/uni-app';
import { ref } from 'vue';
import {
getAndroidTechs,
getReadWriteStandard,
getSakHex,
getSakType,
parseAtqaAndroid,
transformArrayBuffer,
} from './utils';
const NfcAdapter: any = plus.android.importClass('android.nfc.NfcAdapter');
const Context = plus.android.importClass('android.content.Context');
const Tag = plus.android.importClass('android.nfc.Tag');
const Ndef: any = plus.android.importClass('android.nfc.tech.Ndef');
const NfcA: any = plus.android.importClass('android.nfc.tech.NfcA');
const NfcB: any = plus.android.importClass('android.nfc.tech.NfcB');
const NfcF: any = plus.android.importClass('android.nfc.tech.NfcF');
const NfcV: any = plus.android.importClass('android.nfc.tech.NfcV');
const NdefFormat = plus.android.importClass('android.nfc.tech.NdefFormat');
const NdefFormatable: any = plus.android.importClass(
'android.nfc.tech.NdefFormatable',
);
const MifareClassic: any = plus.android.importClass(
'android.nfc.tech.MifareClassic',
);
const MifareUltralight: any = plus.android.importClass(
'android.nfc.tech.MifareUltralight',
);
const IsoDep: any = plus.android.importClass('android.nfc.tech.IsoDep');
const Intent: any = plus.android.importClass('android.content.Intent');
const PendingIntent: any = plus.android.importClass(
'android.app.PendingIntent',
);
const IntentFilter: any = plus.android.importClass(
'android.content.IntentFilter',
);
const Settings: any = plus.android.importClass('android.provider.Settings');
const NdefRecord: any = plus.android.importClass('android.nfc.NdefRecord');
const NdefMessage: any = plus.android.importClass('android.nfc.NdefMessage');
const INTENT_FLAGS_SINGLE_TOP = Intent.FLAG_ACTIVITY_SINGLE_TOP;
const PENDING_INTENT_FLAGS = PendingIntent.FLAG_MUTABLE;
const EXTRA_NDEF_MESSAGES = NfcAdapter.EXTRA_NDEF_MESSAGES;
const EXTRA_ID = NfcAdapter.EXTRA_ID;
const EXTRA_TAG = NfcAdapter.EXTRA_TAG;
const TNF_MIME_MEDIA = NdefRecord.TNF_MIME_MEDIA;
const ACTION_NFC_SETTINGS = Settings.ACTION_NFC_SETTINGS;
const NFC_TECH_LISTS = [
['android.nfc.tech.Ndef'],
['android.nfc.tech.NdefFormatable'],
['android.nfc.tech.IsoDep'],
['android.nfc.tech.NfcA'],
['android.nfc.tech.NfcB'],
['android.nfc.tech.NfcF'],
['android.nfc.tech.NfcV'],
['android.nfc.tech.MifareClassic'],
['android.nfc.tech.MifareUltralight'],
];
const NFC_INTENT_FILTERS = [
NfcAdapter.ACTION_NDEF_DISCOVERED,
NfcAdapter.ACTION_TECH_DISCOVERED,
NfcAdapter.ACTION_TAG_DISCOVERED,
];
export default function useNFCAndroid(
listener: (data: NFC.Result) => void,
options: {
timeout?: number;
/** 检测连接状态 */
checkConnectedDelay?: number;
} = {
timeout: 0,
checkConnectedDelay: 500,
},
) {
/** 是否支持 nfc */
const isSupportNFC = ref<boolean | undefined>(false);
/** 是否监听中 */
const isListening = ref<boolean>(false);
/** 是否已连接 */
const isConnected = ref<boolean>(false);
/** nfc 数据 */
const data = ref<NFC.Result>({
id: new ArrayBuffer(0),
techs: [],
byteArray: [],
hexArray: [],
hexNumberArray: [],
hexString: '',
});
/** ndef 写入方式比较特殊,需要特殊处理 */
let isNdef = ref(false);
const mainActivity: any = plus.android.runtimeMainActivity();
let checkInterval: any = null;
let nfcAdapter: NFC.Adapter | null = null;
let nfcPendingIntent: any = null;
let nfcTag: any = null;
let nfcInstance: any = null;
function checkConnected() {
checkInterval = setInterval(() => {
if (!nfcAdapter) {
isConnected.value = false;
clearInterval(checkInterval);
return;
}
if (!nfcAdapter.isEnabled()) {
isConnected.value = false;
clearInterval(checkInterval);
return;
}
if (!nfcInstance) {
isConnected.value = false;
clearInterval(checkInterval);
return;
}
if (nfcInstance.isConnected()) {
isConnected.value = true;
} else {
isConnected.value = false;
clearInterval(checkInterval);
}
}, options.checkConnectedDelay ?? 500);
}
function innerListener() {
try {
const intent = mainActivity.getIntent();
if (!intent) {
uni.showToast({ title: 'NFC 数据解析失败', icon: 'none' });
return;
}
// 获取id
const id = intent.getByteArrayExtra(EXTRA_ID);
const transform = transformArrayBuffer(id);
let techs: NFC.NfcType[] = [];
let maxTransceiveLength = 0;
let sak = 0;
let sakHex = '';
let sakType = undefined;
let atqaInfo = {
uidType: 0,
storageSize: 0,
manufacturer: 0,
cardFeatures: 0,
reserved: 0,
};
let readWriteStandard: string[] = [];
nfcTag = intent.getParcelableExtra(EXTRA_TAG); // 先获取 Tag 实例
if (nfcTag) {
// 核心方法:通过 Tag.getTechList() 获取原生技术列表数组
const nativeTechList = nfcTag.getTechList();
// 转换为可直接使用的 string 数组(兼容原生返回格式)
techs = Array.isArray(nativeTechList) ? nativeTechList : [];
techs = getAndroidTechs(techs);
isNdef.value = techs.includes('Ndef');
// 根据技术类型 获取实例
const type = techs[0];
switch (type) {
case 'NFC-A':
nfcInstance = NfcA.get(nfcTag);
break;
case 'NFC-B':
nfcInstance = NfcB.get(nfcTag);
break;
case 'NFC-F':
nfcInstance = NfcF.get(nfcTag);
break;
case 'NFC-V':
nfcInstance = NfcV.get(nfcTag);
break;
case 'MIFARE Classic':
nfcInstance = MifareClassic.get(nfcTag);
break;
case 'MIFARE Ultralight':
nfcInstance = MifareUltralight.get(nfcTag);
break;
case 'Ndef':
isNdef.value = true;
nfcInstance = Ndef.get(nfcTag);
break;
case 'Ndef Formatable':
isNdef.value = true;
nfcInstance = NdefFormatable.get(nfcTag);
break;
case 'ISO-DEP':
nfcInstance = IsoDep.get(nfcTag);
break;
default:
nfcInstance = undefined;
break;
}
if (!nfcInstance) {
uni.showToast({ title: 'NFC 实例获取失败', icon: 'none' });
return;
}
nfcInstance?.connect();
maxTransceiveLength = nfcInstance?.getMaxTransceiveLength?.() ?? 0;
readWriteStandard = getReadWriteStandard(techs);
if (techs.includes('NFC-A')) {
// NFC-A 标签的 SAK 信息
const nfcAInstance = NfcA.get(nfcTag);
const sakShortObject =
plus.android.invoke(nfcAInstance, 'getSak') ?? 0;
const sakStr = plus.android.invoke(sakShortObject, 'toString');
sak = Number(sakStr) || 0;
sakType = getSakType(sak);
sakHex = getSakHex(sak);
const atqa = plus.android.invoke(nfcAInstance, 'getAtqa');
atqaInfo = parseAtqaAndroid(atqa);
}
}
// TODO 实际使用中 获取的ndefMessage格式???
// 解析 NDEF 数据
const ndefMessage = intent.getParcelableArrayExtra(EXTRA_NDEF_MESSAGES);
let message: any = '';
if (ndefMessage && ndefMessage.length > 0) {
const records = ndefMessage[0].getRecords();
if (records && records.length > 0) {
const payload = records[0].getPayload();
message = payload
? plus.android.newObject('java.lang.String', payload)
: '';
isNdef.value = true;
}
}
// 返回数据
data.value = {
...data.value,
...transform,
...atqaInfo,
id,
sak,
sakHex,
sakType,
techs,
message,
maxTransceiveLength,
readWriteStandard,
};
// 回调通知外部
listener(data.value);
// 可将改行去除
uni?.__dev_tool_nfc_collection__?.(data.value);
uni.showToast({ title: 'NFC 读取成功', icon: 'none' });
checkConnected();
} catch (error) {
uni.showToast({ title: 'NFC 数据解析失败', icon: 'none' });
}
}
function initNfcAdapter() {
try {
if (!mainActivity) {
uni.showToast({ title: '无法获取主 Activity 实例', icon: 'none' });
return;
}
nfcAdapter = NfcAdapter.getDefaultAdapter(mainActivity);
// 1. 设备不支持 NFC
if (!nfcAdapter) {
isSupportNFC.value = false;
return;
}
// 2. NFC 功能未开启
if (!nfcAdapter.isEnabled()) {
isSupportNFC.value = false;
return;
}
// 3. 初始化成功,注册监听
isSupportNFC.value = true;
} catch (error) {
isSupportNFC.value = false;
}
}
function startHCE() {
if (!isSupportNFC.value) {
uni.showToast({
title: '设备不支持 NFC',
icon: 'none',
});
return;
}
// 前置校验
if (!nfcAdapter) {
uni.showToast({ title: '请先初始化 NFC 适配器', icon: 'none' });
return false;
}
//
if (isListening.value) {
uni.showToast({ title: 'NFC 监听已开启,无需重复操作', icon: 'none' });
return true;
}
try {
// 1. 注册 newintent 事件监听
plus.globalEvent.addEventListener('newintent', innerListener);
// 2. 构建前台调度 PendingIntent(缓存实例,避免重复创建)
if (!nfcPendingIntent) {
const intent = new Intent(mainActivity, mainActivity.getClass());
intent.addFlags(INTENT_FLAGS_SINGLE_TOP);
nfcPendingIntent = PendingIntent.getActivity(
mainActivity,
0,
intent,
PENDING_INTENT_FLAGS,
);
}
// 3. 构建 Intent 过滤器
const filters = NFC_INTENT_FILTERS.map(
(filter) => new IntentFilter(filter),
);
// 4. 启用前台调度(核心:App 前台优先捕获 NFC 事件)
nfcAdapter?.enableForegroundDispatch(
mainActivity,
nfcPendingIntent,
filters,
NFC_TECH_LISTS,
);
// 5. 更新监听状态
isListening.value = true;
uni.showToast({
title: '开始监听NFC',
icon: 'none',
});
return true;
} catch (e) {
uni.showToast({ title: 'NFC 监听开启失败', icon: 'none' });
isListening.value = false;
return false;
}
}
function stopHCE() {
// 前置校验
if (!isSupportNFC.value) {
uni.showToast({
title: '设备不支持 NFC',
icon: 'none',
});
return;
}
// 前置校验
if (!nfcAdapter) {
uni.showToast({ title: '请先初始化 NFC 适配器', icon: 'none' });
return false;
}
//
if (!isListening.value) {
uni.showToast({ title: 'NFC 监听已关闭,无需重复操作', icon: 'none' });
return true;
}
try {
// 1. 禁用前台调度
nfcAdapter.disableForegroundDispatch(mainActivity);
// 2. 更新监听状态
isListening.value = false;
isConnected.value = false;
uni.showToast({
title: '停止监听NFC',
icon: 'none',
});
clearInterval(checkInterval);
return true;
} catch (e) {
uni.showToast({ title: 'NFC 监听停止失败', icon: 'error' });
return false;
}
}
function connect() {
if (!isSupportNFC.value) {
uni.showToast({ title: '设备不支持 NFC', icon: 'none' });
return;
}
if (!isListening.value) {
uni.showToast({ title: '请先开启前台调度监听', icon: 'none' });
return;
}
if (!nfcInstance) {
uni.showToast({ title: '请先靠近 NFC 标签', icon: 'none' });
return;
}
try {
if (!nfcInstance.isConnected()) {
// 补充连接状态判断
nfcInstance.connect();
isConnected.value = true;
uni.showToast({ title: 'NFC 连接成功', icon: 'none' });
} else {
isConnected.value = true;
uni.showToast({ title: 'NFC 已处于连接状态', icon: 'none' });
}
checkConnected();
} catch (error) {
uni.showToast({ title: 'NFC 连接失败', icon: 'none' });
isConnected.value = false;
}
}
function close() {
if (!isSupportNFC.value) {
uni.showToast({ title: '设备不支持 NFC', icon: 'none' });
return;
}
if (!nfcInstance) {
uni.showToast({ title: '无连接中的 NFC 标签', icon: 'none' });
return;
}
try {
if (nfcInstance.isConnected()) {
nfcInstance.close();
}
// 清空缓存,重置状态
nfcTag = null;
nfcInstance = null;
isConnected.value = false;
isNdef.value = false;
clearInterval(checkInterval);
uni.showToast({ title: 'NFC 连接已关闭', icon: 'none' });
} catch (error) {
uni.showToast({ title: 'NFC 连接关闭失败', icon: 'none' });
}
}
function offHCEMessage() {
try {
if (!isSupportNFC.value) {
uni.showToast({ title: '设备不支持 NFC,无需移除监听', icon: 'none' });
return true;
}
// 1. 移除 newintent 事件监听
plus.globalEvent.removeEventListener('newintent', innerListener);
// 2. 停止前台调度,关闭连接
if (nfcAdapter && isListening.value) {
nfcAdapter.disableForegroundDispatch(mainActivity);
}
if (nfcInstance && nfcInstance.isConnected()) {
nfcInstance.close();
}
// 3. 清空所有缓存,重置状态
nfcPendingIntent = null;
nfcTag = null;
nfcInstance = null;
isListening.value = false;
isConnected.value = false;
isNdef.value = false;
clearInterval(checkInterval);
uni.showToast({ title: 'NFC 监听已移除', icon: 'none' });
return true;
} catch (e) {
uni.showToast({ title: 'NFC 监听移除失败', icon: 'none' });
return false;
}
}
/**
* TODO 寻找一张卡来进行测试写入
*
* @param {ArrayBuffer} writeData
* @return {*}
*/
function write(writeData: ArrayBuffer) {
if (!nfcTag) {
uni.showToast({ title: '请先靠近 NFC 标签建立连接', icon: 'none' });
return;
}
try {
uni.showToast({
title: '开始写入数据,请勿断开连接',
icon: 'none',
});
// 1. 转换 ArrayBuffer 为 Java 字节数组
const uint8Array = new Uint8Array(writeData);
const text = Array.from(uint8Array)
.map((byte) => String.fromCharCode(byte))
.join('');
const textBytes = plus.android.invoke(text, 'getBytes');
// 2. 构建 NDEF 记录与消息
const mimeBytes = plus.android.invoke('text/plain', 'getBytes');
const textRecord = new NdefRecord(
TNF_MIME_MEDIA,
mimeBytes,
plus.android.invoke('', 'getBytes'),
textBytes,
);
const message = new NdefMessage([textRecord]);
// 3. 写入数据(支持已格式化和未格式化标签)
nfcInstance.connect();
if (!nfcInstance.isWritable()) {
uni.showToast({ title: 'NFC 标签不允许写入', icon: 'none' });
nfcInstance.close();
return;
}
if (nfcInstance.getMaxTransceiveLength() < message.toByteArray().length) {
uni.showToast({ title: '写入数据超出标签容量', icon: 'none' });
nfcInstance.close();
return;
}
nfcInstance.writeNdefMessage(message);
nfcInstance.close();
checkConnected();
uni.showToast({ title: '数据写入成功', icon: 'none' });
} catch (e) {
uni.showToast({ title: '数据写入失败', icon: 'none' });
}
}
/**
* TODO 寻找一张卡来进行测试写入
*
* @param {NFC.NdefData} data
*/
function writeNdf(data: NFC.NdefData) {}
onLoad(() => {
initNfcAdapter();
});
onUnload(() => {
stopHCE();
offHCEMessage();
});
return {
data,
isNdef,
isSupportNFC,
isListening,
isConnected,
nfcInstance,
startHCE,
stopHCE,
connect,
close,
write,
writeNdf,
};
}