Używaj Menedżera czujników do mierzenia kroków na urządzeniu mobilnym

Użyj Menedżera czujników do wypełniania danych kroków w aplikacji mobilnej, jak opisano w tym Google. Aby dowiedzieć się więcej o zaprojektowaniu UI aplikacji do ćwiczeń i zarządzaniu nim, patrz Utwórz podstawową aplikację do fitnessu

Pierwsze kroki

Aby rozpocząć mierzenie kroków z podstawowego licznika kroków, Mobile, musisz dodać zależności do modułu aplikacji build.gradle. Używaj najnowszych wersji zależności. Ponadto gdy aplikacja zacznie obsługiwać inne formaty, np. Wear OS, dodaj zależności wymagane przez te formaty.

Poniżej znajdziesz kilka przykładów niektórych zależności interfejsu. Pełną listę znajdziesz tutaj: zapoznaj się z przewodnikiem Elementy interfejsu.

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

Uzyskiwanie czujnika licznika kroków

Gdy użytkownik przyzna niezbędne uprawnienia do rozpoznawania aktywności, możesz uzyskać dostęp do czujnika licznika kroków:

  1. Uzyskaj obiekt SensorManager z getSystemService().
  2. Pobierz czujnik licznika kroków z SensorManager:
private val sensorManager by lazy {
        getSystemService(Context.SENSOR_SERVICE) as SensorManager }
private val sensor: Sensor? by lazy {
        sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) }

Niektóre urządzenia nie mają czujnika licznika kroków. Należy sprawdzić czujnik i wyświetla komunikat o błędzie, jeśli urządzenie nie ma takiego:

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

Utwórz usługę na pierwszym planie

W podstawowej aplikacji do fitnessu może być dostępny przycisk. do otrzymywania od użytkownika zdarzeń rozpoczęcia i zatrzymania w celu śledzenia kroków.

Pamiętaj o sprawdzonych metodach dotyczących czujników. Czujnik licznika kroków powinien liczyć tylko kroki, gdy czujnik jest zarejestrowany detektor. Łącząc rejestrację czujnika z pierwszym planem usługi, czujnik jest zarejestrowany tak długo, jak to konieczne. Może też pozostają zarejestrowane, gdy aplikacja nie działa na pierwszym planie.

Użyj tego fragmentu kodu, aby wyrejestrować czujnik w metodzie onPause() funkcji Twoja usługa na pierwszym planie:

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

Analizowanie danych pod kątem zdarzeń

Aby uzyskać dostęp do danych z czujnika, zaimplementuj interfejs SensorEventListener. Notatka musisz powiązać rejestrację czujnika z parametrem usługi na pierwszym planie cyklu życia usługi przez wyrejestrowanie czujnika, gdy usługa jest wstrzymana lub zakończona. ten fragment kodu pokazuje, jak wdrożyć interfejs SensorEventListener dla 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")
    }
}

Utwórz bazę danych dla zdarzeń z czujnika

W aplikacji może się pojawić ekran, na którym użytkownik będzie mógł śledzić liczbę kroków z upływem czasu. Aby udostępnić tę funkcję w swojej aplikacji, użyj biblioteki trwałości sal.

Ten fragment kodu tworzy tabelę, która zawiera liczbę kroków pomiarów oraz czasu uzyskania przez aplikację dostępu do poszczególnych pomiarów:

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

utworzyć obiekt dostępu do danych (DAO), aby odczytać i zapisać dane:

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

Aby utworzyć instancję DAO, utwórz obiekt RoomDatabase:

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

Zapisywanie danych z czujnika w bazie danych

Model ViewModel używa nowej klasy StepCounter, więc możesz zapisać kroki jak najszybciej w miarę ich czytania:

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

Klasa repository będzie wyglądać tak:

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


Okresowe pobieranie danych z czujnika

Jeśli korzystasz z usługi na pierwszym planie, nie musisz konfigurować WorkManager ponieważ w czasie, gdy aplikacja aktywnie śledzi kroki użytkownika, zaktualizowana łączna liczba kroków powinna pojawić się w aplikacji.

Jeśli chcesz grupować rekordy kroków, możesz jednak użyć polecenia WorkManager, aby mierz kroki w określonych odstępach czasu, np. co 15 minut. WorkManager to komponent, który działa w tle gwarantowane wykonanie. Więcej informacji znajdziesz w ćwiczeniach z programowania w WorkManageru.

Aby skonfigurować pobieranie danych przez obiekt Worker, zastąp doWork() zgodnie z poniższym fragmentem kodu:

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

Aby skonfigurować funkcję WorkManager do przechowywania bieżącej liczby kroków co 15 minut, wykonaj następujące:

  1. Rozszerz klasę Application, aby zaimplementować Configuration.Provider za pomocą prostego interfejsu online.
  2. W metodzie onCreate() umieść PeriodicWorkRequestBuilder w kolejce.

Proces ten wygląda w tym fragmencie kodu:

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

Aby zainicjować dostawcę treści, który kontroluje dostęp do kroku aplikacji do bazy danych licznika natychmiast po uruchomieniu aplikacji dodaj poniższy element do plik manifestu aplikacji:

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