将 Android widget 与 Google 助理集成

1. 概览

第一个涉及与应用有关的 Action 的 Codelab 中,你学习了如何通过实现“健康与健身内置 intent”类别中的内置 intent (BII) 将 Google 助理的功能扩展到示例健身应用。

借助与应用有关的 Action,用户只需说出“Ok Google,在‘ExampleApp’中开始跑步”这样的语音指令,就能通过 Google 助理直接启动特定的应用功能。除了启动应用外,Google 助理还可以向用户显示交互式 Android widget,以执行对符合条件的 BII 的请求。

一个手机屏幕,其中显示了以下情况:Google 助理返回了一个 widget,以响应\n触发了一项应用 GET_EXERCISE_OBSERVATION BII 功能的用户询问。

构建内容

在本 Codelab 中,你将学习如何返回 Android widget 以执行 Google 助理用户请求。你还将学习:

  • 用于个性化设置 widget 的用户 BII 参数。
  • 在 Google 助理中为 widget 提供文字转语音 (TTS) 形式的简介。
  • 根据“内置 intent”参考文档确定哪些 BII 支持 widget 执行方式。

前提条件

在继续之前,请确保你的开发环境已可以进行与应用有关的 Action 的开发。你的开发环境应具备:

  • 已安装 git 的终端,用于运行 shell 命令。
  • Android Studio 的最新稳定版本。
  • 一部可以访问互联网的 Android 实体设备或虚拟设备。
  • 已登录 Android Studio、Google 应用和 Google 助理应用的 Google 帐号。

如果你使用的是实体设备,请将其连接到本地开发机器。

2. 了解运作方式

Google 助理会使用自然语言理解 (NLU) 技术读取用户的请求,并将其匹配到某个 Google 助理内置 intent (BII)。然后,Google 助理会将该 intent 映射到你在应用中为该 intent 注册的功能(用于实现该 BII)。最后,Google 助理会利用在该功能中找到的详细信息,显示你的应用生成的 Android widget,从而执行用户的请求。

在本 Codelab 中,你将定义一项功能来注册对 GET_EXERCISE_OBSERVATION BII 的支持。在此功能中,你将指示 Google 助理向 FitActions widget 类别生成一个 Android intent,用于执行对此 BII 的请求。你将更新此类别,以生成可供 Google 助理向用户显示的个性化 widget,以及 Google 助理可以播报的 TTS 简介。

下图演示了此流程:

一个演示 Google 助理 widget 执行方式的流程图。

FitActions widget

FitActions 示例应用包含一个锻炼信息 widget,用户可将其添加到自己的主屏幕。此 widget 非常适合执行触发 GET_EXERCISE_OBSERVATION BII 的用户询问。

widget 的运作方式

当用户将某个 widget 添加到主屏幕时,该 widget 会对设备广播接收器执行 ping 操作。此服务会从应用的 AndroidManifest.xml 资源内该 widget 的接收器定义中检索 widget 的相关信息。它会使用此信息生成代表该 widget 的 RemoteViews 对象。

示例应用定义了与 StatsWidgetProvider 类别相对应的接收器 widgets.StatsWidgetProvider

<!-- app/src/main/AndroidManifest.xml -->

<receiver
  android:name=".widgets.StatsWidgetProvider"
  android:exported="false">
  <intent-filter>
    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
  </intent-filter>
  <meta-data
    android:name="android.appwidget.provider"
    android:resource="@xml/stats_widget" />
</receiver>

StatsWidgetProvider 类别(即 StatsWidgetProvider.kt)用于管理 StatsWidget 对象创建流程。它具有以下责任:

  • 创建 widget 实例,并使用应用数据库中的锻炼数据填充实例。
  • 使用 formatDataAndSetWidget() 设置锻炼数据的格式以方便阅读。
  • 在无法获得锻炼数据时使用 setNoActivityDataWidget() 提供默认值。

添加 Google 助理支持

在本 Codelab 中,你将更新示例应用以处理与应用有关的 Action 功能。这些更新包括:

  1. 配置 GET_EXERCISE_OBSERVATION BII 功能以返回 StatsWidget 对象的实例。
  2. 更新 StatsWidget 类别以使用与应用有关的 Action 功能,例如:
    • 使用 BII 参数,让用户可通过说“Ok Google,在‘ExampleApp’中显示我的跑步统计数据”这样的语音指令来查看具体的锻炼统计信息。
    • 提供 TTS 简介字符串。
    • 管理特殊情况,例如当用户的询问未包含锻炼类型参数时。

