适用于 Android 的 Thread 网络 SDK

Thread Network SDK 提供类似于 钥匙串,让您的 Android 应用可以将 Thread 网络凭据共享给 Google Play 服务。这样,您的应用就可以通过 而无需直接公开凭据和用户数据。

只需进行几次 API 调用,您就可以:

  1. 向 Google Play 服务请求首选 Thread 网络凭据。
  2. 设置新的边界路由器,并将您的 Thread 网络凭据添加到 Google Play 服务。
  3. 如果已有现场边界路由器,则可以检查您的边界 路由器在首选网络中并进行迁移(如有必要)。

需要考虑用户和开发者的不同历程。我们将介绍 本指南介绍了这些概念,以及其他主要功能和推荐用法。

主要术语和 API 概念

在开始之前,了解以下术语会很有帮助:

  • Thread 网络凭据:Thread TLV 的二进制 blob,用于编码 Thread 网络名称、网络密钥以及 Thread 设备加入给定 Thread 网络所需的其他属性。

  • 首选 Thread 网络凭据:自动选择的 Thread 网络凭据,可使用 getPreferredCredentials API 与不同供应商的应用共享。

  • 边界代理 ID:线程边界路由器的 16 字节全局唯一 ID 设备。此 ID 由边界路由器供应商创建和管理。

  • “线程边界路由器设置”应用:这是一款可用于设置 新的 Thread 边界路由器设备,并将 Thread 网络凭据添加到 Google Play 服务。您的应用是所添加的 并有权访问这些凭据

许多线程网络 API 都会返回一个异步完成的 Task。您可以使用 addOnSuccessListeneraddOnFailureListener 注册回调以接收结果。如需了解详情,请参阅 任务 文档。

凭据所有权和维护

添加 Thread 网络凭据的应用会成为 并且具有访问这些凭据的完整权限。如果您尝试访问其他应用添加的凭据,则会收到 PERMISSION_DENIED 错误。

作为应用所有者,建议您在更新 Thread 边界路由器网络时,及时更新存储在 Google Play 服务中的凭据。这意味着,您需要在需要时添加凭据,在边界路由器的 Thread 网络凭据发生变化时更新凭据,以及在移除 Thread 边界路由器或将其恢复出厂设置时移除凭据。

边界代理发现

凭据必须与边境代理人 ID 一起保存。您需要确保 Thread 边界路由器设置应用能够确定 Thread 边界路由器的边界代理 ID。

线程边界路由器必须使用 mDNS 来通告线程网络信息。 包括网络名称、扩展的 Pan ID 和边界代理 ID。通过 这些属性对应的 txt 值为 nnxpid, 。

对于配有 Google 边界路由器的网络,Google Play 服务会自动 获取 Google Thread 网络凭据以供使用。

将 SDK 集成到 Android 应用中

如需开始使用,请完成以下步骤:

  1. 请按照以下网址中提供的说明操作: 设置 Google Play 服务

  2. 将 Google Play 服务依赖项添加到 build.gradle 文件中:

    implementation 'com.google.android.gms:play-services-threadnetwork:16.2.1'
    
  3. 可选:定义 BorderAgent 数据类来存储边界路由器 信息。在本指南中,我们将使用这些数据:

    data class BorderAgentInfo(
      // Network Name max 16 len
      val networkName: String = "",
      val extPanId: ByteArray = ByteArray(16),
      val borderAgentId: ByteArray = ByteArray(16),
      ...
    )
    

接下来,我们将介绍添加和管理首选凭据的推荐步骤。

新的边界路由器设置

在为新的边界路由器创建新网络之前,请务必先尝试使用首选网络凭据。这样可以确保 Thread 设备会尽可能连接到一个 Thread 网络。

启动对 getPreferredCredentials 的调用 一个 Activity,提示用户允许网络请求。如果网络凭据已存储在 Thread SDK 数字钥匙串中,则系统会将凭据返回给您的应用。

请求凭据

