Android 传感器(二)— 使用传感器实现步数统计

2,340 阅读3分钟

相信大家对步数统计并不陌生,微信运动有步数排行榜,支付宝根据步数可以获取蚂蚁森林的能量。其实安卓设备包含的传感器可以轻松的实现步数统计,本文介绍一下如何使用传感器实现步数统计。

步数统计

Android提供的计步器传感器步测器传感器都能实现统计步数的功能。

申请权限

当设备API版本为 Android10(29)以上时,需要申请ACTIVITY_RECOGNITION权限,才能正常使用计步器传感器和步测器传感器,代码如下:

  • 在Manifest中添加权限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
</manifest>
  • 动态申请权限
class SensorExampleActivity : AppCompatActivity() {

    private lateinit var binding: LayoutSensorExampleActivityBinding

    private var requestPermissionName: String = ""

    private val requestSinglePermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted: Boolean ->
        if (granted) {
            // 同意授权,可以使用计步传感器和步测传感器
        } else {
            // 未同意授权
            if (!shouldShowRequestPermissionRationale(requestPermissionName)) {
                //用户拒绝权限并且系统不再弹出请求权限的弹窗
                //这时需要我们自己处理,比如自定义弹窗告知用户为何必须要申请这个权限
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = LayoutSensorExampleActivityBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.includeTitle.tvTitle.text = "Sensor Example"
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            requestPermissionName = Manifest.permission.ACTIVITY_RECOGNITION
            if (ActivityCompat.checkSelfPermission(this, requestPermissionName) == PackageManager.PERMISSION_GRANTED) {
                // 已同意授权,可以使用计步传感器和步测传感器
            } else {
                requestSinglePermissionLauncher.launch(requestPermissionName)
            }
        }
    }
}

计步器传感器

计步器传感器提供的是自传感器激活后最后一次重启以来用户行走的总步数。与步测器传感器相比,计步器传感器的采样延迟时间更长(最多10秒),但精确度更高。

使用计步器传感器实现步数统计代码如下:

class SensorExampleActivity : AppCompatActivity() {

    private lateinit var binding: LayoutSensorExampleActivityBinding

    private lateinit var sensorManager: SensorManager
    private var stepCounterSensor: Sensor? = null

    private var isSensorListenerRegister = false

    private var accumulatedSteps = -1

    private var currentStep = 0

    private var requestPermissionName: String = ""

    private val requestSinglePermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted: Boolean ->
        if (granted) {
            initStepSensor()
        } else {
            // 未同意授权
            if (!shouldShowRequestPermissionRationale(requestPermissionName)) {
                //用户拒绝权限并且系统不再弹出请求权限的弹窗
                //这时需要我们自己处理,比如自定义弹窗告知用户为何必须要申请这个权限
            }
        }
    }

    private fun checkActivityRecognitionPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            requestPermissionName = Manifest.permission.ACTIVITY_RECOGNITION
            if (ActivityCompat.checkSelfPermission(this, requestPermissionName) == PackageManager.PERMISSION_GRANTED) {
                initStepSensor()
            } else {
                requestSinglePermissionLauncher.launch(requestPermissionName)
            }
        }
    }

    private fun initStepSensor() {
        stepCounterSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
        registerSensorListener()
    }

    private val sensorEventListener = object : SensorEventListener {
        override fun onSensorChanged(event: SensorEvent?) {
            // 传感器数据变化时回调此方法
            when (event?.sensor?.type) {
                Sensor.TYPE_STEP_COUNTER -> {
                    // 注意,计步器传感器返回的数据
                    // 是自计步器传感器上次重启以来用户行走的总的步数
                    currentStep = if (accumulatedSteps == -1) {
                        accumulatedSteps = event.values[0].toInt()
                        0
                    } else {
                        event.values[0].toInt() - accumulatedSteps
                    }
                    binding.tvStepCount.run { post { text = "传感器回调步数:${ event.values[0].toInt()}\n首次回调步数:$accumulatedSteps\n本次行走步数:$currentStep" } }
                }
            }
        }

        override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
            // 传感器的精度发生变化时回调此方法,通常无需做处理
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = LayoutSensorExampleActivityBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.includeTitle.tvTitle.text = "Sensor Example"
        binding.tvStepCount.visibility = View.VISIBLE
        
        sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
        checkActivityRecognitionPermission()
    }

    override fun onResume() {
        super.onResume()
        registerSensorListener()
    }

    override fun onPause() {
        super.onPause()
        // 移除传感器监听
        sensorManager.unregisterListener(sensorEventListener)
        isSensorListenerRegister = false
    }

    private fun registerSensorListener() {
        stepCounterSensor?.let {
            if (!isSensorListenerRegister) {
                isSensorListenerRegister = true
                // 注册传感器监听并且设置数据采样延迟
                // SensorManager.SENSOR_DELAY_FASTEST 延迟0微妙
                // SensorManager.SENSOR_DELAY_GAME 演示20000微妙
                // SensorManager.SENSOR_DELAY_UI 延迟60000微妙
                // SensorManager.SENSOR_DELAY_NORMAL 延迟200000微秒
                sensorManager.registerListener(sensorEventListener, it, SensorManager.SENSOR_DELAY_NORMAL)
            }
        }
    }
}