3. 准备开发环境

下载基础文件

运行以下命令以克隆示例应用的 GitHub 代码库

git clone --branch start-widget-codelab https://github.com/actions-on-google/appactions-fitness-kotlin.git

克隆完代码库后,按照以下步骤在 Android Studio 中将其打开:

  1. 在“Welcome to Android Studio”对话框中,点击 Import project
  2. 查找并选择克隆代码库的文件夹。

如需查看代表 Codelab 完成后的应用的版本,请使用 --branch master 标记克隆示例应用的代码库。

更改 Android 应用 ID

在本 Codelab 后面的内容中,你将使用 Google 助理插件在实体设备或虚拟设备上测试你的 Action。如要运行该测试工具,你必须先将应用上传到 Google Play 管理中心的某个项目。为避免在将你的应用上传到 Play 管理中心时出现“Duplicate package name”错误,请将示例应用的 applicationId 更改为唯一 ID(Google Play 不允许上传的任何两个应用具有相同 applicationId)。

  1. app/build.gradle 中,将 applicationIdPUT_YOUR_APPLICATION_ID_HERE 更新为唯一 ID,例如 com.codelabs.myname。如需详细了解 Android 应用 ID,请参阅设置应用 ID
  2. 打开 app/src/res/xml/shortcuts.xml 并将 android:targetPackage 的两个 (2) 实例更新为唯一 applicationId

上传到 Play 管理中心

你必须将应用上传到 Google Play 管理中心内的项目,然后才能在 Android Studio 中使用 Google 助理插件。在 Android Studio 中构建你的应用,并将其作为内部版本草稿上传到 Play 管理中心。

如需在 Android Studio 中构建应用,请执行以下操作:

  1. 依次点击 Build > Generate Signed Bundle / APK
  2. 选择 Android App Bundle,然后点击 Next
  3. 输入详细信息以为应用签名,然后点击 Next。在 Destination Folder 部分中跟踪记录生成 app bundle 的路径。
  4. 选择 prodReleaserelease build 变体,然后点击 Finish

Play 管理中心内,将你刚刚创建的 app bundle 作为新应用进行上传:

  1. 所有应用页面上,点击创建应用
  2. 应用名称框中,输入应用的任一名称,例如“Widget Codelab”。
  3. 对于应用或游戏,请选择应用
  4. 对于免费或付费,请选择免费
  5. 接受所有列出的声明
  6. 点击创建应用
  7. 在 Play 管理中心的侧边菜单中,点击测试,然后找到内部测试页面。
  8. 在此页面中,点击创建新的发布版本
  9. 如果出现提示,请点击继续以同意使用 Google Play 应用签名。
  10. app bundle 和 APK 面板中,上传你在上一步生成的 AAB 文件。此文件可能位于项目的 app/prod/releaseapp/release 目录中。点击保存

安装测试插件

借助 Google 助理插件,你可以在测试设备上测试与应用有关的 Action。其运作方式是通过 Android 设备上的 Google 应用向 Google 助理发送信息。如果你还没有安装该插件,请按以下步骤进行安装:

  1. 依次点击 File > Settings(在 MacOS 中,依次点击 Android Studio > Preferences)。
  2. 在“Plugins”部分中,点击 Marketplace,然后搜索“Google Assistant”。此外,你还可以手动下载并安装该测试工具。
  3. 安装该工具,然后重启 Android Studio。

在设备上测试应用

在对该应用进行更多更改之前,最好了解一下该示例应用的功能。

在测试设备上运行应用:

  1. 在 Android Studio 中,选择你的实体设备或虚拟设备,然后依次选择 Run > Run app,或点击工具栏中的 Run 图标 在 Android Studio 中运行应用图标。
  2. 长按主屏幕按钮,以设置 Google 助理并验证它能否正常运行。你需要在设备上登录 Google 助理(如果你尚未登录的话)。

如需详细了解 Android 虚拟设备,请参阅创建和管理虚拟设备

快速浏览该应用以了解其功能。该应用会预填充 10 个锻炼活动并在第一个视图中显示此信息。

