Используйте Sensor Manager для измерения шагов с мобильного устройства

Используйте Sensor Manager для заполнения данных о шагах в мобильном приложении, как описано в этом руководстве. Дополнительные сведения о разработке пользовательского интерфейса приложения для тренировок и управлении им см. в разделе Создание базового фитнес-приложения .

Начиная

Чтобы начать измерение шагов вашего основного счетчика шагов на мобильном устройстве, вам нужно будет добавить зависимости в файл build.gradle вашего модуля приложения. Убедитесь, что вы используете последние версии зависимостей. Кроме того, когда вы расширяете поддержку вашего приложения для других форм-факторов, таких как Wear OS, добавьте зависимости, которые требуются для этих форм-факторов.

Ниже приведены несколько примеров некоторых зависимостей пользовательского интерфейса. Полный список можно найти в этом руководстве по элементам пользовательского интерфейса .

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

Получите датчик счетчика шагов.

После того, как пользователь предоставил необходимое разрешение на распознавание активности , вы можете получить доступ к датчику счетчика шагов:

  1. Получите объект SensorManager из getSystemService() .
  2. Получите датчик счетчика шагов из SensorManager :
private val sensorManager by lazy {
        getSystemService
(Context.SENSOR_SERVICE) as SensorManager }
private val sensor: Sensor? by lazy {
        sensorManager
.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) }

Некоторые устройства не имеют датчика счетчика шагов. Вам следует проверить наличие датчика и отобразить сообщение об ошибке, если на устройстве его нет:

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

Создайте свой сервис переднего плана

В базовом фитнес-приложении у вас может быть кнопка для получения от пользователя событий начала и остановки для отслеживания шагов.

Помните о лучших практиках работы с датчиками. В частности, датчик счетчика шагов должен подсчитывать шаги только тогда, когда зарегистрирован прослушиватель датчика. Связав регистрацию датчика со службой переднего плана, датчик регистрируется до тех пор, пока это необходимо, и датчик может оставаться зарегистрированным, когда приложение не находится на переднем плане.

Используйте следующий фрагмент, чтобы отменить регистрацию датчика в методе onPause() вашей службы переднего плана:

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

Анализируйте данные для событий

Чтобы получить доступ к данным датчика, реализуйте интерфейс SensorEventListener . Обратите внимание, что вам следует связать регистрацию датчика с жизненным циклом вашей приоритетной службы, отменяя регистрацию датчика, когда служба приостанавливается или завершается. В следующем фрагменте показано, как реализовать интерфейс SensorEventListener для 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")
   
}
}

Создайте базу данных для событий датчиков

Ваше приложение может отображать экран, на котором пользователь может просматривать свои шаги с течением времени. Чтобы обеспечить такую ​​возможность в своем приложении, используйте библиотеку Room persistence .

В следующем фрагменте кода создается таблица, содержащая набор измерений количества шагов, а также время, когда ваше приложение получало доступ к каждому измерению:

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

Создайте объект доступа к данным (DAO) для чтения и записи данных:

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

Чтобы создать экземпляр DAO, создайте объект RoomDatabase :

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

Сохранение данных датчика в базе данных

ViewModel использует новый класс StepCounter, поэтому вы можете сохранять шаги сразу после их прочтения:

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

Класс repository будет выглядеть так:

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


Периодическое получение данных датчиков

Если вы используете службу переднего плана, вам не нужно настраивать WorkManager поскольку в то время, когда ваше приложение активно отслеживает шаги пользователя, в вашем приложении должно появиться обновленное общее количество шагов.

Однако если вы хотите группировать записи шагов, вы можете использовать WorkManager для измерения шагов через определенный интервал, например, каждые 15 минут. WorkManager — это компонент, выполняющий фоновую работу для гарантированного выполнения. Узнайте больше в лаборатории кода WorkManager .

Чтобы настроить объект Worker для получения данных, переопределите метод doWork() , как показано в следующем фрагменте кода:

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

Чтобы настроить WorkManager на сохранение текущего количества шагов каждые 15 минут, выполните следующие действия:

  1. Расширьте класс Application , чтобы реализовать интерфейс Configuration.Provider .
  2. В методе onCreate() поставьте в очередь PeriodicWorkRequestBuilder .

Этот процесс показан в следующем фрагменте кода:

@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()
}

Чтобы инициализировать поставщика контента, который контролирует доступ к базе данных счетчика шагов вашего приложения, сразу после запуска приложения, добавьте следующий элемент в файл манифеста вашего приложения:

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