Activity Recognition Transition API Codelab

1. 简介

我们随时随地都携带手机,但到目前为止,应用很难根据用户不断变化的环境和活动调整其体验。

过去,为了实现这一点,开发者需要花费宝贵的工程时间来整合各种信号(位置、传感器等),以确定步行或驾车等活动的开始或结束时间。更糟糕的是,如果应用独立且持续地检查用户活动的变化,电池续航时间会受到影响。

Activity Recognition Transition API 通过提供一个简单的 API 来解决这些问题,该 API 会为您处理所有工作,只告诉您真正关心的事情:用户的 activity 何时发生变化。您的应用只需订阅感兴趣的 activity 中的转换,该 API 就会通知您发生的变化

例如,即时通讯应用可以询问“用户进入或离开车辆时通知我”,以将用户的状态设为忙碌。同样,停车检测应用可以询问“请告诉我用户何时下车并开始步行”,以保存用户的停车位置。

在此 Codelab 中,您将学习如何使用 Activity Recognition Transition API 确定用户何时开始/停止步行或跑步等活动。

前提条件

熟悉 Android 开发,并对回调有一定了解。

学习内容

  • 注册 activity 转换
  • 处理这些事件
  • 不再需要 activity 转换时取消注册 activity 转换

所需条件

  • Android Studio Bumblebee
  • 一台 Android 设备或模拟器

2. 使用入门

克隆初始项目代码库

为帮助您尽快入门,我们准备了一个入门级项目,您可以在此项目的基础上进行构建。如果您已安装 git,只需运行以下命令即可。(您可以在终端/命令行中输入 git --version 进行检查,验证其是否正确执行。)

 git clone https://github.com/android/codelab-activity_transitionapi

如果未安装 git,您可以将项目下载为 ZIP 文件:

导入项目

启动 Android Studio,然后在欢迎屏幕中选择“Open an existing Android Studio project”,以打开项目目录。

项目加载完成后,您可能还会看到一条提醒,指出 Git 将不会跟踪所有本地更改,您可以点击右上角的 IgnoreX。(您所做的任何更改都不会保存到 Git 代码库中。)

如果您采用的是 Android 视图,那么在项目窗口的左上角应该会看到类似下图所示的内容。(如果您采用的是 Project 视图,那么需要展开项目才能看到这些内容。)

d2363db913d8e5ad.png

您可以看到两个文件夹图标(basecomplete)。它们都称为“模块”。

请注意,首次打开项目时,Android Studio 可能需要数秒时间在后台编译项目。在此期间,您会在 Android Studio 底部的状态栏中看到一个旋转图标:

c9f23d5336be3cfe.png

建议您等待此过程完成后再更改代码。这样,Android Studio 就可以提取所有必要的组件。

此外,如果您看到“Reload for language changes to take effect?”的提示或类似内容,请选择“Yes”。

了解初始项目

现在,您已经完成准备工作,可以开始添加运动状态识别功能了。我们将使用 base 模块,这是此 Codelab 的起点。换句话说,您将在每个步骤向 base 中添加代码。

complete 模块可用于检查您的工作,或在您遇到问题时提供参考。

关键组件概览:

  • MainActivity:包含进行活动识别所需的所有代码。

模拟器设置

如果您在设置 Android 模拟器时需要帮助,请参阅运行应用一文。

运行初始项目

现在,我们来运行应用。

  • 将 Android 设备连接到计算机或启动模拟器。
  • 在工具栏中,从下拉选择器中选择 base 配置,然后点击旁边的绿色三角形(运行)按钮:

a640a291ffaf62ad.png

  • 您应该会看到以下应用:

f58d4bb92ee77f41.png

  • 目前,该应用除了输出消息之外,不会执行任何操作。现在,我们将添加活动识别功能。

总结

在此步骤中,您学习了以下内容:

  • 此 Codelab 的常规设置。
  • 应用的基础知识。
  • 如何部署应用。

3. 查看库并向清单添加权限

如需在应用中使用 Transition API,您必须在应用清单中声明依赖 Google Location and Activity Recognition API 并指定 com.google.android.gms.permission.ACTIVITY_RECOGNITION 权限。

  1. 在 build.gradle 文件中,搜索 TODO: Review play services library required for activity recognition。此步骤(第 1 步)无需执行任何操作,只需查看我们要求声明的依赖项即可。代码应如下所示:
    // TODO: Review play services library required for activity recognition.
    implementation 'com.google.android.gms:play-services-location:19.0.1'
  1. base 模块的 AndroidManifest.xml 中,搜索 TODO: Add both activity recognition permissions to the manifest,并将以下代码添加到 <manifest> 元素中。
