全部產品
Search
文件中心

:認證與串連

更新時間:Jun 30, 2024

裝置接入物聯網平台之前,需通過身份認證。本文介紹如何將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

地區支援

  • 基於MQTT的動態註冊,物聯網平台支援的所有地區。

  • 基於HTTPS的動態註冊,僅限於上海地區,不推薦使用,不在本範例程式碼介紹範圍之內。

華東2(上海)、華北2(北京)

返回的裝置密鑰

DeviceSecret具體使用方式,請參考上述一機一密樣本中的Step1。

裝置的ClientID和DeviceToken,請將其持久固化至裝置,以便連雲等其他功能使用。具體使用方式,請參考上述一機一密樣本中的Step7。

添加裝置

需要在物聯網平台預註冊裝置DeviceName。

不需要在物聯網平台預註冊裝置DeviceName。

使用次數限制

  • 同一組裝置認證只能用於啟用一個物理裝置。若DeviceName名下已啟用物理裝置A,但物理裝置B需要使用該DeviceName,則您可以在物聯網平台上刪除裝置A,使裝置A的DeviceSecret作廢,再使用原DeviceName重新添加裝置,啟用物理裝置B。

  • 若裝置因丟失DeviceSecret等原因需要重新啟用,需您調用ResetThing介面,重設裝置狀態為未啟用,然後將裝置重新連網啟用。此時,物聯網平台下發的DeviceSecret不變。

物聯網平台允許最多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函數,實現自訂的Tlog。例如:將日誌通過持久化儲存到特定檔案中。

    日誌輸出範例程式碼:

            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檔案的參數,實現裝置的快速接入。表格中提示”需要填寫“的參數進行填寫,為否的參數保持預設設定不需修改。

  • 參數

    說明

    一機一密

    一型一密(預註冊)

    一型一密(免預註冊)

    productKey

    物聯網平台為產品頒發的標識符。

    需要填寫

    需要填寫

    需要填寫

    deviceName

    裝置在產品內的標識符。

    需要填寫

    需要填寫

    需要填寫

    productSecret

    產品金鑰。

    需要填寫

    需要填寫

    deviceSecret

    裝置密鑰。

    需要填寫

    registerType

    一型一密的方式,預註冊或免預註冊。

    需要填寫,內容為字串regnwl

    instanceId

    執行個體ID,對企業執行個體和新版本公用執行個體適用,具體請參見執行個體概述

    需要填寫

    需要填寫

    mqttHost

    MQTT接入網域名稱。

    需要填寫(上海地區除外),需要修改存取點的網域名稱和連接埠號碼。

    • 使用新版公用執行個體、企業執行個體接入的使用者,控制台的執行個體詳情中帶有連接埠號碼資訊,可直接拷貝使用。格式如下: {instanceid}.mqtt.iothub.aliyuncs.com:8883

    • 使用舊版執行個體的使用者,控制台的執行個體詳情中沒有連接埠號碼資訊, 請拷貝後自行添加連接埠號碼資訊。執行個體接入參考格式如下:

      {YourProductKey}.iot-as-mqtt.{region}.aliyuncs.com:8883具體請參見查看執行個體終端節點

常見問題

Android Link SDK相關問題