15、Activity识别:ActivityRecognitionClient、DetectedActivity类型、运动状态判断(步行/跑步/驾车)
各位同学,今天我们来聊一个挺有意思的话题——Activity识别。
说白了,就是让手机自己猜你正在干嘛。走路?跑步?还是开车?
我第一次接触这个API时,觉得它像个黑魔法。手机又没长眼睛,怎么知道我在运动?后来研究源码才发现,它靠的是加速度计和陀螺仪的数据,再加上Google的机器学习模型。嗯,其实没那么玄乎。
15.1 ActivityRecognitionClient 的基本用法
先说说怎么用这个API。核心类就一个——ActivityRecognitionClient。
我个人习惯把它封装成一个单例,方便全局调用。来看代码:
// 初始化客户端
val client = ActivityRecognitionClient(context)
// 创建请求
val request = ActivityTransitionRequest(
listOf(
ActivityTransition.Builder()
.setActivityType(DetectedActivity.STILL)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build(),
ActivityTransition.Builder()
.setActivityType(DetectedActivity.WALKING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build()
)
)
// 注册监听
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
Intent(context, ActivityTransitionReceiver::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
client.requestActivityTransitionUpdates(request, pendingIntent)
.addOnSuccessListener {
Log.d(TAG, "Activity transition updates started")
}
.addOnFailureListener { e ->
Log.e(TAG, "Failed to start: ${e.message}")
}
这里有个坑,我刚开始做的时候踩过——PendingIntent 的 FLAG_IMMUTABLE 在 Android 12 以上是必须的。如果你忘了加,低版本没问题,高版本直接崩溃。
15.2 DetectedActivity 类型详解
Google 定义了一套标准的活动类型。我整理了一张表,方便你查阅:
| 类型常量 | 数值 | 说明 | 置信度要求 |
|---|---|---|---|
| IN_VEHICLE | 0 | 在交通工具中(汽车/公交/火车) | 通常 > 70% |
| ON_BICYCLE | 1 | 骑行中 | > 60% |
| ON_FOOT | 2 | 步行或跑步(通用) | > 50% |
| STILL | 3 | 静止不动 | > 80% |
| UNKNOWN | 4 | 无法识别 | - |
| TILTING | 5 | 手机倾斜(拿起/放下) | > 50% |
| WALKING | 7 | 步行 | > 60% |
| RUNNING | 8 | 跑步 | > 60% |
你可能会问:ON_FOOT 和 WALKING 有什么区别?
其实 ON_FOOT 是一个更宽泛的分类。当系统不确定你是走路还是跑步时,它会返回 ON_FOOT。而 WALKING 和 RUNNING 是更精确的子类型。我在项目中通常先判断 ON_FOOT,再进一步区分步行和跑步。
15.3 运动状态判断的核心逻辑
判断运动状态,说白了就是看置信度。每个 DetectedActivity 都带一个 confidence 字段,范围 0-100。
举个例子:
fun determineActivity(probableActivities: List<DetectedActivity>): String {
// 按置信度降序排列
val sorted = probableActivities.sortedByDescending { it.confidence }
val top = sorted.firstOrNull() ?: return "未知"
return when (top.type) {
DetectedActivity.IN_VEHICLE -> "驾车"
DetectedActivity.ON_BICYCLE -> "骑行"
DetectedActivity.WALKING -> "步行"
DetectedActivity.RUNNING -> "跑步"
DetectedActivity.STILL -> "静止"
else -> "其他"
}
}
但这里有个问题——置信度不是100%可靠的。我记得有一次测试,手机放在桌上,系统居然识别成了步行,置信度还给了65%。后来发现是因为桌子在震动。
15.4 实战:区分步行、跑步和驾车
在实际项目中,我一般会结合速度和加速度变化来做判断。ActivityRecognition 给出的结果只是一个参考,真正的业务逻辑需要自己打磨。
来看一个我常用的判断策略:
fun classifyMovement(
activity: DetectedActivity,
speed: Float, // 单位:m/s
acceleration: Float // 单位:m/s²
): String {
// 先看系统识别结果
when (activity.type) {
DetectedActivity.IN_VEHICLE -> {
// 系统说是驾车,但速度很慢?可能是堵车
return if (speed < 2.0f) "驾车(低速)" else "驾车"
}
DetectedActivity.RUNNING -> {
// 跑步速度一般在 2-6 m/s
return if (speed in 2.0f..6.0f) "跑步" else "疑似跑步"
}
DetectedActivity.WALKING -> {
// 步行速度一般在 0.5-2 m/s
return if (speed in 0.5f..2.0f) "步行" else "疑似步行"
}
else -> {
// 系统不确定,用速度辅助判断
return when {
speed > 8.0f -> "驾车"
speed > 2.0f -> "跑步"
speed > 0.5f -> "步行"
else -> "静止"
}
}
}
}
你可能会问:速度数据从哪来?用 Location.getSpeed() 就行。但要注意,这个值在室内可能不准,因为 GPS 信号弱。
15.5 知识体系总览
我把这一章的核心逻辑画成了图,方便你理解整体流程:
15.6 避坑指南
最后,分享几个我踩过的坑:
- 不要在主线程调用——
requestActivityUpdates虽然是异步的,但回调可能不在主线程。记得用Handler切回主线程更新 UI。 - 注意权限——Android 10 以上需要
ACTIVITY_RECOGNITION权限。运行时申请时,用户可能会拒绝。我建议在拒绝后降级使用SensorManager自己算。 - 省电策略——ActivityRecognition 本身很省电,但如果你同时开了 GPS 和网络定位,电量消耗会翻倍。我一般只在需要精确判断时才开启 GPS。
- 测试环境——模拟器上 ActivityRecognition 返回的结果不准。真机测试时,建议拿着手机走几步、跑几步、坐车兜一圈。我曾经在模拟器上测了一整天,结果全是 UNKNOWN。
核心要点回顾:
- ActivityRecognitionClient 是入口,通过 PendingIntent 接收回调
- DetectedActivity 有 8 种类型,置信度是关键指标
- 运动状态判断要结合速度和加速度,不要只看 API 结果
- 权限和电量是需要提前规划好的事情
好了,这一章就到这里。Activity 识别这块内容,说白了就是「传感器 + 模型 + 业务规则」的组合。你只要把这三层理清楚,代码写起来就很顺。
公众号:蓝海资料掘金营,微信deep3321