项目概述

传感器与设备信息是现代应用获取设备状态和用户交互的重要方式。在 KMP 框架下,我们需要实现跨平台的传感器数据采集、设备信息获取和系统状态监测。本文详细介绍了如何在 Kotlin Multiplatform 中实现高效的传感器系统,包括加速度计、陀螺仪、磁力计和设备信息获取。

这类能力在以下场景尤其常见:

  • 运动/健康 App:通过加速度计、陀螺仪判断步数、姿态、运动强度;
  • 游戏/交互应用:利用重力感应、旋转等实现体感操作;
  • 系统监控与运营:采集设备型号、系统版本、电池状态等,用于统计和问题排查。

本篇的目标是:

  • 抽象统一的 SensorManagerDeviceInfoManager 接口,屏蔽不同平台的传感器/系统 API 差异;
  • 在 Android、iOS、鸿蒙及 Web 等平台下实现相同能力;
  • 为后续的传感器数据融合、设备状态监控等高级功能提供基础。

第一部分:传感器核心概念

传感器管理架构

// commonMain/kotlin/com/example/sensor/SensorManager.kt
expect class SensorManager {
    suspend fun startListening(sensorType: SensorType): Boolean
    suspend fun stopListening(sensorType: SensorType): Boolean
    suspend fun getSensorData(sensorType: SensorType): SensorData?
    suspend fun getAvailableSensors(): List<SensorInfo>
    fun addListener(listener: SensorListener)
}

enum class SensorType {
    ACCELEROMETER, GYROSCOPE, MAGNETOMETER, LIGHT, PROXIMITY, PRESSURE, TEMPERATURE, HUMIDITY
}

data class SensorData(
    val sensorType: SensorType,
    val values: FloatArray,
    val accuracy: Int,
    val timestamp: Long
)

data class SensorInfo(
    val name: String,
    val type: SensorType,
    val vendor: String,
    val version: Int,
    val power: Float,
    val maxRange: Float,
    val resolution: Float,
    val minDelay: Int,
    val maxDelay: Int
)

interface SensorListener {
    fun onSensorDataChanged(sensorData: SensorData)
    fun onAccuracyChanged(sensorType: SensorType, accuracy: Int)
    fun onError(error: String)
}

这一部分定义了跨平台统一的传感器访问模型:

  • SensorManager 提供开始/停止监听、拉取当前数据、查询可用传感器等能力;
  • SensorType 统一列出了常见的硬件传感器类型,便于在不同平台上映射;
  • SensorDataSensorInfo 分别代表一次传感器读数以及传感器本身的能力描述;
  • SensorListener 则负责把底层事件统一回调到业务层,方便做实时 UI 更新或数据采集。

通过这层抽象,你可以在 common 层编写,例如“摇一摇事件监听”“运动轨迹记录”“姿态检测”等逻辑,而不必直接和 Android/iOS/鸿蒙各自的传感器 API 打交道。

设备信息架构

// commonMain/kotlin/com/example/device/DeviceInfoManager.kt
expect class DeviceInfoManager {
    suspend fun getDeviceInfo(): DeviceInfo
    suspend fun getSystemInfo(): SystemInfo
    suspend fun getBatteryInfo(): BatteryInfo
    suspend fun getNetworkInfo(): NetworkInfo
    suspend fun getStorageInfo(): StorageInfo
    fun addListener(listener: DeviceInfoListener)
}

data class DeviceInfo(
    val manufacturer: String,
    val model: String,
    val brand: String,
    val device: String,
    val product: String,
    val hardware: String,
    val fingerprint: String,
    val buildId: String,
    val buildVersion: String,
    val sdkVersion: Int,
    val releaseVersion: String
)

data class SystemInfo(
    val osName: String,
    val osVersion: String,
    val cpuCores: Int,
    val totalMemory: Long,
    val availableMemory: Long,
    val uptime: Long
)

data class BatteryInfo(
    val level: Int,
    val temperature: Int,
    val voltage: Int,
    val health: String,
    val status: String,
    val isCharging: Boolean,
    val chargingTime: Long
)

interface DeviceInfoListener {
    fun onBatteryChanged(batteryInfo: BatteryInfo)
    fun onNetworkChanged(networkInfo: NetworkInfo)
    fun onStorageChanged(storageInfo: StorageInfo)
}