效果如图:

计步器 -original-original.gif

步测器传感器

步测器传感器在用户每次迈步时都会触发,采样延迟时间预计低于2秒。

使用步测器传感器实现步数统计代码如下:

class SensorExampleActivity : AppCompatActivity() {

    private lateinit var binding: LayoutSensorExampleActivityBinding

    private lateinit var sensorManager: SensorManager
    private var stepDetectorSensor: Sensor? = null

    private var isSensorListenerRegister = false

    private var currentStep = 0

    private var requestPermissionName: String = ""

    private val requestSinglePermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted: Boolean ->
        if (granted) {
            initStepSensor()
        } else {
            // 未同意授权
            if (!shouldShowRequestPermissionRationale(requestPermissionName)) {
                //用户拒绝权限并且系统不再弹出请求权限的弹窗
                //这时需要我们自己处理,比如自定义弹窗告知用户为何必须要申请这个权限
            }
        }
    }

    private fun checkActivityRecognitionPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            requestPermissionName = Manifest.permission.ACTIVITY_RECOGNITION
            if (ActivityCompat.checkSelfPermission(this, requestPermissionName) == PackageManager.PERMISSION_GRANTED) {
                initStepSensor()
            } else {
                requestSinglePermissionLauncher.launch(requestPermissionName)
            }
        }
    }

    private fun initStepSensor() {
        stepDetectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR)
        registerSensorListener()
    }

    private val sensorEventListener = object : SensorEventListener {
        override fun onSensorChanged(event: SensorEvent?) {
            // 传感器数据变化时回调此方法
            when (event?.sensor?.type) {
                Sensor.TYPE_STEP_DETECTOR -> {
                    currentStep += event.values[0].toInt()
                    binding.tvStepCount.run { post { text = "Step:$currentStep" } }
                }
            }
        }

        override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
            // 传感器的精度发生变化时回调此方法,通常无需做处理
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = LayoutSensorExampleActivityBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.includeTitle.tvTitle.text = "Sensor Example"
        binding.tvStepCount.visibility = View.VISIBLE
        
        sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
        checkActivityRecognitionPermission()
    }

    override fun onResume() {
        super.onResume()
        registerSensorListener()
    }

    override fun onPause() {
        super.onPause()
        // 移除传感器监听
        sensorManager.unregisterListener(sensorEventListener)
        isSensorListenerRegister = false
    }

    private fun registerSensorListener() {
        stepDetectorSensor?.let {
            if (!isSensorListenerRegister) {
                isSensorListenerRegister = true
                // 注册传感器监听并且设置数据采样延迟
                // SensorManager.SENSOR_DELAY_FASTEST 延迟0微妙
                // SensorManager.SENSOR_DELAY_GAME 演示20000微妙
                // SensorManager.SENSOR_DELAY_UI 延迟60000微妙
                // SensorManager.SENSOR_DELAY_NORMAL 延迟200000微秒
                sensorManager.registerListener(sensorEventListener, it, SensorManager.SENSOR_DELAY_NORMAL)
            }
        }
    }
}

效果如图:

步测器 -original-original.gif

示例

演示代码已在示例Demo中添加。

ExampleDemo github

ExampleDemo gitee