Sử dụng Trình quản lý cảm biến để đo số bước trên thiết bị di động

Sử dụng Trình quản lý cảm biến để điền dữ liệu về số bước vào một ứng dụng di động theo mô tả trong của chúng tôi. Để biết thêm thông tin về cách thiết kế và quản lý giao diện người dùng của ứng dụng tập thể dục, tham chiếu đến Tạo ứng dụng thể dục cơ bản.

Bắt đầu

Để bắt đầu đo lường các bước của bộ đếm bước cơ bản từ thiết bị di động, bạn sẽ cần thêm các phần phụ thuộc vào mô-đun ứng dụng của mình Tệp build.gradle. Hãy đảm bảo rằng bạn sử dụng phiên bản phần phụ thuộc mới nhất. Ngoài ra, khi bạn mở rộng khả năng hỗ trợ của ứng dụng sang các kiểu dáng khác, chẳng hạn như Wear OS, thêm các phần phụ thuộc mà các hệ số hình dạng này yêu cầu.

Dưới đây là một vài ví dụ về một số phần phụ thuộc giao diện người dùng. Để xem danh sách đầy đủ, hãy tham khảo hướng dẫn này về Thành phần trên giao diện người dùng.

implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.activity:activity-compose")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material")

Lấy cảm biến bộ đếm bước

Sau khi người dùng cấp quyền nhận dạng hoạt động cần thiết, bạn có thể truy cập cảm biến bộ đếm bước:

  1. Lấy đối tượng SensorManager từ getSystemService().
  2. Thu nạp cảm biến bộ đếm bước từ SensorManager:
private val sensorManager by lazy {
        getSystemService(Context.SENSOR_SERVICE) as SensorManager }
private val sensor: Sensor? by lazy {
        sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) }

Một số thiết bị không có cảm biến bộ đếm bước. Bạn nên kiểm tra cảm biến và hiển thị thông báo lỗi nếu thiết bị không có:

if (sensor == null) {
    Text(text = "Step counter sensor is not present on this device")
}

Tạo dịch vụ trên nền trước

Trong ứng dụng thể dục cơ bản, bạn có thể thấy một nút để nhận các sự kiện bắt đầu và dừng từ người dùng để theo dõi các bước.

Bạn cũng cần lưu ý đến các phương pháp hay nhất về cảm biến. Cụ thể, cảm biến bộ đếm bước chỉ sẽ đếm số bước khi cảm biến trình nghe đã được đăng ký. Bằng cách liên kết quy trình đăng ký cảm biến với nền trước dịch vụ, cảm biến sẽ được đăng ký trong khi cần thiết và cảm biến có thể vẫn được đăng ký khi ứng dụng không chạy trên nền trước.

Hãy sử dụng đoạn mã sau để huỷ đăng ký cảm biến trong phương thức onPause() của dịch vụ trên nền trước của bạn:

override fun onPause() {
    super.onPause()
    sensorManager.unregisterListener(this)
}

Phân tích dữ liệu cho sự kiện

Để truy cập vào dữ liệu cảm biến, hãy triển khai giao diện SensorEventListener. Ghi chú rằng bạn nên liên kết việc đăng ký cảm biến với vòng đời, huỷ đăng ký cảm biến khi dịch vụ bị tạm dừng hoặc kết thúc. Chiến lược phát hành đĩa đơn đoạn mã sau đây cho biết cách triển khai giao diện SensorEventListener cho Sensor.TYPE_STEP_COUNTER:

private const val TAG = "STEP_COUNT_LISTENER"

context(Context)
class StepCounter {
    private val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
    private val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)

    suspend fun steps() = suspendCancellableCoroutine { continuation ->
        Log.d(TAG, "Registering sensor listener... ")

        val listener: SensorEventListener by lazy {
            object : SensorEventListener {
                override fun onSensorChanged(event: SensorEvent?) {
                    if (event == null) return

                    val stepsSinceLastReboot = event.values[0].toLong()
                    Log.d(TAG, "Steps since last reboot: $stepsSinceLastReboot")

                    if (continuation.isActive) {
                        continuation.resume(stepsSinceLastReboot)
                    }
                }

                override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
                      Log.d(TAG, "Accuracy changed to: $accuracy")
                }
            }
       }

        val supportedAndEnabled = sensorManager.registerListener(listener,
                sensor, SensorManager.SENSOR_DELAY_UI)
        Log.d(TAG, "Sensor listener registered: $supportedAndEnabled")
    }
}

Tạo cơ sở dữ liệu cho các sự kiện cảm biến

Ứng dụng của bạn có thể hiển thị một màn hình để người dùng xem số bước của họ theo thời gian. Để cung cấp chức năng này trong ứng dụng của bạn, hãy sử dụng Thư viện lưu trữ Room.

Đoạn mã sau đây sẽ tạo một bảng chứa tập hợp số bước các chỉ số đo lường, cùng với thời gian ứng dụng của bạn truy cập vào từng phép đo:

@Entity(tableName = "steps")
data class StepCount(
  @ColumnInfo(name = "steps") val steps: Long,
  @ColumnInfo(name = "created_at") val createdAt: String,
)

Tạo đối tượng truy cập dữ liệu (DAO) để đọc và ghi dữ liệu:

