HarmonyOS之深入解析NFC的功能和使用
【摘要】
一、简介
NFC(Near Field Communication,近距离无线通信技术) 是一种非接触式识别和互联技术,让移动设备、消费类电子产品、PC 和智能设备之间可以进行近距离无线通信。Harmo...
一、简介
- NFC(Near Field Communication,近距离无线通信技术) 是一种非接触式识别和互联技术,让移动设备、消费类电子产品、PC 和智能设备之间可以进行近距离无线通信。
- HarmonyOS 的 NFC 提供的功能有:
-
- NFC 基础查询:在进行 NFC 功能开发之前,开发者应该先确认设备是否支持 NFC 功能、NFC 是否打开等基本信息。
-
- 访问安全单元(Secure Element,简称为 SE):SE 可用于保存重要信息,应用可以访问指定 SE,并发送数据到 SE 上。
-
- 卡模拟:设备可以模拟卡片,替代卡片完成对应操作,如模拟门禁卡、公交卡等。
-
- NFC 消息通知:通过这个模块,开发者可以获取 NFC 开关状态改变的消息以及 NFC 的场强消息。
二、NFC 基础查询
- 要进行 NFC 功能开发,需要设备支持 NFC 功能。
- 开发者可以通过 NfcController 类的方法 isNfcAvailable() 来确认设备是否支持 NFC 功能。如果设备支持 NFC 功能,可通过 isNfcOpen() 来查询 NFC 的开关状态。
- 示例代码如下:
// 查询本机是否支持NFC
if (context != null) {
NfcController nfcController = NfcController.getInstance(context);
} else {
return;
}
boolean isAvailable = nfcController.isNfcAvailable();
if (isAvailable) {
// 调用查询NFC是否打开接口,返回值为NFC是否是打开的状态
boolean isOpen = nfcController.isNfcOpen();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
三、访问安全单元
① 应用场景
- 安全单元(Secure Element,简称为 SE)可用于保存重要信息,应用或者其他模块可以通过接口完成以下功能:
-
- 获取安全单元的个数和名称。
-
- 判断安全单元是否在位。
-
- 在指定安全单元上打开基础通道。
-
- 在指定安全单元上打开逻辑通道。
-
- 发送 APDU(Application Protocol Data Unit)数据到安全单元上。
② API 说明
- NFC 访问安全单元功能的的主要接口:
类名 | 接口名 | 功能描述 |
---|---|---|
SEService | SEService() | 创建一个安全单元服务的实例 |
isConnected() | 查询安全单元服务是否已连接 | |
shutdown() | 关闭安全单元服务 | |
getReaders() | 获取全部安全单元 | |
getVersion() | 获得安全单元服务的版本 | |
OnCallback | 用于回调的内部类,用于定义回调接口。 在服务连接成功后,回调该接口通知应用 |
|
Reader | getName() | 获取安全单元的名称 |
isSecureElementPresent() | 检查安全单元是否在位 | |
openSession() | 打开当前安全单元上的session | |
closeSession() | 关闭当前安全单元上的所有session | |
Session | openBasicChannel(Aid aid) | 打开基础通道 |
openLogicalChannel(Aid aid) | 创建逻辑通道 | |
getATR() | 获得重设安全单元指令的响应 | |
closeSessionChannels() | 关闭当前session的所有通道 | |
Channel | isClosed() | 判断通道是否关闭 |
isBasicChannel() | 判断是否是基础通道 | |
transmit(byte[] command) | 发送指令到安全单元 | |
getSelectResponse() | 获得应用程序选择指令的响应 | |
closeChannel() | 关闭通道 | |
Aid | Aid(byte[] aid, int offset, int length) | 构造一个AID类的实例 |
isAidValid() | 查询AID是否有效 | |
getAidBytes() | 获取AID的字节数组形式的值 |
③ 使用流程
- 调用 SEService 类的构造函数,创建一个安全单元服务的实例,用于访问安全单元。
- 调用 isConnected() 接口,查询安全单元服务的连接状态。
- 调用 getReaders() 接口,获取本机的全部安全单元。
- 调用 Reader 类的 openSession() 接口打开 Session,返回一个打开的 Session 实例。
- 调用 Session 类的 openBasicChannel(Aid aid) 接口打开基础通道,或者调用 openLogicalChannel(Aid aid) 接口打开逻辑通道,返回一个打开通道 Channel 实例。
- 调用 Channel 类的 transmit(byte[] command),发送 APDU 到安全单元。
- 调用 Channel 类的 closeChannel() 接口关闭通道。
- 调用 Session 类的 closeSessionChannels() 接口关闭 Session 的所有通道。
- 调用 Reader 类的 closeSessions() 接口关闭安全单元的所有 Session。
- 调用 SEService 类的 shutdown() 接口关闭安全单元服务。
private static final String ESE = "eSE";
private class AppServiceConnectedCallback implements SEService.OnCallback {
@Override
public void serviceConnected() {
// 应用自实现
}
}
// 创建安全单元服务实例
SEService sEService = new SEService(context, new AppServiceConnectedCallback());
// 查询安全单元服务的连接状态
boolean isConnected = sEService.isConnected();
// 获取本机的全部安全单元,并获取指定的安全单元eSE
Reader[] elements = sEService.getReaders();
Reader eSe = null;
for (int i = 0; i < elements.length; i++) {
if (ESE.equals(elements[i].getName())) {
eSe = elements[i];
break;
}
}
if (eSe == null) {
return;
}
// 查询安全单元是否在位
boolean isPresent = eSe.isSecureElementPresent();
// 打开Session
Optional<Session> optionalSession = eSe.openSession();
Session session = optionalSession.orElse(null);
if (session == null) {
return;
}
// 打开通道
if (eSe != null) {
byte[] aidValue = new byte[]{(byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04, (byte)0x05};
// 创建Aid实例
Aid aid = new Aid(aidValue, 0, aidValue.length);
// 打开基础通道
Optional<Channel> optionalChannel = session.openBasicChannel(aid);
Channel basicChannel = optionalChannel.orElse(null);
// 打开逻辑通道
optionalChannel = session.openLogicalChannel(aid);
Channel logicalChannel = optionalChannel.orElse(null);
// 发送指令给安全单元,返回值为安全单元对指令的响应
byte[] resp = logicalChannel.transmit(new byte[]{(byte)0x00, (byte)0xa4, (byte)0x00, (byte)0x00, (byte)0x02, (byte)0x00, (byte)0x00});
// 关闭通道资源
if (basicChannel.isPresent()) {
basicChannel.closeChannel();
}
if (logicalChannel.isPresent()) {
logicalChannel.closeChannel();
}
// 关闭Session资源
session.close();
// 关闭安全单元资源
eSe.closeSessions();
// 关闭安全单元服务资源
sEService.shutdown();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
四、卡模拟功能
① 应用场景
- 设备可以模拟卡片,替代卡片完成对应操作,如模拟门禁卡、公交卡等。
- 应用或者其他模块可以通过接口完成以下功能:
-
- 查询是否支持指定安全单元的卡模拟功能,安全单元包括 HCE(Host Card Emulation)、ESE(Embedded Secure Element)和 SIM(Subscriber Identity Module)卡。
-
- 打开或关闭指定技术类型的卡模拟,并查询卡模拟状态。
-
- 获取 NFC 信息,包括当前激活的安全单元、Hisee 上电状态、是否支持RSSI(Received Signal Strength Indication)查询等。
-
- 根据 NFC 服务的类型获取刷卡时选择服务的方式,包括支付(Payment)类型和非支付(Other)类型。
-
- 动态设置和注销前台优先应用。
-
- NFC 应用的 AID(Application Identifier,应用标识)相关操作,包括注册和删除应用的 AID、查询应用是否是指定 AID 的默认应用、获取应用的 AID 等。
-
- 定义 Host 和 OffHost 服务的抽象类,应用可以通过继承抽象类来实现 NFC 卡模拟功能。
② API 说明
- NFC 卡模拟功能的主要接口说明如下,在使用对应的接口前,需要申请 ohos.permission.NFC_CARD_EMULATION 权限。
- NFC 卡模拟功能的主要接口如下表所示:
类名 | 接口名 | 功能描述 |
---|---|---|
CardEmulation | getInstance(NfcController controller) | 创建一个卡模拟类的实例 |
isSupported(int feature) | 查询是否支持卡模拟功能 | |
setListenMode(int mode) | 设置卡模拟模式 | |
isListenModeEnabled() | 查询卡模拟功能是否打开 | |
getNfcInfo(String key) | 获取NFC的信息 | |
getSelectionType(String category) | 根据NFC服务的类型获取刷卡时选择服务的方式 | |
registerForegroundPreferred(Ability appAbility, ElementName appName) | 动态设置前台优先应用 | |
unregisterForegroundPreferred(Ability appAbility) | 取消设置前台优先应用 | |
isDefaultForAid(ElementName appName, String aid) | 判断应用是否是指定AID的默认处理应用 | |
registerAids(ElementName appName, String type, List aids) | 给应用注册指定类型的AID | |
removeAids(ElementName appName, String type) | 删除应用的指定类型的AID | |
getAids(ElementName appName, String type) | 获取应用中指定类型的AID列表 | |
HostService | sendResponse(byte[] response) | 发送响应的数据到对端设备 |
handleRemoteCommand(byte[] cmd, IntentParams params) | 处理对端设备发送的命令 | |
disabledCallback(int errCode) | 连接异常的回调 |
③ 查询是否支持卡模拟功能
- 调用 NfcController 类的 getInstance(Context context) 接口,获取 NfcController 实例。
- 调用 CardEmulation 类的 getInstance(NfcController controller) 接口,获取 CardEmulation 实例,去管理本机卡模拟模块操作。
- 调用 isSupported(int feature) 接口去查询是否支持 HCE、UICC、ESE 卡模拟。
// 获取NFC控制对象
NfcController nfcController = NfcController.getInstance(context);
// 获取卡模拟控制对象
CardEmulation cardEmulation = CardEmulation.getInstance(nfcController);
// 查询是否支持HCE、UICC、ESE卡模拟,返回值表示是否支持对应安全单元的卡模拟
boolean isSupportedHce = cardEmulation.isSupported(CardEmulation.FEATURE_HCE);
boolean isSupportedUicc = cardEmulation.isSupported(CardEmulation.FEATURE_UICC);
boolean isSupportedEse = cardEmulation.isSupported(CardEmulation.FEATURE_ESE);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
④ 开关卡模拟及查询卡模拟状态
- 调用 NfcController 类的 getInstance(Context context) 接口,获取 NfcController 实例。
- 调用 CardEmulation 类的 getInstance(NfcController controller) 接口,获取 CardEmulation 实例,去管理本机卡模拟模块操作。
- 调用 setListenMode(int mode) 接口去打开或者关闭卡模拟。
- 调用 isListenModeEnabled() 接口去查询卡模拟是否打开。
// 获取NFC控制对象
NfcController nfcController = NfcController.getInstance(context);
// 获取卡模拟控制对象
CardEmulation cardEmulation = CardEmulation.getInstance(nfcController);
// 打开卡模拟
cardEmulation.setListenMode(CardEmulation.ENABLE_MODE_ALL);
// 调用查询卡模拟开关状态的接口,返回值为卡模拟是否是打开的状态
boolean isEnabled = cardEmulation.isListenModeEnabled();
// 关闭卡模拟
cardEmulation.setListenMode(CardEmulation.DISABLE_MODE_A_B);
// 调用查询卡模拟开关状态的接口,返回值为卡模拟是否是打开的状态
isEnabled = cardEmulation.isListenModeEnabled();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
⑤ 获取 NFC 信息
- 调用 NfcController 类的 getInstance(Context context) 接口,获取 NfcController 实例。
- 调用 CardEmulation 类的 getInstance(NfcController controller) 接口,获取 CardEmulation 实例,去管理本机卡模拟模块操作。
- 调用 getNfcInfo(String key) 接口去获取 NFC 信息。
// 获取NFC控制对象
NfcController nfcController = NfcController.getInstance(context);
// 获取卡模拟控制对象
CardEmulation cardEmulation = CardEmulation.getInstance(nfcController);
// 查询本机当前使能的安全单元类型
String seType = cardEmulation.getNfcInfo(CardEmulation.KEY_ENABLED_SE_TYPE); // ENABLED_SE_TYPE_ESE
// 查询Hisee上电状态
String hiseeState = cardEmulation.getNfcInfo(CardEmulation.KEY_HISEE_READY);
// 查询是否支持RSSI的查询
String rssiAbility = cardEmulation.getNfcInfo(CardEmulation.KEY_RSSI_SUPPORTED);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
⑥ 根据 NFC 服务的类型获取刷卡时选择服务的方式
- 调用 NfcController 类的 getInstance(Context context) 接口,获取 NfcController 实例。
- 调用 CardEmulation 类的 getInstance(NfcController controller) 接口,获取 CardEmulation 实例,去管理本机卡模拟模块操作。
- 调用 getSelectionType(Sring category) 接口去获取选择服务的方式。
// 获取NFC控制对象
NfcController nfcController = NfcController.getInstance(context);
// 获取卡模拟控制对象
CardEmulation cardEmulation = CardEmulation.getInstance(nfcController);
// 获取选择服务的方式
int result = cardEmulation.getSelectionType(CardEmulation.CATEGORY_PAYMENT); // SELECTION_TYPE_PREFER_DEFAULT
result = cardEmulation.getSelectionType(CardEmulation.CATEGORY_OTHER); // SELECTION_TYPE_ASK_IF_CONFLICT
- 1
- 2
- 3
- 4
- 5
- 6
- 7
⑦ 动态设置和注销前台优先应用
- 调用 NfcController 类的 getInstance(Context context) 接口,获取 NfcController 实例。
- 调用 CardEmulation 类的 getInstance(NfcController controller) 接口,获取 CardEmulation 实例,去管理本机卡模拟模块操作。
- 调用 registerForegroundPreferred(Ability appAbility, ElementName appName) 接口去动态设置前台优先应用。
- 调用 unregisterForegroundPreferred(Ability appAbility) 接口去取消设置前台优先应用。
// 获取NFC控制对象
NfcController nfcController = NfcController.getInstance(context);
// 获取卡模拟控制对象
CardEmulation cardEmulation = CardEmulation.getInstance(nfcController);
// 动态设置前台优先应用
Ability ability = new Ability();
cardEmulation.registerForegroundPreferred(ability, new ElementName());
// 注销前台优先应用
cardEmulation.unregisterForegroundPreferred(ability);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
⑧
五、NFC 消息通知
① 应用场景
- NFC 消息通知是 HarmonyOS 内部或者与应用之间跨进程通讯的机制,注册者在注册消息通知后,一旦符合条件的消息被发出,注册者即可接收到该消息。
② API 说明
- NFC 消息通知的相关广播介绍:
描述 | 通知名 | 附加参数 |
---|---|---|
NFC状态 | usual.event.nfc.action.ADAPTER_STATE_CHANGED | extra_nfc_state |
进场消息 | usual.event.nfc.action.RF_FIELD_ON_DETECTED | extra_nfc_transaction |
离场消息 | usual.event.nfc.action.RF_FIELD_OFF_DETECTED | - |
③ 注册并获取 NFC 状态改变消息
- 构建消息通知接收者 NfcStateEventSubscriber。
- 注册 NFC 状态改变消息。
- NfcStateEventSubscriber 接收并处理 NFC 状态改变消息。
// 构建消息接收者/注册者
class NfcStateEventSubscriber extends CommonEventSubscriber {
NfcStateEventSubscriber (CommonEventSubscribeInfo info) {
super(info);
}
@Override
public void onReceiveEvent(CommonEventData commonEventData) {
if (commonEventData == null || commonEventData.getIntent() == null) {
return;
}
if (NfcController.STATE_CHANGED.equals(commonEventData.getIntent().getAction())) {
IntentParams params = commonEventData.getIntent().getParams();
int currState = commonEventData.getIntent().getIntParam(NfcController.EXTRA_NFC_STATE, NfcController.STATE_OFF);
}
}
}
// 注册消息
MatchingSkills matchingSkills = new MatchingSkills();
// 增加获取NFC状态改变消息
matchingSkills.addEvent(NfcController.STATE_CHANGED);
matchingSkills.addEvent(CommonEventSupport.COMMON_EVENT_NFC_ACTION_ADAPTER_STATE_CHANGED);
CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);
NfcStateEventSubscriber subscriber = new NfcStateEventSubscriber(subscribeInfo);
try {
CommonEventManager.subscribeCommonEvent(subscriber);
} catch (RemoteException e) {
HiLog.error(TAG, "doSubscribe occur exception: %{public}s" ,e.toString());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
④ 注册并获取 NFC 场强消息
- 构建消息通知接收者 NfcFieldOnAndOffEventSubscriber。
- 注册 NFC 场强消息。
- NfcFieldOnAndOffEventSubscriber 接收并处理 NFC 场强消息。
// 构建消息接收者/注册者
class NfcFieldOnAndOffEventSubscriber extends CommonEventSubscriber {
NfcFieldOnAndOffEventSubscriber (CommonEventSubscribeInfo info) {
super(info);
}
@Override
public void onReceiveEvent(CommonEventData commonEventData) {
if (commonEventData == null || commonEventData.getIntent() == null) {
return;
}
if (NfcController.FIELD_ON_DETECTED.equals(commonEventData.getIntent().getAction())) {
IntentParams params = commonEventData.getIntent().getParams();
if (params == null) {
HiLog.info(TAG, "Pure FIELD_ON_DETECTED");
} else {
HiLog.info(TAG, "Transaction FIELD_ON_DETECTED");
Intent transactionIntent = (Intent) params.getParam("transactionIntent");
}
} else if (NfcController.FIELD_OFF_DETECTED.equals(commonEventData.getIntent().getAction())) {
HiLog.info(TAG, "FIELD_OFF_DETECTED");
}
HiLog.info(TAG, "NfcFieldOnAndOffEventSubscriber onReceiveEvent: %{public}s", commonEventData.getIntent().getAction());
}
}
// 注册消息
MatchingSkills matchingSkills = new MatchingSkills();
// 增加获取NFC状态改变消息
matchingSkills.addEvent(NfcController.FIELD_ON_DETECTED);
matchingSkills.addEvent(NfcController.FIELD_OFF_DETECTED);
CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);
HiLog.info(TAG, "subscribeInfo permission: %{public}s", subscribeInfo.getPermission());
NfcFieldOnAndOffEventSubscriber subscriber = new NfcFieldOnAndOffEventSubscriber(subscribeInfo);
try {
CommonEventManager.subscribeCommonEvent(subscriber);
} catch (RemoteException e) {
HiLog.error(TAG, "doSubscribe occur exception: %{public}s", e.toString());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
文章来源: blog.csdn.net,作者:Serendipity·y,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/Forever_wj/article/details/118393557
【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)