<!-- TODO: Add both activity recognition permissions to the manifest. -->
<!-- Required for 28 and below. -->
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<!-- Required for 29+. -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />

现在,您的代码应如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.example.myapp">
<!-- TODO: Add both activity recognition permissions to the manifest. -->
<!-- Required for 28 and below. -->
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<!-- Required for 29+. -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />

  ...
</manifest>

从注释中可以看出,您需要为 Android 10 添加第二项权限。这是 API 版本 29 中添加的运行时权限的必需条件。

大功告成!您的应用现在可以支持活动识别功能,只需添加相应代码即可。

运行应用

从 Android Studio 运行您的应用。它应该完全相同。我们尚未添加任何代码来跟踪转换,后续部分将详细介绍这一操作。

4. 在 Android 中检查/请求运行时权限

虽然我们已涵盖 API 28 及更低版本的权限,但我们需要在 API 29 及更高版本中支持运行时权限:

  • MainActivity.java 中,我们将检查用户是否使用的是 Android 10 (29) 或更高版本,如果是,我们将检查运动状态识别权限。
  • 如果未授予权限,我们会将用户转到启动画面 (PermissionRationalActivity.java),其中解释了应用为何需要该权限并允许他们批准该权限。

查看用于检查 Android 版本的代码

base 模块的 MainActivity.java 中,搜索 TODO: Review check for devices with Android 10 (29+)。您应该会看到以下代码段。

请注意,这一部分无需采取任何操作。

// TODO: Review check for devices with Android 10 (29+).
private boolean runningQOrLater =
    android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q;

如前所述,您需要在 Android 10 及更高版本中获得运行时权限 android.permission.ACTIVITY_RECOGNITION 的批准。我们使用此简单检查来确定是否需要检查运行时权限。

根据需要查看运动状态识别的运行时权限检查

base 模块的 MainActivity.java 中,搜索 TODO: Review permission check for 29+。您应该会看到以下代码段。

请注意,这一部分无需采取任何操作。

// TODO: Review permission check for 29+.
if (runningQOrLater) {

   return PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
           this,
           Manifest.permission.ACTIVITY_RECOGNITION
   );
} else {
   return true;
}

我们使用在上一步中创建的变量来确定是否需要检查运行时权限。

对于 Android Q 及更高版本,我们会检查运行时权限并返回结果。这是名为 activityRecognitionPermissionApproved() 的更大方法的一部分,该方法只需一调用即可让开发者知道我们是否需要请求权限。

请求运行时权限并启用/停用运动状态识别转换

base 模块的 MainActivity.java 中,搜索 TODO: Enable/Disable activity tracking and ask for permissions if needed。在注释后面添加以下代码。

// TODO: Enable/Disable activity tracking and ask for permissions if needed.
if (activityRecognitionPermissionApproved()) {

   if (activityTrackingEnabled) {
      disableActivityTransitions();

   } else {
      enableActivityTransitions();
   }

} else {  
   // Request permission and start activity for result. If the permission is approved, we
   // want to make sure we start activity recognition tracking.
   Intent startIntent = new Intent(this, PermissionRationalActivity.class);
   startActivityForResult(startIntent, 0);

}

在这里,我们会询问是否已批准运动状态识别。如果已启用运动状态识别,我们会将其停用。否则,我们会启用该功能。

如果该权限请求未获批准,我们会将用户转到启动画面 activity,其中解释了我们为何需要该权限并允许他们启用该权限。

查看权限请求代码

base 模块的 PermissionRationalActivity.java 中,搜索 TODO: Review permission request for activity recognition。您应该会看到以下代码段。

请注意,这一部分无需采取任何操作。

// TODO: Review permission request for activity recognition.
ActivityCompat.requestPermissions(
             this,
             new String[]{Manifest.permission.ACTIVITY_RECOGNITION},
             PERMISSION_REQUEST_ACTIVITY_RECOGNITION)

这是 activity 最重要的部分,也是需要审核的部分。该代码会在用户请求时触发权限请求。

此外,PermissionRationalActivity.java 类会显示用户应批准活动识别权限的原因(最佳实践)。用户可以点击不用了按钮或继续按钮(这会触发上述代码)。