@Dao
interface StepsDao {
    @Query("SELECT * FROM steps")
    suspend fun getAll(): List<StepCount>

    @Query("SELECT * FROM steps WHERE created_at >= date(:startDateTime) " +
            "AND created_at < date(:startDateTime, '+1 day')")
    suspend fun loadAllStepsFromToday(startDateTime: String): Array<StepCount>

    @Insert
    suspend fun insertAll(vararg steps: StepCount)

    @Delete
    suspend fun delete(steps: StepCount)
}

Để tạo thực thể cho DAO, hãy tạo một đối tượng RoomDatabase:

@Database(entities = [StepCount::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun stepsDao(): StepsDao
}

Lưu trữ dữ liệu cảm biến vào cơ sở dữ liệu

ViewModel sử dụng lớp StepCounter mới, vì vậy, bạn có thể lưu trữ các bước ngay khi cần khi bạn đọc chúng:

viewModelScope.launch {
    val stepsFromLastBoot = stepCounter.steps()
    repository.storeSteps(stepsFromLastBoot)
}

Lớp repository sẽ có dạng như sau:

class Repository(
    private val stepsDao: StepsDao,
) {

    suspend fun storeSteps(stepsSinceLastReboot: Long) = withContext(Dispatchers.IO) {
        val stepCount = StepCount(
            steps = stepsSinceLastReboot,
            createdAt = Instant.now().toString()
        )
        Log.d(TAG, "Storing steps: $stepCount")
        stepsDao.insertAll(stepCount)
    }

    suspend fun loadTodaySteps(): Long = withContext(Dispatchers.IO) {
        printTheWholeStepsTable() // DEBUG

        val todayAtMidnight = (LocalDateTime.of(LocalDate.now(), LocalTime.MIDNIGHT).toString())
        val todayDataPoints = stepsDao.loadAllStepsFromToday(startDateTime = todayAtMidnight)
        when {
            todayDataPoints.isEmpty() -> 0
            else -> {
                val firstDataPointOfTheDay = todayDataPoints.first()
                val latestDataPointSoFar = todayDataPoints.last()

                val todaySteps = latestDataPointSoFar.steps - firstDataPointOfTheDay.steps
                Log.d(TAG, "Today Steps: $todaySteps")
                todaySteps
            }
        }
    }
}

Định kỳ truy xuất dữ liệu cảm biến

Nếu sử dụng dịch vụ trên nền trước, bạn không cần định cấu hình WorkManager vì trong khoảng thời gian ứng dụng của bạn chủ động theo dõi số bước của người dùng, tổng số bước đã cập nhật sẽ xuất hiện trong ứng dụng của bạn.

Tuy nhiên, nếu muốn phân nhóm các bản ghi số bước, bạn có thể sử dụng WorkManager để đo các bước tại một khoảng thời gian cụ thể, chẳng hạn như 15 phút một lần. WorkManager là thành phần thực hiện chức năng nền đảm bảo quá trình thực thi. Tìm hiểu thêm trong lớp học lập trình WorkManager.

Để định cấu hình đối tượng Worker nhằm truy xuất dữ liệu, hãy ghi đè doWork() như minh hoạ trong đoạn mã sau đây:

private const val TAG = " StepCounterWorker"

@HiltWorker
class StepCounterWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted workerParams: WorkerParameters,
    val repository: Repository,
    val stepCounter: StepCounter
) : CoroutineWorker(appContext, workerParams) {

    override suspend fun doWork(): Result {
        Log.d(TAG, "Starting worker...")

        val stepsSinceLastReboot = stepCounter.steps().first()
        if (stepsSinceLastReboot == 0L) return Result.success()

        Log.d(TAG, "Received steps from step sensor: $stepsSinceLastReboot")
        repository.storeSteps(stepsSinceLastReboot)

        Log.d(TAG, "Stopping worker...")
        return Result.success()
    }
}

Để thiết lập WorkManager lưu trữ số bước hiện tại 15 phút một lần, hãy như sau:

  1. Mở rộng lớp Application để triển khai Configuration.Provider .
  2. Trong phương thức onCreate(), hãy thêm một PeriodicWorkRequestBuilder vào hàng đợi.

Quy trình này xuất hiện trong đoạn mã sau:

@HiltAndroidApp
@RequiresApi(Build.VERSION_CODES.S)
internal class PulseApplication : Application(), Configuration.Provider {

    @Inject
    lateinit var workerFactory: HiltWorkerFactory

    override fun onCreate() {
        super.onCreate()

        val myWork = PeriodicWorkRequestBuilder<StepCounterWorker>(
                15, TimeUnit.MINUTES).build()

        WorkManager.getInstance(this)
            .enqueueUniquePeriodicWork("MyUniqueWorkName",
                    ExistingPeriodicWorkPolicy.UPDATE, myWork)
    }

    override val workManagerConfiguration: Configuration
        get() = Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .setMinimumLoggingLevel(android.util.Log.DEBUG)
            .build()
}

Để khởi chạy trình cung cấp nội dung kiểm soát quyền truy cập vào bước trong ứng dụng cơ sở dữ liệu bộ đếm ngay khi khởi động ứng dụng, hãy thêm phần tử sau đây vào tệp kê khai của ứng dụng:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    tools:node="remove" />