Activity Recognition Transition API Codelab

1. 简介

我们无论走到哪里都会随身携带手机,但直到现在,应用都很难根据用户不断变化的环境和活动来调整体验。

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

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

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

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

前提条件

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

学习内容

  • 注册 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 运动状态识别。此步骤(第 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;
}

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

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

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

base 模块的 MainActivity.java 中,搜索 TODO: Enable/Disable 活动记录 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)

这是活动中最重要且需要检查的部分。当用户请求权限时,代码会触发权限请求。

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

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

5. 注册/取消注册 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);

接下来,我们将 ActivityTransitionRequest 的实例和我们在上一步中创建的 PendingIntent 对象传递给 requestActivityTransitionUpdates() 方法,以注册 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 中,搜索 TODO: Extract activity transition information from listener,该代码位于我们之前创建的 BroadcastReceiver 的 onReceive() 中。在注释后添加以下代码。

// 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 更改,因此我们建议使用实体设备。

您应该能够记录活动更改。

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

8. 查看代码

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

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