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 将不会跟踪所有本地更改,您可以点击右上角的 Ignore 或 X。(您所做的任何更改都不会保存到 Git 代码库中。)
如果您采用的是 Android 视图,那么在项目窗口的左上角应该会看到类似下图所示的内容。(如果您采用的是 Project 视图,那么需要展开项目才能看到这些内容。)
您可以看到两个文件夹图标(base
和 complete
)。它们都称为“模块”。
请注意,首次打开项目时,Android Studio 可能需要数秒时间在后台编译项目。在此期间,您会在 Android Studio 底部的状态栏中看到一个旋转图标:
建议您等待此过程完成后再更改代码。这样,Android Studio 就可以提取所有必要的组件。
此外,如果您看到“Reload for language changes to take effect?”的提示或类似内容,请选择“Yes”。
了解初始项目
现在,您已经完成准备工作,可以开始添加运动状态识别功能了。我们将使用 base
模块,这是此 Codelab 的起点。换句话说,您将在每个步骤向 base
中添加代码。
complete
模块可用于检查您的工作,或在您遇到问题时提供参考。
关键组件概览:
MainActivity
:包含进行活动识别所需的所有代码。
模拟器设置
如果您在设置 Android 模拟器时需要帮助,请参阅运行应用一文。
运行初始项目
现在,我们来运行应用。
- 将 Android 设备连接到计算机或启动模拟器。
- 在工具栏中,从下拉选择器中选择
base
配置,然后点击旁边的绿色三角形(运行)按钮:
- 您应该会看到以下应用:
- 目前,该应用除了输出消息之外,不会执行任何操作。现在,我们将添加活动识别功能。
总结
在此步骤中,您学习了以下内容:
- 此 Codelab 的常规设置。
- 应用的基础知识。
- 如何部署应用。
3. 查看库并向清单添加权限
如需在应用中使用 Transition API,您必须在应用清单中声明依赖 Google Location and Activity Recognition API 并指定 com.google.android.gms.permission.ACTIVITY_RECOGNITION 权限。
- 在 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'
- 在
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 转换更新,您必须实现以下两项:
- ActivityTransitionRequest 对象,用于指定 activity 的类型和转换。
- PendingIntent 回调,可让应用接收通知。如需了解详情,请参阅使用待定 intent。
创建要遵循的 ActivityTransition 列表
如需创建 ActivityTransitionRequest 对象,您必须创建 ActivityTransition 对象的列表,用来表示您要跟踪的转换。ActivityTransition 对象包含以下数据:
- activity 类型,由 DetectedActivity 类表示。Transition API 支持以下 activity:
- 转换类型,由 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_ENTER 和 ACTIVITY_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 转换并在屏幕上列出这些转换。
您随时可以浏览完整代码,查看您所完成的工作,并更好地了解代码如何协同运行。