试用现有的 widget

  1. 点按主屏幕按钮,前往测试设备的主屏幕。
  2. 长按主屏幕上的空白区域,然后选择 Widgets
  3. 将 widget 列表向下滚动至 FitActions
  4. 长按 FitActions 图标并将其 widget 放置在主屏幕上。

一张设备主屏幕的屏幕截图,其中显示了 FitActions widget。

4. 添加与应用有关的 Action

在此步骤中,你将添加 GET_EXERCISE_OBSERVATION BII 功能。你将通过在 shortcuts.xml 中添加新的 capability 元素来实现此操作。此功能用于指定如何触发该 BII 功能、如何使用 BII 参数,以及要调用哪些 Android intent 来执行该请求。

  1. 使用以下配置向示例项目 shortcuts.xml 资源中添加新的 capability 元素:
    <!-- fitnessactions/app/src/main/res/xml/shortcuts.xml -->
    
    <capability android:name="actions.intent.GET_EXERCISE_OBSERVATION">
      <app-widget
        android:identifier="GET_EXERCISE_OBSERVATION"
        android:targetClass="com.devrel.android.fitactions.widgets.StatsWidgetProvider"
        android:targetPackage="PUT_YOUR_APPLICATION_ID_HERE">
        <parameter
          android:name="exerciseObservation.aboutExercise.name"
          android:key="aboutExerciseName"
          android:required="true">
        </parameter>
        <extra android:name="hasTts" android:value="true"/>
      </app-widget>
      <!-- Add Fallback Intent-->
    </capability>
    
    android:targetPackagePUT_YOUR_APPLICATION_ID_HERE 替换为你的唯一 applicationId

此功能会将 GET_EXERCISE_OBSERVATION BII 映射到 app-widget intent,以便在 BII 被触发时,相应 widget 会实例化并向用户显示。

在触发 widget 前,Google 助理会从用户询问中提取支持的 BII 参数。本 Codelab 需要使用 BII 参数 exerciseObservation.aboutExercise.name,它代表用户请求的锻炼类型。此应用支持三种锻炼类型:“跑步”“步行”和“骑车”。你提供内嵌目录,以便将这些支持的值告知 Google 助理。

  1. 将以下配置(GET_EXERCISE_OBSERVATION 功能上方)添加到 shortcuts.xml 中,以定义这些目录元素:
    <!-- shortcuts.xml -->
    
    <!-- shortcuts are bound to the GET_EXERCISE_OBSERVATION capability and
         represent the types of exercises supported by the app. -->
    
    <shortcut
      android:shortcutId="running"
      android:shortcutShortLabel="@string/activity_running">
      <capability-binding android:key="actions.intent.GET_EXERCISE_OBSERVATION">
        <parameter-binding
          android:key="exerciseObservation.aboutExercise.name"
          android:value="@array/runningSynonyms"/>
      </capability-binding>
    </shortcut>
    
    <shortcut
      android:shortcutId="walking"
      android:shortcutShortLabel="@string/activity_walking">
      <capability-binding android:key="actions.intent.GET_EXERCISE_OBSERVATION">
        <parameter-binding
          android:key="exerciseObservation.aboutExercise.name"
          android:value="@array/walkingSynonyms"/>
      </capability-binding>
    </shortcut>
    
    <shortcut
      android:shortcutId="cycling"
      android:shortcutShortLabel="@string/activity_cycling">
      <capability-binding android:key="actions.intent.GET_EXERCISE_OBSERVATION">
        <parameter-binding
          android:key="exerciseObservation.aboutExercise.name"
          android:value="@array/cyclingSynonyms"/>
      </capability-binding>
    </shortcut>
    
    <capability android:name="actions.intent.GET_EXERCISE_OBSERVATION">
      <!-- ... -->
    </capability>
    

添加后备 intent

后备 intent 用于处理由于用户询问中缺少该功能所需的参数而导致询问无法执行的情况。GET_EXERCISE_OBSERVATION 功能需要使用由 android:required="true" 属性指定的 exerciseObservation.aboutExercise.name 参数。对于这类情况,Google 助理会要求你定义后备 intent 以确保成功执行请求,即使询问中未提供任何参数也能成功。

  1. shortcuts.xml 中,使用以下配置向 GET_EXERCISE_OBSERVATION 功能中添加后备 intent:
    <!-- shortcuts.xml -->
    
    <capability android:name="actions.intent.GET_EXERCISE_OBSERVATION">
    
      <app-widget>
        <!-- ... -->
      </app-widget>
    
      <!-- Fallback intent with no parameters needed to successfully execute.-->
      <intent
        android:identifier="GET_EXERCISE_OBSERVATION_FALLBACK"
        android:action="android.intent.action.VIEW"
        android:targetClass="com.devrel.android.fitactions.widgets.StatsWidgetProvider">
      </intent>
    </capability>
    