如果您想了解详情,欢迎随时查看该文件。

5. 注册/取消注册 activity 转换接收器

在设置 activity 识别代码之前,我们希望确保 activity 可以处理系统引发的转换操作。

为转场效果创建 BroadcastReceiver

base 模块的 MainActivity.java 中,搜索 TODO: Create a BroadcastReceiver to listen for activity transitions。将代码段粘贴到下方。

// TODO: Create a BroadcastReceiver to listen for activity transitions.
// The receiver listens for the PendingIntent above that is triggered by the system when an
// activity transition occurs.
mTransitionsReceiver = new TransitionsReceiver();

为转换注册 BroadcastReceiver

base 模块的 MainActivity.java 中,搜索 TODO: Register a BroadcastReceiver to listen for activity transitions。(位于 onStart() 中)。将以下代码段粘贴到该位置。

// TODO: Register a BroadcastReceiver to listen for activity transitions.
registerReceiver(mTransitionsReceiver, new IntentFilter(TRANSITIONS_RECEIVER_ACTION));

现在,我们可以在通过 PendingIntent 引发 activity 转换时获取更新。

取消注册 BroadcastReceiver

base 模块的 MainActivity.java 中,搜索 Unregister activity transition receiver when user leaves the app。(位于 onStop() 中)。将以下代码段粘贴到该位置。

// TODO: Unregister activity transition receiver when user leaves the app.
unregisterReceiver(mTransitionsReceiver);

最佳实践是在 Activity 关闭时取消注册接收器。

6. 设置 activity 转换和请求更新

如需开始接收 activity 转换更新,您必须实现以下两项:

创建要遵循的 ActivityTransition 列表

如需创建 ActivityTransitionRequest 对象,您必须创建 ActivityTransition 对象的列表,用来表示您要跟踪的转换。ActivityTransition 对象包含以下数据:

  1. activity 类型,由 DetectedActivity 类表示。Transition API 支持以下 activity:
  1. 转换类型,由 ActivityTransition 类表示。过渡类型包括:

base 模块的 MainActivity.java 中,搜索 TODO: Add activity transitions to track。在注释后面添加以下代码。

// TODO: Add activity transitions to track.
activityTransitionList.add(new ActivityTransition.Builder()
        .setActivityType(DetectedActivity.WALKING)
        .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
        .build());
activityTransitionList.add(new ActivityTransition.Builder()
        .setActivityType(DetectedActivity.WALKING)
        .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
        .build());
activityTransitionList.add(new ActivityTransition.Builder()
        .setActivityType(DetectedActivity.STILL)
        .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
        .build());
activityTransitionList.add(new ActivityTransition.Builder()
        .setActivityType(DetectedActivity.STILL)
        .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
        .build());

此代码会将我们要跟踪的转场效果添加到之前为空的列表中。

创建 PendingIntent

如前所述,如果我们希望在 ActivityTransitionRequest 发生任何更改时收到提醒,则需要 PendingIntent。因此,在设置 ActivityTransitionRequest 之前,我们需要创建 PendingIntent

base 模块的 MainActivity.java 中,搜索 TODO: Initialize PendingIntent that will be triggered when a activity transition occurs。在注释后面添加以下代码。

// TODO: Initialize PendingIntent that will be triggered when a activity transition occurs.
Intent intent = new Intent(TRANSITIONS_RECEIVER_ACTION);
mActivityTransitionsPendingIntent =
        PendingIntent.getBroadcast(MainActivity.this, 0, intent, 0);

现在,我们有一个 PendingIntent,可以在发生某个 ActivityTransition 时触发它。

创建 ActivityTransitionRequest 并请求更新

您可以通过将 ActivityTransition 列表传递给 ActivityTransitionRequest 类来创建 ActivityTransitionRequest 对象。

base 模块的 MainActivity.java 中,搜索 Create request and listen for activity changes。在注释后面添加以下代码。

// TODO: Create request and listen for activity changes.
ActivityTransitionRequest request = new ActivityTransitionRequest(activityTransitionList);

// Register for Transitions Updates.
Task<Void> task =
        ActivityRecognition.getClient(this)
                .requestActivityTransitionUpdates(request, mActivityTransitionsPendingIntent);


task.addOnSuccessListener(
        new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void result) {
                activityTrackingEnabled = true;
                printToScreen("Transitions Api was successfully registered.");

            }
        });
