设备接入物联网平台之前,需通过身份认证。本文介绍如何将Android Link SDK进行初始化,实现将设备接入物联网平台。
前提条件
已创建产品和设备。具体操作,请参见创建产品和设备。
已获取设备的认证信息以及设备的接入域名。具体请参见需配置的文件参数说明。
背景信息
Android Link SDK支持设备密钥和ID²认证进行设备身份认证。
设备密钥:
认证方式
注册方式
说明
不涉及
每台设备烧录自己的设备证书(ProductKey、DeviceName和DeviceSecret)。
预注册
同一产品下设备烧录相同产品证书(ProductKey和ProductSecret)。
产品需开启动态注册功能。
设备通过动态注册获取DeviceSecret。
免预注册
同一产品下设备烧录相同产品证书(ProductKey和ProductSecret)。
产品需开启动态注册功能。
设备通过动态注册获取ClientID与DeviceToken的组合。
说明一型一密预注册和免预注册的区别,请参见预注册和免预注册的区别。
ID²认证:ID²是一种物联网设备的可信身份标识,具备不可篡改、不可伪造等安全属性。
具体代码实现,请参见Demo中的InitManager.java
。
一机一密
一机一密的设备认证方式的示例代码如下:
AppLog.setLevel(ALog.LEVEL_DEBUG);
final LinkKitInitParams params = new LinkKitInitParams();
String productKey = "${YourProductKey}";
String deviceName = "${YourDeviceName}";
String deviceSecret = "${YourDeviceSecret}";
String productSecret = "";
//Step1: 构造设备认证信息
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; // 产品类型
deviceInfo.deviceName = deviceName; // 设备名称
deviceInfo.deviceSecret = deviceSecret; // 设备密钥
deviceInfo.productSecret = productSecret; // 产品密钥
params.deviceInfo = deviceInfo;
//Step2: 全局默认域名
IoTApiClientConfig userData = new IoTApiClientConfig();
params.connectConfig = userData;
//Step3: 物模型缓存
Map<String, ValueWrapper> propertyValues = new HashMap<>();
/**
* 物模型的数据会缓存到该字段中,不可删除或者设置为空,否则功能会异常
* 用户调用物模型上报接口之后,物模型会有相关数据缓存。
*/
params.propertyValues = propertyValues;
//Step4: mqtt设置
/**
* Mqtt 相关参数设置,包括接入点等信息,具体参见deviceinfo文件说明
* 域名、产品密钥、认证安全模式等;
*/
IoTMqttClientConfig clientConfig = new IoTMqttClientConfig();
clientConfig.receiveOfflineMsg = false;//cleanSession=1 不接受离线消息
//mqtt接入点信息
clientConfig.channelHost = "${YourMqttHostUrl}:8883";
params.mqttClientConfig = clientConfig;
//如果灭屏情况下经常出现设备离线,请参考下述的"设置自定义心跳和解决灭屏情况下的心跳不准问题"一节
//Step5: 高阶功能配置,除物模型外,其余默认均为关闭状态
IoTDMConfig ioTDMConfig = new IoTDMConfig();
// 默认开启物模型功能,物模型初始化(包含请求云端物模型)完成后才返回onInitDone
ioTDMConfig.enableThingModel = true;
// 默认不开启网关功能,开启之后,初始化的时候会初始化网关模块,获取云端网关子设备列表
ioTDMConfig.enableGateway = false;
// 默认不开启,是否开启日志推送功能
ioTDMConfig.enableLogPush = false;
params.ioTDMConfig = ioTDMConfig;
//Step6: 下行消息处理回调设置
LinkKit.getInstance().registerOnPushListener(new IConnectNotifyListener() {
@Override
public void onNotify(String s, String s1, AMessage aMessage) {
//TODO: 处理下行消息的回调,请参考文档
}
@Override
public boolean shouldHandle(String s, String s1) {
return true; //TODO 根据实际情况设置,请参考文档
}
@Override
public void onConnectStateChange(String connectId, ConnectState connectState) {
// 对应连接类型的连接状态变化回调,具体连接状态参考SDK ConnectState
AppLog.d(TAG, "onConnectStateChange() called with: connectId = [" + connectId + "], connectState = [" + connectState + "]");
//首次连云可能失败。对于首次连云失败,SDK会报出ConnectState.CONNECTFAIL这种状态。对于这种场景,用户可以尝试若干次后退出,也可以一直重试直到连云成功
//TODO: 以下是首次建连时用户主动重试的一个参考实现,用户可以打开下面注释使能下述代码
// if(connectState == ConnectState.CONNECTFAIL){
// try{
// Thread.sleep(5000);
// PersistentNet.getInstance().reconnect();
// }catch (Exception e){
// AppLog.d(TAG, "exception is " + e);
// };
// AppLog.d(TAG, "onConnectStateChange() try to reconnect when connect failed");
// }
//SDK连云成功后,后续如果网络波动导致连接断开时,SDK会抛出ConnectState.DISCONNECTED这种状态。在这种情况下,SDK会自动尝试重连,重试的间隔是1s、2s、4s、8s...128s...128s,到了最大间隔128s后,会一直以128s为间隔重连直到连云成功。
}
});
//对于一型一密免预注册的设备, 设备连云时要用上deviceToken和clientId
//Step7: 一型一密免预注册设置,默认关闭
// MqttConfigure.deviceToken = DemoApplication.deviceToken;
// MqttConfigure.clientId = DemoApplication.clientId;
//Step8: H2文件上传设置
/**
* 如果要用到HTTP2文件上传, 需要用户设置域名.默认关闭
*/
// IoTH2Config ioTH2Config = new IoTH2Config();
// ioTH2Config.clientId = "client-id";
// ioTH2Config.endPoint = "https://" + productKey + ioTH2Config.endPoint;// 线上环境
// params.iotH2InitParams = ioTH2Config;
/**
* 设备初始化建联
* onError 初始化建联失败,如果因网络问题导致初始化失败,需要用户重试初始化
* onInitDone 初始化成功
*/
LinkKit.getInstance().init(getAppContext(), params, new ILinkKitConnectListener() {
@Override
public void onError(AError error) {
ALog.d(TAG, "onError() called with: error = [" + (error) + "]");
}
@Override
public void onInitDone(Object data) {
ALog.d(TAG, "onInitDone() called with: data = [" + data + "]");
//TODO 开始用户自己的业务
}
});
一型一密
一型一密又称动态注册,用于向物联网平台获取设备的密钥,具体分为免预注册和预注册两种方式。使用该功能前,需要确保:
已在物联网平台创建产品已开启动态注册开关。
Demo的
deviceinfo
文件中的deviceSecret的值为空,productSecret不为空。请确保已执行下面示例代码中的step 1、step 2、step 3。
动态注册成功或失败之后,需要断开当前的动态注册长连接,具体参考step 4。
为了您的设备安全,一型一密的认证方式获取到设备密钥后,请将其持久固化至设备,若需连接至物联网平台,请参考上述一机一密流程。
免预注册和预注册区别如下:
区别 | 预注册 | 免预注册 |
通信协议 | MQTT、HTTPS | MQTT |
地域支持 |
| 华东2(上海)、华北2(北京) |
返回的设备密钥 | DeviceSecret具体使用方式,请参考上述一机一密示例中的Step1。 | 设备的ClientID和DeviceToken,请将其持久固化至设备,以便连云等其他功能使用。具体使用方式,请参考上述一机一密示例中的Step7。 |
添加设备 | 需要在物联网平台预注册设备DeviceName。 | 不需要在物联网平台预注册设备DeviceName。 |
使用次数限制 |
| 物联网平台允许最多5个物理设备使用同一组ProductKey、ProductSecret、DeviceName进行激活,并为不同物理设备颁发不同的ClientID、DeviceToken。 |
具体代码实现,请参见Demo中的DemoApplication.java
。
示例代码如下:
String productKey = "${YourProductKey}";
String deviceName = "${YourDeviceName}";
String deviceSecret = "${YourDeviceSecret}";
String productSecret = "";
MqttInitParams initParams = new MqttInitParams(productKey, productSecret, deviceName, deviceSecret, MqttConfigure.MQTT_SECURE_MODE_TLS);
//动态注册step1: 确定一型一密的类型(免预注册, 还是非免预注册)
//case 1: 如果registerType里面填写了regnwl, 表明设备的一型一密方式为免预注册(即无需创建设备)
//case 2: 如果这个字段为空, 或填写"register", 则表示为需要预注册的一型一密(需要实现创建设备)
initParams.registerType = "";
//动态注册step2: 设置动态注册的注册接入点域名
MqttConfigure.mqttHost = "${YourMqttHostUrl}:8883";;
//动态注册step3: 对于企业实例, 或者2021年07月30日之后(含当日)开通的物联网平台服务下公共实例
//对于2021年07月30日之前(不含当日)开通的物联网平台服务下公共实例,该字段为空字符串""
MqttConfigure.registerInstanceId = "${YourInstanceId}";
final Object lock = new Object();
LinkKit.getInstance().deviceDynamicRegister(this, initParams, new IOnCallListener() {
@Override
public void onSuccess(com.aliyun.alink.linksdk.channel.core.base.ARequest request, com.aliyun.alink.linksdk.channel.core.base.AResponse response) {
ALog.i(TAG, "onSuccess() called with: request = [" + request + "], response = [" + response + "]");
// response.data is byte array
try {
String responseData = new String((byte[]) response.data);
JSONObject jsonObject = JSONObject.parseObject(responseData);
String pk = jsonObject.getString("productKey");
String dn = jsonObject.getString("deviceName");
// 一型一密预注册返回
String deviceSecret = jsonObject.getString("deviceSecret");
// 一型一密免预注册返回
String clientId = jsonObject.getString("clientId");
String deviceToken = jsonObject.getString("deviceToken");
//TODO: 请用户保存用户密钥,不要在此做连云的操作,要等step 4执行完成后再做连云的操作(例如在其onSuccess分支中进行连云)
//让等待的api继续执行
synchronized (lock){
lock.notify();
}
} catch (Exception e) {
}
}
@Override
public void onFailed(com.aliyun.alink.linksdk.channel.core.base.ARequest request, com.aliyun.alink.linksdk.channel.core.base.AError error) {
ALog.e(TAG, "onFailed() called with: request = [" + request + "], error = [" + error + "]");
//让等待的api继续执行
synchronized (lock){
lock.notify();
}
}
@Override
public boolean needUISafety() {
return false;
}
});
try{
//等待下行报文,一般1s内就有回复
synchronized (lock){
lock.wait(3000);
}
//step 4: 退出动态注册
//不要在LinkKit.getInstance().deviceDynamicRegister回调中执行下述函数,否则会报错
LinkKit.getInstance().stopDeviceDynamicRegister(10 * 1000, null, new IMqttActionListener() {
@Override
public void onSuccess(IMqttToken iMqttToken) {
ALog.d(TAG, "onSuccess() called with: iMqttToken = [" + iMqttToken + "]");
// TODO: 在此处参考一机一密进行连云和初始化
}
@Override
public void onFailure(IMqttToken iMqttToken, Throwable throwable) {
ALog.w(TAG, "onFailure() called with: iMqttToken = [" + iMqttToken + "], throwable = [" + throwable + "]");
}
});
}catch (Exception e){
};
ID²认证
。/**
* 在物联网平台创建使用iTLS认证方式的设备,采用该方式初始化
* 产品需要ID²授权
*/
IoTMqttClientConfig clientConfig = new IoTMqttClientConfig(productKey, deviceName, deviceSecret);
clientConfig.channelHost = productKey + ".itls.cn-shanghai.aliyuncs.com:1883";
clientConfig.productSecret = productSecret;
clientConfig.secureMode = 8;
linkKitInitParams.mqttClientConfig = clientConfig;
//对于企业版实例, 或2021年07月30日之后(含当日)开通的物联网平台服务下公共实例,通过如下方式指定实例ID,其中${实例id}替换为具体ID值,格式为iot-*******
MqttConfigure.extraMqttClientIdItems=",instanceId=" + "${实例id}";
示例代码中的参数clientConfig.channelHost
为设备的接入域名。
更多设置
您可以设置以下参数,实现设备接入相关的更多设置。
MQTT连接:
配置项
说明
相关代码
保活时间
设置设备的保活时间。通过该设置实现设备与物联网平台保持长连接。
说明当前SDK默认保活时间为65秒。
您可设置的心跳周期在30 ~ 1200秒。
// interval 单位秒 MqttConfigure.setKeepAliveInterval(int interval);
QoS等级
设置QoS(Quality of Service)等级,即物联网平台与设备之间保证交付信息的协议。仅支持:
0
:最多一次。1
:最少一次。
MqttPublishRequest request = new MqttPublishRequest(); // 支持 0 和 1, 默认0 request.qos = 0; request.isRPC = false; request.topic = topic.replace("request", "response"); String resId = topic.substring(topic.indexOf("rrpc/request/")+13); request.msgId = resId; // TODO 用户根据实际情况填写 request.payloadObj = "{\"id\":\"" + resId + "\", \"code\":\"200\"" + ",\"data\":{} }";
离线消息
通过cleanSession,设置是否接收离线消息。
IoTMqttClientConfig clientConfig = new IoTMqttClientConfig(); // 对应 receiveOfflineMsg = !cleanSession, 默认不接受离线消息 clientConfig.receiveOfflineMsg = true;
客户端ID
通过clientId设置客户端ID。clientId的详细说明,请参见clientId说明。
MqttConfigure.clientId = "abcdef******";
重连机制
通过automaticReconnect参数的值,设置是否重连。
true
:重连。false
:不重连。
参数设置:
MqttConfigure.automaticReconnect = true;
SDK反初始化:
如果需要注销初始化,调用如下反初始化接口。该接口为同步接口。
说明SDK进行初始化时,需确保最近一次的反初始化已执行完成。否则,会导致重复初始化失败。
// 取消注册 notifyListener,notifyListener对象需和注册的时候是同一个对象 LinkKit.getInstance().unRegisterOnPushListener(notifyListener); LinkKit.getInstance().deinit();
连接状态与下行消息监听:
如果需要监听设备的上下线信息,物联网平台下发的所有数据,可以设置以下监听器。
IConnectNotifyListener notifyListener = new IConnectNotifyListener() { @Override public void onNotify(String connectId, String topic, AMessage aMessage) { // 下行数据回调 // connectId连接类型Topic下行Topic; aMessage下行数据 // 数据解析如下: //String pushData = new String((byte[]) aMessage.data); // pushData 示例 {"method":"thing.service.test_service","id":"123374967","params":{"vv":60},"version":"1.0.0"} // method 服务类型; params 下推数据内容 } @Override public boolean shouldHandle(String connectId, String topic) { // 选择是否不处理某个Topic的下行数据 // 如果不处理某个Topic,则onNotify不会收到对应Topic的下行数据 return true; //TODO 根据实际情况设置 } @Override public void onConnectStateChange(String connectId, ConnectState connectState) { // 对应连接类型的连接状态变化回调,具体连接状态参考SDK ConnectState AppLog.d(TAG, "onConnectStateChange() called with: connectId = [" + connectId + "], connectState = [" + connectState + "]"); //首次连云可能失败。对于首次连云失败,SDK会报出ConnectState.CONNECTFAIL这种状态。对于这种场景,用户可以尝试若干次后退出,也可以一直重试直到连云成功 //TODO: 以下是首次建连时用户主动重试的一个参考实现,用户可以打开下面注释使能下述代码 // if(connectState == ConnectState.CONNECTFAIL){ // try{ // Thread.sleep(5000); // PersistentNet.getInstance().reconnect(); // }catch (Exception e){ // AppLog.d(TAG, "exception is " + e); // }; // AppLog.d(TAG, "onConnectStateChange() try to reconnect when connect failed"); // } //SDK连云成功后,后续如果网络波动导致连接断开时,SDK会抛出ConnectState.DISCONNECTED这种状态。在这种情况下,SDK会自动尝试重连,重试的间隔是1s、2s、4s、8s...128s...128s,到了最大间隔128s后,会一直以128s为间隔重连直到连云成功。 } } // 注册下行监听,包括长连接的状态和下行的数据 LinkKit.getInstance().registerOnPushListener(notifyListener); // 取消下行监听,需要确保和注册的是同一个对象 // LinkKit.getInstance().unRegisterOnPushListener(notifyListener);
说明下行数据的回调
onNotify
,默认是在UI线程透出。自lp-iot-linkkit
的1.7.3版本起,您可以通过PersistentConnect.mNotifyReceivedMsgOnMainThread = false;
选择将下行消息通过非UI线程透出。对于下行消息密集,或者UI线程业务繁忙的场景,建议将该配置项设置为false
。日志开关:
打开SDK内部日志输出开关:
PersistentNet.getInstance().openLog(true); ALog.setLevel(ALog.LEVEL_DEBUG);
安卓SDK提供了一个全量的拦截器,支持您重写拦截器的
log
函数,实现自定义的日志处理。例如:将日志通过持久化保存到特定文件中。日志输出示例代码:
ALog.setLogDispatcher(new ILogDispatcher() { @Override public void log(int level, String prefix, String msg) { switch (level){ case LEVEL_DEBUG: System.out.println("debug:"+ prefix + msg); break; case LEVEL_INFO: System.out.println("info:" + prefix + msg); break; case LEVEL_ERROR: System.out.println("error:" + prefix + msg); break; case LEVEL_WARNING: System.out.println("warnings:" + prefix + msg); break; default: System.out.println("other:" + prefix + msg); } } });
获取SDK的版本号:
ALog.i(TAG, "sdk version = " + LinkKit.getInstance().getSDKVersion());
安卓设备快速重连
安卓SDK断开后,快速重连的默认时间为65秒。部分场景下,您需要在断开后快速重连。请参考如下代码:
LinkKit.getInstance().reconnect();
设置自定义心跳和解决电池供电及灭屏情况下的心跳不准问题
通过接口MqttPingSender,可实现自定义心跳时间。更多信息,请参见TimerPingSender。通过TimerPingSender,您可以设置下一次发送心跳的时间点,以及停止发送心跳。
说明发送心跳的时间点不要超过保活时间。
示例代码如下:
// 其中PrivateMqttPingSender实现了MqttPingSender接口 MqttConfigure.pingSender = new PrivateMqttPingSender();
部分安卓6以及更高版本的电池供电的安卓设备在灭屏情况下,会因心跳发送不及时,导致设备离线。此时,您可参考接口AlarmMqttPingSender实现自定义心跳时间。
重要该方式会定期唤醒安卓系统,会产生额外的功耗开销。
对于安卓API
level >= 31
的设备:需要在
AndroidManifest.xml
文件中添加权限:android.permission.SCHEDULE_EXACT_ALARM
。在第67行调用API
PendingIntent.getBroadcast
时,flag
字段(最后一个参数)需要根据实际情况考虑设置为FLAG_IMMUTABLE
或者FLAG_MUTABLE
。
在电池供电同时灭屏的情况下,安卓6以及更高版本的安卓系统的网络可能会由于Doze机制自动断开(例如系统日志中会出现
Netd : Destroyed 3 sockets for UidRanges
),这种情况下,即使定时器准确心跳报文还是无法正常发出,请联系您的安卓系统的供应商解决该问题。
Demo使用说明
如果设备初始化失败,需再次对SDK进行初始化。当设备由于网络等原因导致与物联网平台断开连接时,SDK会自动尝试与物联网平台建立连接。
修改Android Link SDK Demo中
./app/src/main/res/raw/deviceinfo
文件的参数,实现设备的快速接入。表格中提示”需要填写“的参数进行填写,为否的参数保持默认设置不需修改。使用新版公共实例、企业实例接入的用户,控制台的实例详情中带有端口号信息,可直接拷贝使用。格式如下:
{instanceid}.mqtt.iothub.aliyuncs.com:8883
使用旧版实例的用户,控制台的实例详情中没有端口号信息, 请拷贝后自行添加端口号信息。实例接入参考格式如下:
{YourProductKey}.iot-as-mqtt.{region}.aliyuncs.com:8883
具体请参见查看实例终端节点。
参数 | 说明 | 一机一密 | 一型一密(预注册) | 一型一密(免预注册) |
productKey | 物联网平台为产品颁发的标识符。 | 需要填写 | 需要填写 | 需要填写 |
deviceName | 设备在产品内的标识符。 | 需要填写 | 需要填写 | 需要填写 |
productSecret | 产品密钥。 | 否 | 需要填写 | 需要填写 |
deviceSecret | 设备密钥。 | 需要填写 | 否 | 否 |
registerType | 一型一密的方式,预注册或免预注册。 | 否 | 否 | 需要填写,内容为字符串regnwl。 |
instanceId | 实例ID,对企业实例和新版本公共实例适用,具体请参见实例概述。 | 否 | 需要填写 | 需要填写 |
mqttHost | MQTT接入域名。 | 需要填写(上海区域除外),需要修改接入点的域名和端口号。 |