在此示例配置中,后备执行方式是一个 Extra 数据中不含参数的 Android intent。

5. 为 Google 助理启用该 widget

构建好 GET_EXERCISE_OBSERVATION 功能后,更新 widget 类别以支持对与应用有关的 Action 的语音调用。

添加“widget 扩展程序”库

与应用有关的 Action 的“widget 扩展程序”库可增强你的 widget,以实现语音优先的 Google 助理体验。具体来说,它让你可为 widget 提供自定义的 TTS 简介。

  1. 将“widget 扩展程序”库依赖项添加到示例应用的 /app/build.gradle 资源:
    // app/build.gradle
    
    dependencies {
      //...
      implementation "com.google.assistant.appactions:widgets:0.0.1"
    }
    
    请点击 Android Studio 中显示的警告框中的 Sync Now。在每次 build.gradle 更改后同步有助于避免在构建应用时出错。

添加 widget service

Service 是一种可在后台执行长时间运行的操作的应用组件。你的应用需要提供一项 service 来处理 widget 请求。

  1. 使用以下配置向示例应用的 AndroidManifest.xml 资源中添加一项 service:
    <!-- AndroidManifest.xml -->
    <service
       android:name=".widgets.StatsWidgetProvider"
       android:enabled="true"
       android:exported="true">
       <intent-filter>
           <action
               android:name="com.google.assistant.appactions.widgets.PIN_APP_WIDGET" />
       </intent-filter>
    </service>
    
    

在触发 widget 执行方式的语音询问期间,Google 助理会使用此 service 向应用发送请求。service 会一并收到该请求以及 BII 数据。此 service 会使用这些数据生成要在 Google 助理内呈现的 RemoteView widget 对象。

更新 widget 类别

你的应用现已配置为将 GET_EXERCISE_OBSERVATION 功能请求传送到你的 widget 类别。接下来,更新 StatsWidget.kt 类别,以使用 BII 参数值生成针对用户请求的个性化 widget 实例。

  1. 打开 StatsWidget.kt 类别,导入与应用有关的 Action“widget 扩展程序”库:
    // StatsWidget.kt
    
    // ... Other import statements
    import com.google.assistant.appactions.widgets.AppActionsWidgetExtension
    
    
  2. 添加以下私有变量,在确定应填充到 widget 中的信息时使用这些变量:
    // StatsWidget.kt
    
    private val hasBii: Boolean
    private val isFallbackIntent: Boolean
    private val aboutExerciseName: String
    private val exerciseType: FitActivity.Type
    
  3. 添加 init 函数,让该类别可以使用从 Google 助理传递过来的 widget 选项数据:
    // StatsWidget.kt
    
    init {
      val optionsBundle = appWidgetManager.getAppWidgetOptions(appWidgetId)
      val bii = optionsBundle.getString(AppActionsWidgetExtension.EXTRA_APP_ACTIONS_BII)
      hasBii = !bii.isNullOrBlank()
      val params = optionsBundle.getBundle(AppActionsWidgetExtension.EXTRA_APP_ACTIONS_PARAMS)
    
      if (params != null) {
        isFallbackIntent = params.isEmpty
        if (isFallbackIntent) {
          aboutExerciseName = context.resources.getString(R.string.activity_unknown)
        } else {
            aboutExerciseName = params.get("aboutExerciseName") as String
          }
      } else {
          isFallbackIntent = false
          aboutExerciseName = context.resources.getString(R.string.activity_unknown)
      }
      exerciseType = FitActivity.Type.find(aboutExerciseName)
    }
    
    