task.addOnFailureListener(
        new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                printToScreen("Transitions Api could NOT be registered: " + e);
                Log.e(TAG, "Transitions Api could NOT be registered: " + e);

            }
        });

我们来回顾一下代码。首先,我们从 activity 转换列表中创建 ActivityTransitionRequest

ActivityTransitionRequest request = new ActivityTransitionRequest(activityTransitionList);

接下来,我们将在 requestActivityTransitionUpdates() 方法中传递 ActivityTransitionRequest 实例和我们在上一步创建的 PendingIntent 对象,以注册 activity 转换更新。requestActivityTransitionUpdates() 方法会返回一个 Task 对象,您可以检查此方法是否成功,如代码的下一个代码块所示:

// Register for Transitions Updates.
Task<Void> task =
        ActivityRecognition.getClient(this)
                .requestActivityTransitionUpdates(request, mActivityTransitionsPendingIntent);


task.addOnSuccessListener(
        new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void result) {
                activityTrackingEnabled = true;
                printToScreen("Transitions Api was successfully registered.");

            }
        });
task.addOnFailureListener(
        new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                printToScreen("Transitions Api could NOT be registered: " + e);
                Log.e(TAG, "Transitions Api could NOT be registered: " + e);

            }
        });

成功注册 activity 转换更新后,您的应用会在已注册的 PendingIntent 中接收通知。我们还设置了一个变量,用于表示活动跟踪功能处于启用状态,以便我们知道在用户再次点击该按钮时是否要停用/启用该功能。

在应用关闭时移除更新

请务必在应用关闭时移除转换更新。

base 模块的 MainActivity.java 中,搜索 Stop listening for activity changes。在注释后面添加以下代码。

// TODO: Stop listening for activity changes.
ActivityRecognition.getClient(this).removeActivityTransitionUpdates(mActivityTransitionsPendingIntent)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                activityTrackingEnabled = false;
                printToScreen("Transitions successfully unregistered.");
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                printToScreen("Transitions could not be unregistered: " + e);
                Log.e(TAG,"Transitions could not be unregistered: " + e);
            }
        });

现在,我们需要在应用关闭时调用包含上述代码的方法

base 模块的 onPause() 中,在 MainActivity.java 中搜索 TODO: Disable activity transitions when user leaves the app。在注释后面添加以下代码。

// TODO: Disable activity transitions when user leaves the app.
if (activityTrackingEnabled) {
    disableActivityTransitions();
}

至此,关于跟踪 activity 转换的更改就介绍完了。现在,我们只需处理更新即可。

7. 处理事件

当发生请求的 activity 转换时,您的应用会收到 Intent 回调。您可以从 intent 中提取 ActivityTransitionResult 对象,其中包括 ActivityTransitionEvent 对象的列表。这些事件按时间先后顺序排序,例如,如果应用在 ACTIVITY_TRANSITION_ENTERACTIVITY_TRANSITION_EXIT 转换时请求 IN_VEHICLE activity 类型,那么它会在用户开始驾车时收到 ActivityTransitionEvent 对象,并在用户转换到任何其他 activity 时收到另一个对象。

我们来添加处理这些事件的代码。

base 模块的 MainActivity.java 中,搜索我们之前创建的 BroadcastReceiver 的 onReceive() 中的 TODO: Extract activity transition information from listener。在注释后面添加以下代码。

// TODO: Extract activity transition information from listener.
if (ActivityTransitionResult.hasResult(intent)) {

    ActivityTransitionResult result = ActivityTransitionResult.extractResult(intent);

    for (ActivityTransitionEvent event : result.getTransitionEvents()) {

        String info = "Transition: " + toActivityString(event.getActivityType()) +
                " (" + toTransitionType(event.getTransitionType()) + ")" + "   " +
                new SimpleDateFormat("HH:mm:ss", Locale.US).format(new Date());

        printToScreen(info);
    }
}

这会将信息转换为 String 并将其输出到屏幕。

大功告成,此步骤已完成!请尝试运行应用。

重要提示:在模拟器上很难重现 activity 更改,因此我们建议您使用实体设备。

您应该能够跟踪 activity 更改。

为获得最佳效果,请在实体设备上安装该应用,然后四处走动。:)

8. 查看代码

您构建了一个简单的应用,用于跟踪 activity 转换并在屏幕上列出这些转换。

您随时可以浏览完整代码,查看您所完成的工作,并更好地了解代码如何协同运行。