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}")
        }

这里有个坑,我刚开始做的时候踩过——PendingIntentFLAG_IMMUTABLE 在 Android 12 以上是必须的。如果你忘了加,低版本没问题,高版本直接崩溃。

注意:ActivityRecognitionClient 需要 Google Play Services 支持。如果你的设备没有 Google 服务框架,这个 API 会直接返回错误。我曾经在海外版平板上吃过这个亏。

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_FOOTWALKING 有什么区别?

其实 ON_FOOT 是一个更宽泛的分类。当系统不确定你是走路还是跑步时,它会返回 ON_FOOT。而 WALKINGRUNNING 是更精确的子类型。我在项目中通常先判断 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%。后来发现是因为桌子在震动。

我的经验:不要只看最高置信度的活动。建议取前两个结果做交叉验证。比如第一个是步行(70%),第二个是静止(30%),那大概率是步行。但如果第一个是驾车(40%),第二个是步行(38%),那最好再等几秒,收集更多数据。

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 知识体系总览

我把这一章的核心逻辑画成了图,方便你理解整体流程:

Activity识别核心流程 传感器数据 ActivityRecognitionClient DetectedActivity 列表(含置信度) 速度 + 加速度辅助 业务规则过滤

15.6 避坑指南

最后,分享几个我踩过的坑:

  • 不要在主线程调用——requestActivityUpdates 虽然是异步的,但回调可能不在主线程。记得用 Handler 切回主线程更新 UI。
  • 注意权限——Android 10 以上需要 ACTIVITY_RECOGNITION 权限。运行时申请时,用户可能会拒绝。我建议在拒绝后降级使用 SensorManager 自己算。
  • 省电策略——ActivityRecognition 本身很省电,但如果你同时开了 GPS 和网络定位,电量消耗会翻倍。我一般只在需要精确判断时才开启 GPS。
  • 测试环境——模拟器上 ActivityRecognition 返回的结果不准。真机测试时,建议拿着手机走几步、跑几步、坐车兜一圈。我曾经在模拟器上测了一整天,结果全是 UNKNOWN。

核心要点回顾:

  • ActivityRecognitionClient 是入口,通过 PendingIntent 接收回调
  • DetectedActivity 有 8 种类型,置信度是关键指标
  • 运动状态判断要结合速度和加速度,不要只看 API 结果
  • 权限和电量是需要提前规划好的事情

好了,这一章就到这里。Activity 识别这块内容,说白了就是「传感器 + 模型 + 业务规则」的组合。你只要把这三层理清楚,代码写起来就很顺。

公众号:蓝海资料掘金营,微信deep3321