让我们来了解一下这些更新如何使 StatsWidget.kt 类别响应由 GET_EXERCISE_OBSERVATION 功能生成的 Android intent:

  • optionsBundle = Bundle
    • Bundle 是一种对象,旨在用于各种进程边界中、内含 intent 的 activity 之间,以及用于存储各项配置变化之间的过渡状态。Google 助理使用 Bundle 对象将配置数据传递给 widget。
  • bii = actions.intent.GET_EXERCISE_OBSERVATION
    • 你可以使用 AppActionsWidgetExtension 从 Bundle 中获取 BII 的名称。
  • hasBii = true
    • 检查是否存在相应 BII。
  • params = Bundle[{aboutExerciseName=running}]
    • 由与应用有关的 Action 生成的一个特殊 Bundle 会嵌套在 widget 选项 Bundle 中。它包含 BII 的键值对。在这种情况下,值 running 提取自示例询问,“Ok Google,在‘ExampleApp’中显示我的跑步统计数据”。
  • isFallbackIntent = false
    • 检查 intent Extras 中是否存在必需的 BII 参数。
  • aboutExerciseName = running
    • 获取 aboutExerciseName 的 intent Extras 值。
  • exerciseType = RUNNING
    • 使用 aboutExerciseName 查找对应的数据库类型对象。

现在,StatsWidget 类别可以处理传入的与应用有关的 Action 的 Android intent 数据;请更新 widget 创建流程逻辑,以检查此 widget 是否由与应用有关的 Action 触发。

  1. StatsWidget.kt 中,将 updateAppWidget() 函数替换为以下代码:
    // StatsWidget.kt
    
    fun updateAppWidget() {
       /**
        * Checks for App Actions BII invocation and if BII parameter data is present.
        * If parameter data is missing, use data from last exercise recorded to the
        *  fitness tracking database.
        */
       if (hasBii && !isFallbackIntent) {
           observeAndUpdateRequestedExercise()
       } else observeAndUpdateLastExercise()
    }
    
    

上述代码引用了新函数 observeAndUpdateRequestedExercise。此函数使用由与应用有关的 Action Android intent 传递的 exerciseType 参数数据,以生成 widget 数据。

  1. 使用以下代码添加 observeAndUpdateRequestedExercise 函数:
    // StatsWidget.kt
    
    /**
    * Create and observe the last exerciseType activity LiveData.
    */
    private fun observeAndUpdateRequestedExercise() {
      val activityData = repository.getLastActivities(1, exerciseType)
    
       activityData.observeOnce { activitiesStat ->
           if (activitiesStat.isNotEmpty()) {
               formatDataAndSetWidget(activitiesStat[0])
               updateWidget()
           } else {
               setNoActivityDataWidget()
               updateWidget()
           }
       }
    }
    
    

在上述代码中,使用在应用中找到的代码库类别从应用的本地数据库中检索健身数据。此类别提供的 API 可简化对数据库的访问。代码库的运作方式是,在对数据库执行查询时公开 LiveData 对象。在你的代码中,你可以观察此 LiveData 以检索最新的健身活动。

启用 TTS

你可以提供 TTS 字符串,让 Google 助理可在显示 widget 时播报内容。我们建议你添加该字符串,以在 widget 中提供可听的背景信息。此功能由与应用有关的 Action“widget 扩展程序”库提供,你可以使用该库设置 Google 助理中伴随 widget 提供的文字和 TTS 简介。

提供 TTS 简介的理想位置是在 formatDataAndSetWidget 函数中,该函数会设置从应用数据库返回的 activity 数据的格式。

  1. StatsWidget.kt 中,将以下代码添加到 formatDataAndSetWidget 函数中:
    // StatsWidget.kt
    
    private fun formatDataAndSetWidget(
      activityStat: FitActivity,
    ) {
          // ...
    
          // Add conditional for hasBii for widget with data
          if (hasBii) {
             // Formats TTS speech and display text for Assistant
             val speechText = context.getString(
                 R.string.widget_activity_speech,
                 activityExerciseTypeFormatted,
                 formattedDate,
                 durationInMin,
                 distanceInKm
             )
             val displayText = context.getString(
                 R.string.widget_activity_text,
                 activityExerciseTypeFormatted,
                 formattedDate
             )
             setTts(speechText, displayText)
          }
    }
    
    