设备信息相关接口关注的是“设备是什么 + 当前状态如何”:

  • DeviceInfo 聚合了厂商、型号、硬件指纹、系统版本等静态信息,可用于日志、AB 实验、灰度配置等;
  • SystemInfoBatteryInfo 等则关注运行时状态(CPU 核心数、内存、电量、温度等),适合健康检查和性能调优;
  • DeviceInfoListener 把电池、网络、存储变化统一暴露给业务层,用于触发降级策略或告警提示。

在 KMP 结构中,通过统一的 DeviceInfoManager,可以构建跨平台的“设备/运行环境探测模块”,为埋点、运营配置和稳定性监控提供基础数据。

第二部分:平台特定实现

Android 传感器实现

// androidMain/kotlin/com/example/sensor/SensorManager.kt
import android.hardware.SensorManager as AndroidSensorManager
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.content.Context

actual class SensorManager(private val context: Context) {
    private val sensorManager = context.getSystemService(AndroidSensorManager::class.java)
    private val listeners = mutableListOf<SensorListener>()
    private val sensorListeners = mutableMapOf<SensorType, SensorEventListener>()
    
    actual suspend fun startListening(sensorType: SensorType): Boolean {
        return try {
            val androidSensorType = when (sensorType) {
                SensorType.ACCELEROMETER -> android.hardware.Sensor.TYPE_ACCELEROMETER
                SensorType.GYROSCOPE -> android.hardware.Sensor.TYPE_GYROSCOPE
                SensorType.MAGNETOMETER -> android.hardware.Sensor.TYPE_MAGNETIC_FIELD
                SensorType.LIGHT -> android.hardware.Sensor.TYPE_LIGHT
                SensorType.PROXIMITY -> android.hardware.Sensor.TYPE_PROXIMITY
                SensorType.PRESSURE -> android.hardware.Sensor.TYPE_PRESSURE
                SensorType.TEMPERATURE -> android.hardware.Sensor.TYPE_AMBIENT_TEMPERATURE
                SensorType.HUMIDITY -> android.hardware.Sensor.TYPE_RELATIVE_HUMIDITY
            }
            
            val sensor = sensorManager?.getDefaultSensor(androidSensorType)
            val eventListener = object : SensorEventListener {
                override fun onSensorChanged(event: SensorEvent) {
                    val sensorData = SensorData(
                        sensorType = sensorType,
                        values = event.values,
                        accuracy = event.accuracy,
                        timestamp = event.timestamp
                    )
                    listeners.forEach { it.onSensorDataChanged(sensorData) }
                }
                
                override fun onAccuracyChanged(sensor: android.hardware.Sensor?, accuracy: Int) {
                    listeners.forEach { it.onAccuracyChanged(sensorType, accuracy) }
                }
            }
            
            sensorListeners[sensorType] = eventListener
            sensorManager?.registerListener(eventListener, sensor, AndroidSensorManager.SENSOR_DELAY_NORMAL)
            true
        } catch (e: Exception) {
            listeners.forEach { it.onError(e.message ?: "Failed to start listening") }
            false
        }
    }
    
    actual suspend fun stopListening(sensorType: SensorType): Boolean {
        return try {
            sensorListeners[sensorType]?.let { listener ->
                sensorManager?.unregisterListener(listener)
                sensorListeners.remove(sensorType)
            }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun getAvailableSensors(): List<SensorInfo> {
        return try {
            sensorManager?.sensorList?.map { sensor ->
                SensorInfo(
                    name = sensor.name,
                    type = when (sensor.type) {
                        android.hardware.Sensor.TYPE_ACCELEROMETER -> SensorType.ACCELEROMETER
                        android.hardware.Sensor.TYPE_GYROSCOPE -> SensorType.GYROSCOPE
                        else -> SensorType.ACCELEROMETER
                    },
                    vendor = sensor.vendor,
                    version = sensor.version,
                    power = sensor.power,
                    maxRange = sensor.maxRange,
                    resolution = sensor.resolution,
                    minDelay = sensor.minDelay,
                    maxDelay = sensor.maxDelay
                )
            } ?: emptyList()
        } catch (e: Exception) {
            emptyList()
        }
    }
    
    actual fun addListener(listener: SensorListener) {
        listeners.add(listener)
    }
}

// Android 设备信息
actual class DeviceInfoManager {
    actual suspend fun getDeviceInfo(): DeviceInfo {
        return DeviceInfo(
            manufacturer = android.os.Build.MANUFACTURER,
            model = android.os.Build.MODEL,
            brand = android.os.Build.BRAND,
            device = android.os.Build.DEVICE,
            product = android.os.Build.PRODUCT,
            hardware = android.os.Build.HARDWARE,
            fingerprint = android.os.Build.FINGERPRINT,
            buildId = android.os.Build.ID,
            buildVersion = android.os.Build.VERSION.RELEASE,
            sdkVersion = android.os.Build.VERSION.SDK_INT,
            releaseVersion = android.os.Build.VERSION.RELEASE
        )
    }
    
    actual suspend fun getSystemInfo(): SystemInfo {
        val runtime = Runtime.getRuntime()
        return SystemInfo(
            osName = "Android",
            osVersion = android.os.Build.VERSION.RELEASE,
            cpuCores = Runtime.getRuntime().availableProcessors(),
            totalMemory = runtime.totalMemory(),
            availableMemory = runtime.freeMemory(),
            uptime = android.os.SystemClock.uptimeMillis()
        )
    }
    
    actual suspend fun getBatteryInfo(): BatteryInfo {
        // Implementation for battery info
        return BatteryInfo(
            level = 0,
            temperature = 0,
            voltage = 0,
            health = "GOOD",
            status = "CHARGING",
            isCharging = false,
            chargingTime = 0L
        )
    }
}

Android 端传感器与设备信息实现的要点:

  • 传感器部分使用 AndroidSensorManager + SensorEventListener,根据 SensorType 映射到具体的传感器常量;
  • onSensorChanged 中将 SensorEvent 转换为公共的 SensorData,并通过 SensorListener 广播给上层;
  • 设备信息利用 BuildSystemClock 等系统 API 获取设备型号、系统版本、内存等基础信息,并封装为 DeviceInfoSystemInfo 等。

在实际项目中可以进一步扩展:

  • 根据不同业务场景调整采样频率(SENSOR_DELAY_NORMAL/FASTEST),平衡功耗和精度;
  • 将电池、网络等信息和埋点/日志系统结合,便于线上问题定位;
  • 对部分敏感信息进行脱敏或聚合处理,符合隐私和合规要求。

iOS 传感器实现

// iosMain/kotlin/com/example/sensor/SensorManager.kt
import platform.CoreMotion.*
import platform.Foundation.*

actual class SensorManager {
    private val motionManager = CMMotionManager()
    private val listeners = mutableListOf<SensorListener>()
    
    actual suspend fun startListening(sensorType: SensorType): Boolean {
        return try {
            when (sensorType) {
                SensorType.ACCELEROMETER -> {
                    motionManager.startAccelerometerUpdatesToQueue(
                        NSOperationQueue.mainQueue()
                    ) { data, error ->
                        data?.acceleration?.let { acceleration ->
                            val sensorData = SensorData(
                                sensorType = sensorType,
                                values = floatArrayOf(
                                    acceleration.x.toFloat(),
                                    acceleration.y.toFloat(),
                                    acceleration.z.toFloat()
                                ),
                                accuracy = 0,
                                timestamp = System.currentTimeMillis()
                            )
                            listeners.forEach { it.onSensorDataChanged(sensorData) }
                        }
                    }
                }
                SensorType.GYROSCOPE -> {
                    motionManager.startGyroUpdatesToQueue(
                        NSOperationQueue.mainQueue()
                    ) { data, error ->
                        data?.rotationRate?.let { rotationRate ->
                            val sensorData = SensorData(
                                sensorType = sensorType,
                                values = floatArrayOf(
                                    rotationRate.x.toFloat(),
                                    rotationRate.y.toFloat(),
                                    rotationRate.z.toFloat()
                                ),
                                accuracy = 0,
                                timestamp = System.currentTimeMillis()
                            )
                            listeners.forEach { it.onSensorDataChanged(sensorData) }
                        }
                    }
                }
                else -> return false
            }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun stopListening(sensorType: SensorType): Boolean {
        return try {
            when (sensorType) {
                SensorType.ACCELEROMETER -> motionManager.stopAccelerometerUpdates()
                SensorType.GYROSCOPE -> motionManager.stopGyroUpdates()
                else -> return false
            }
            true
        } catch (e: Exception) {
            false
        }
    }
}

iOS 端示例主要演示了如何使用 CoreMotion

  • 通过 CMMotionManager 分别开启加速度计和陀螺仪数据流,将回调结果转换为公共的 SensorData
  • 根据不同 SensorType 选择开启/停止对应的数据流;
  • 在 KMP 中将这部分实现放在 iosMain,对上层完全透明。

在生产环境中,还可以配合 UIDeviceProcessInfo 等 API 获取更多设备与系统状态信息,并统一封装到 DeviceInfoManager 中。

第三部分:编译后的代码形式

JavaScript 编译版本

// 传感器管理器 (JavaScript/Web)
class SensorManager {
    constructor() {
        this.listeners = [];
        this.sensors = {};
    }
    
    async startListening(sensorType) {
        try {
            if (sensorType === 'ACCELEROMETER') {
                window.addEventListener('devicemotion', (event) => {
                    const sensorData = {
                        sensorType: 'ACCELEROMETER',
                        values: [
                            event.acceleration.x,
                            event.acceleration.y,
                            event.acceleration.z
                        ],
                        accuracy: 0,
                        timestamp: Date.now()
                    };
                    this.listeners.forEach(l => l.onSensorDataChanged(sensorData));
                });
            }
            return true;
        } catch (error) {
            return false;
        }
    }
    
    async stopListening(sensorType) {
        // Remove event listener
        return true;
    }
}

// 设备信息管理器
class DeviceInfoManager {
    async getDeviceInfo() {
        return {
            manufacturer: navigator.vendor,
            model: navigator.userAgent,
            brand: 'Web',
            device: 'Browser',
            product: navigator.platform,
            hardware: navigator.hardwareConcurrency,
            fingerprint: '',
            buildId: '',
            buildVersion: navigator.appVersion,
            sdkVersion: 0,
            releaseVersion: navigator.appVersion
        };
    }
    
    async getSystemInfo() {
        return {
            osName: navigator.platform,
            osVersion: navigator.appVersion,
            cpuCores: navigator.hardwareConcurrency,
            totalMemory: performance.memory?.jsHeapSizeLimit || 0,
            availableMemory: performance.memory?.jsHeapSizeLimit - performance.memory?.usedJSHeapSize || 0,
            uptime: performance.now()
        };
    }
}

Web 端的实现示例展示了:

  • 如何通过浏览器提供的 devicemotion 事件模拟加速度计数据;
  • 使用 navigatorperformance 等对象获取设备与运行环境信息(如内核数、内存上限等);
  • 在没有真实硬件传感器的环境中,尽可能提供与 KMP 抽象兼容的数据结构,便于复用业务逻辑。

这对于构建 Web 调试面板、数据可视化后台等场景尤其有用,可以用同一套 KMP 代码来消费这些“伪传感器数据”。

第四部分:鸿蒙系统实际应用

鸿蒙传感器实现

// ohos/kotlin/com/example/sensor/SensorManager.kt
import ohos.sensor.agent.SensorManager as OhosSensorManager
import ohos.sensor.data.SensorEvent
import ohos.sensor.listener.SensorEventCallback
import ohos.app.Context

actual class SensorManager(private val context: Context) {
    private val sensorManager = OhosSensorManager.getInstance()
    private val listeners = mutableListOf<SensorListener>()
    
    actual suspend fun startListening(sensorType: SensorType): Boolean {
        return try {
            val ohosType = when (sensorType) {
                SensorType.ACCELEROMETER -> OhosSensorManager.SENSOR_TYPE_ACCELEROMETER
                SensorType.GYROSCOPE -> OhosSensorManager.SENSOR_TYPE_GYROSCOPE
                SensorType.MAGNETOMETER -> OhosSensorManager.SENSOR_TYPE_MAGNETIC_FIELD
                else -> return false
            }
            
            sensorManager.createSensorEventListener(object : SensorEventCallback {
                override fun onSensorEvent(sensorEvent: SensorEvent) {
                    val sensorData = SensorData(
                        sensorType = sensorType,
                        values = sensorEvent.values,
                        accuracy = sensorEvent.accuracy,
                        timestamp = sensorEvent.timestamp
                    )
                    listeners.forEach { it.onSensorDataChanged(sensorData) }
                }
                
                override fun onAccuracyChanged(sensorId: Int, accuracy: Int) {
                    listeners.forEach { it.onAccuracyChanged(sensorType, accuracy) }
                }
            }, ohosType)
            
            true
        } catch (e: Exception) {
            false
        }
    }
}

// 鸿蒙设备信息
actual class DeviceInfoManager {
    actual suspend fun getDeviceInfo(): DeviceInfo {
        return DeviceInfo(
            manufacturer = ohos.system.SystemProperties.get("ro.product.manufacturer", ""),
            model = ohos.system.SystemProperties.get("ro.product.model", ""),
            brand = ohos.system.SystemProperties.get("ro.product.brand", ""),
            device = ohos.system.SystemProperties.get("ro.product.device", ""),
            product = ohos.system.SystemProperties.get("ro.product.name", ""),
            hardware = ohos.system.SystemProperties.get("ro.hardware", ""),
            fingerprint = ohos.system.SystemProperties.get("ro.build.fingerprint", ""),
            buildId = ohos.system.SystemProperties.get("ro.build.id", ""),
            buildVersion = ohos.system.SystemProperties.get("ro.build.version.release", ""),
            sdkVersion = ohos.system.SystemProperties.getInt("ro.build.version.sdk", 0),
            releaseVersion = ohos.system.SystemProperties.get("ro.build.version.release", "")
        )
    }
}

第五部分:高级功能

传感器数据融合

// commonMain/kotlin/com/example/sensor/SensorFusion.kt
class SensorFusion(private val sensorManager: SensorManager) {
    private val accelerometerData = FloatArray(3)
    private val gyroscopeData = FloatArray(3)
    private val magnetometerData = FloatArray(3)
    
    suspend fun getOrientation(): FloatArray {
        // Implement sensor fusion algorithm (e.g., Madgwick filter)
        val orientation = FloatArray(3)
        // Calculate orientation from sensor data
        return orientation
    }
    
    suspend fun getDeviceMotion(): DeviceMotion {
        return DeviceMotion(
            acceleration = accelerometerData.copyOf(),
            rotation = gyroscopeData.copyOf(),
            magneticField = magnetometerData.copyOf()
        )
    }
}

data class DeviceMotion(
    val acceleration: FloatArray,
    val rotation: FloatArray,
    val magneticField: FloatArray
)

// 设备状态监测
class DeviceStateMonitor(private val deviceInfoManager: DeviceInfoManager) {
    private val listeners = mutableListOf<DeviceStateListener>()
    
    suspend fun startMonitoring() {
        // Periodically check device state
    }
    
    suspend fun stopMonitoring() {
        // Stop monitoring
    }
}

interface DeviceStateListener {
    fun onBatteryLow()
    fun onStorageLow()
    fun onMemoryLow()
    fun onTemperatureHigh()
}

在这里插入图片描述

总结

传感器与设备信息是现代应用获取设备状态和用户交互的重要方式。通过 KMP 框架,我们可以在共享代码中定义统一的传感器和设备信息接口,同时在各平台上利用原生 API 实现高效的数据采集。鸿蒙系统提供了完整的传感器 API,使得跨平台实现相对平顺。关键是要合理处理传感器精度、功耗和数据融合。

在工程实践中,你可以结合本篇思考:

  • 当前业务实际需要哪些传感器,采样频率和精度要求如何?
  • 是否已经有统一的“设备画像/运行环境”模块,方便埋点与调优?
  • 在多端场景下,如何通过适配层最大程度复用与传感器相关的业务逻辑?

建议的实战路径:

  • 先做一个简单 demo:展示实时加速度曲线 + 设备基础信息;
  • 再引入传感器融合和设备状态监控,对比不同算法和策略对体验与功耗的影响;
  • 最终将通用能力沉淀为工具模块,在多个业务中复用。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