如需提示用户输入首选凭据,请执行以下操作:

  1. 声明 ActivityLauncher

    private lateinit var preferredCredentialsLauncher: ActivityResultLauncher<IntentSenderRequest>
    
  2. 处理 activity 结果,以 ThreadNetworkCredentials 的形式返回:

    preferredCredentialsLauncher =
     registerForActivityResult(
       StartIntentSenderForResult()
     ) { result: ActivityResult ->
       if (result.resultCode == RESULT_OK) {
         val threadNetworkCredentials = ThreadNetworkCredentials.fromIntentSenderResultData(result.data!!)
         Log.d("debug", threadNetworkCredentials.networkName)
       } else {
         Log.d("debug", "User denied request.")
       }
     }
    
  3. 如果您要设置新的 Thread 边界路由器,建议您调用 preferredCredentials 并启动 activity。这种调用将确保 您的新线程边界路由器将使用已存储的相同凭据 视为手机中的首选,促进不同 TBR 汇集于 同一网络。

    private fun getPreferredThreadNetworkCredentials() {
      ThreadNetwork.getClient(this)
        .preferredCredentials
      .addOnSuccessListener { intentSenderResult ->
        intentSenderResult.intentSender?.let {
          preferredCredentialsLauncher.launch(IntentSenderRequest.Builder(it).build())
          } ?: Log.d("debug", "No preferred credentials found.")
        }
      .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
    }
    
  4. 如果您的用例与设置非 TBR 设备(例如新的 Matter-over-Thread 终端设备时,建议您使用 allActiveCredentials API 来获取凭据。此通话将扫描在本地 因此不会返回 现有的 TBR。

    // Creates the IntentSender result launcher for the getAllActiveCredentials API
    private val getAllActiveCredentialsLauncher =
      registerForActivityResult(
        StartIntentSenderForResult()
      ) { result: ActivityResult ->
        if (result.resultCode == RESULT_OK) {
          val activeCredentials: List<ThreadNetworkCredentials> =
            ThreadNetworkCredentials.parseListFromIntentSenderResultData(
              result.data!!
            )
          // Use the activeCredentials list
        } else {
          // The user denied to share!
        }
      }
    
    // Invokes the getAllActiveCredentials API and starts the dialog activity with the returned
    // IntentSender
    threadNetworkClient
    .getAllActiveCredentials()
    .addOnSuccessListener { intentSenderResult: IntentSenderResult ->
      val intentSender = intentSenderResult.intentSender
      if (intentSender != null) {
        getAllActiveCredentialsLauncher.launch(
          IntentSenderRequest.Builder(intentSender).build()
        )
      } else {
        // No active network credentials found!
      }
    }
    // Handles the failure
    .addOnFailureListener { e: Exception ->
      // Handle the exception
    }
    

创建新的 Thread 网络

如果既没有首选 Thread 网络凭据,也没有活跃线程 可在用户的 Thread 网络,则可以使用 addCredentials API 将凭据添加到 Google Play 服务。为此,您需要创建一个 ThreadBorderAgent,并提供一个 ThreadNetworkCredentials 对象。

如需创建随机网络,请调用 newRandomizeBuilder

val threadCredentials = ThreadNetworkCredentials.newRandomizedBuilder().build()

如需指定 Thread 网络名称,请执行以下操作:

val threadCredentials = ThreadNetworkCredentials.newRandomizedBuilder()
  .setNetworkName("ThreadNetworkSDK")
  .build()

添加凭据

为了让其他 Thread 供应商可以使用您的 Thread 网络凭据,我们需要将这些凭据添加到 Google Play 服务中。在添加新凭据之前,我们还需要知道此 Thread 网络属于哪个边界路由器设备。

在此示例中,我们将根据边界代理 ID 创建 ThreadBorderAgent,并传递您刚刚创建的新 Thread 网络凭据:

private fun addCredentials(borderAgentInfo: BorderAgentInfo, credentialsToBeAdded: ThreadNetworkCredentials) {

  val threadBorderAgent = ThreadBorderAgent.newBuilder(borderAgentInfo.borderAgentId).build()
  Log.d("debug", "border router id:" + threadBorderAgent.id)

  ThreadNetwork.getClient(this)
    .addCredentials(threadBorderAgent, credentialsToBeAdded)
      .addOnSuccessListener {
        Log.d("debug", "Credentials added.")
      }
      .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}

检测和迁移现场边界路由器

如果您当前有现场边界路由器,则可以使用 isPreferredCredentials,用于确定边界路由器是否属于 连接到首选网络此 API 不会提示 用户授予权限,并根据存储的内容检查边界路由器凭据 。

isPreferredCredentails 会以 Int 数据类型返回 0(不匹配)和 1(匹配)。您可以使用 IsPreferredCredentialsResult 查看结果。

public @interface IsPreferredCredentialsResult {
    int PREFERRED_CREDENTIALS_NOT_FOUND = -1;
    int PREFERRED_CREDENTIALS_NOT_MATCHED = 0;
    int PREFERRED_CREDENTIALS_MATCHED = 1;
}

如需使用 isPreferredCredentials,您需要先创建 ThreadNetworkCredentials 对象。您可以通过多种方式实例化 ThreadNetworkCredentials。在后续步骤中,我们将介绍这些选项。

按操作数据集划分的 Thread 网络凭据

有些情况下,您的线程边界路由器已经设置了 Thread 网络,并且您想将此 Thread 网络添加到 Google Play 服务 以便与其他供应商共享您可以创建一个ThreadNetworkCredential 来自原始线程活跃操作数据集 TLV 列表的实例:

  1. 将操作数据集转换为 ByteArray。例如:

    val activeDataset =
          "0e080000000000010000000300000f35060004001fffe0020833333333...".dsToByteArray()
    
    fun String.dsToByteArray(): ByteArray {
      return chunked(2).map { it.toInt(16).toByte() }.toByteArray()
    }
    
  2. 使用 fromActiveOperationalDataset 创建 ThreadNetworkCredentials。成功后,您将能够获取 Thread 广告联盟名称、渠道和其他广告联盟信息。如需查看完整的属性列表,请参阅 ThreadNetworkCredentials

    val threadNetworkCredentials =
        ThreadNetworkCredentials.fromActiveOperationalDataset(activeDataset)
    Log.d(
        "threadNetworkCredentials",
        threadNetworkCredentials.channel.toString() + " - " + threadNetworkCredentials.networkName)
    
  3. 调用 isPreferredCredentials API 并传递 ThreadNetworkCredentials

    ThreadNetwork.getClient(this)
    .isPreferredCredentials(threadNetworkCredentials)
    .addOnSuccessListener { result ->
      when (result) {
        IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_NOT_MATCHED ->
            Log.d("isPreferredCredentials", "Credentials not matched.")
        IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_MATCHED ->
            Log.d("isPreferredCredentials", "Credentials matched.")
      }
    }
    .addOnFailureListener { e: Exception -> Log.d("isPreferredCredentials", "ERROR: [${e}]") }
    

由边界代理提供的 Thread 网络凭据

边界代理 ID 用于唯一标识边界路由器设备。要使用 getCredentialsByBorderAgent API,首先,您需要创建一个 ThreadBorderAgent 对象并传递边境代理 ID。

创建 ThreadBorderAgent 对象后,请调用 getCredentialsByBorderAgent。如果凭据已保存,请检查它们是否为首选凭据。

private fun isPreferredThreadNetworkByBorderAgent(borderAgentInfo: BorderAgentInfo) {

  val threadBorderAgent = ThreadBorderAgent.newBuilder(borderAgentInfo.borderAgentId).build()
  Log.d("debug", "border router id:" + threadBorderAgent.id)

  var isPreferred = IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_NOT_FOUND
  var borderAgentCredentials: ThreadNetworkCredentials?
  val taskByBorderAgent = ThreadNetwork.getClient(this)
  taskByBorderAgent
      .getCredentialsByBorderAgent(threadBorderAgent)
      .addOnSuccessListener { result: ThreadNetworkCredentialsResult ->
        borderAgentCredentials = result.credentials
        result.credentials?.let {
          taskByBorderAgent.isPreferredCredentials(it).addOnSuccessListener { result ->
            isPreferred = result
          }
        }
      }
      .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}

按扩展型 PAN ID 划分的 Thread 网络凭据

getPreferredCredentials 类似,您也可以提示用户 从边界路由器的 Extended Pan ID 获取凭据。通过 getCredentialsByExtendedPanId 会返回 IntentSender,而 Activity 结果包含用户批准后的 ThreadNetworkCredentials 对象。

private fun getCredentialsByExtPanId(borderAgentInfo: BorderAgentInfo) {
  ThreadNetwork.getClient(this)
    .getCredentialsByExtendedPanId(borderAgentInfo.extPanId)
    .addOnSuccessListener { intentSenderResult ->
      intentSenderResult.intentSender?.let {
        preferredCredentialsLauncher.launch(IntentSenderRequest.Builder(it).build())
      }
        ?: Log.d("debug", "No credentials found.")
    }
    .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}

移除凭据

将边界路由器设备从住宅中移除或恢复出厂设置后,您 需要从 Google Play 服务中移除其 Thread 网络。

private fun removeCredentials(borderAgentInfo: BorderAgentInfo) {

  val threadBorderAgent = ThreadBorderAgent.newBuilder(borderAgentInfo.borderAgentId).build()
  Log.d("debug", "border router id:" + threadBorderAgent.id)

  ThreadNetwork.getClient(this)
      .removeCredentials(threadBorderAgent)
      .addOnSuccessListener { Log.d("debug", "Credentials removed.") }
      .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}

资源

如需详细了解 Thread Network SDK,请参阅 API 参考文档