上述代码引用了两个字符串资源:一个用于语音,另一个用于文字。如需了解文字转语音方面的建议,请观看 widget 相关视频中的文字转语音样式建议部分。该示例还引用了 setTts,这是一个向 widget 实例提供 TTS 信息的新函数。

  1. 使用以下代码将此新 setTts 函数添加到 StatsWidget.kt
    // StatsWidget.kt
    
    /**
     * Sets TTS to widget
     */
    private fun setTts(
      speechText: String,
      displayText: String,
    ) {
      val appActionsWidgetExtension: AppActionsWidgetExtension =
          AppActionsWidgetExtension.newBuilder(appWidgetManager)
            .setResponseSpeech(speechText)  // TTS to be played back to the user
            .setResponseText(displayText)  // Response text to be displayed in Assistant
            .build()
    
      // Update widget with TTS
      appActionsWidgetExtension.updateWidget(appWidgetId)
    }
    

最后,当锻炼数据库针对所请求的锻炼类型返回空数据时,设置 TTS 信息以完成 TTS 逻辑。

  1. 使用以下代码更新 StatsWidget.kt 中的 setNoActivityDataWidget() 函数:
    // StatsWidget.kt
    
    private fun setNoActivityDataWidget() {
      // ...
      // Add conditional for hasBii for widget without data
      if (hasBii) {
        // formats speech and display text for Assistant
        // https://developers.google.com/assistant/app/widgets#library
        val speechText =
          context.getString(R.string.widget_no_activity_speech, aboutExerciseName)
        val displayText =
          context.getString(R.string.widget_no_activity_text)
    
        setTts(speechText, displayText)
      }
    }
    

6. 测试与应用有关的 Action

在开发过程中,你可以使用 Google 助理插件在测试设备上预览与应用有关的 Google 助理 Action。你可以使用该工具调整与应用有关的 Action 的 intent 参数,以测试你的 Action 如何处理用户可能要求 Google 助理运行该 Action 的各种方式。

创建预览

如需使用该插件测试与应用有关的 Action,请执行以下操作:

  1. 依次点击 Tools > Google Assistant > App Actions Test Tool。系统可能会要求你登录 Android Studio。如果系统要求登录,请使用之前用于登录 Google Play 管理中心的帐号。
  2. 点击 Create Preview 以创建预览。

测试预期的锻炼类型

返回一个 widget,其中显示应用中上次完成的运行的相关信息;在测试工具中的具体步骤如下:

  1. 在第一步中,该工具会让你选择并配置一个 BII;请选择 actions.intent.GET_EXERCISE_OBSERVATION
  2. exerciseObservation 框中,将默认锻炼名称从 climbing 更新为 run
  3. 点击 Run App Action

一个界面,其中显示了使用 Google 助理插件返回的一个 widget。

测试非预期的锻炼类型

如需在测试工具中测试非预期的锻炼类型,请执行以下操作:

  1. exerciseObservation 框中,将 name 值从 Run 更新为 Climbing
  2. 点击 Run App Action

Google 助理应返回一个显示“未找到任何活动”信息的 widget。

一个界面,其中显示了使用 Google 助理插件返回的一个不含任何锻炼信息的 widget。

测试后备 intent

触发后备 intent 的询问应返回一个 widget,其中显示系统记录的相应锻炼类型的上次活动的相关信息。

如需测试后备 intent,请执行以下操作:

  1. exerciseObservation 框中,删除 aboutExercise 对象。
  2. 点击 Run App Action

Google 助理应返回一个 widget,其中显示与上次完成的锻炼相关的信息。

一个内含 widget 的界面,这个 widget 显示了使用 Google 助理插件找到的上次记录的活动。

7. 后续步骤

恭喜你!

你现在能够借助 Google 助理利用 Android widget 来执行用户的询问了。

所学内容

在本 Codelab 中,你学习了如何:

  • 向 BII 中添加应用 widget。
  • 修改 widget 以从 Android Extras 中存取参数。

后续行动

接下来,你可以尝试进一步优化你的健身应用。如需参考已完成的项目,请参阅 GitHub 上的主代码库

如需进一步了解如何使用与应用有关的 Action 扩展该应用,请参阅以下建议:

如需继续了解 Actions on Google,请浏览下列资源:

欢迎关注我们的 Twitter 帐号 @ActionsOnGoogle,及时了解我们的最新公告,还可以使用标签 #appactions 发布 Twitter 微博,分享你构建的成果!

反馈意见调查

最后,请填写该调查问卷,就你学习本 Codelab 的体验提供反馈意见。