Mit dem Sensor Manager Schritte von einem Mobilgerät aus messen

Verwenden Sie Sensor Manager, um Schrittdaten in einer mobilen App auszufüllen, wie in dieser Anleitung beschrieben. Weitere Informationen zum Entwerfen und Verwalten einer Benutzeroberfläche für Trainings-Apps findest du unter Einfache Fitness-App erstellen.

Erste Schritte

Damit Sie die Schritte Ihres einfachen Schrittzählers auf Ihrem Mobilgerät messen können, müssen Sie die Abhängigkeiten der Datei build.gradle Ihres App-Moduls hinzufügen. Achten Sie darauf, dass Sie die neuesten Versionen der Abhängigkeiten verwenden. Wenn du die Unterstützung deiner App auf andere Formfaktoren wie Wear OS ausdehnst, füge außerdem die Abhängigkeiten hinzu, die für diese Formfaktoren erforderlich sind.

Im Folgenden finden Sie einige Beispiele für einige UI-Abhängigkeiten. Eine vollständige Liste finden Sie im Leitfaden zu UI-Elementen.

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

Schrittzählersensor abrufen

Nachdem der Nutzer die erforderliche Berechtigung zur Aktivitätserkennung erteilt hat, kannst du auf den Schrittzähler zugreifen:

  1. Rufen Sie das SensorManager-Objekt von getSystemService() ab.
  2. Beziehen Sie den Schrittzählersensor aus dem SensorManager:
private val sensorManager by lazy {
        getSystemService(Context.SENSOR_SERVICE) as SensorManager }
private val sensor: Sensor? by lazy {
        sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) }

Einige Geräte haben keinen Schrittzählersensor. Sie sollten nach dem Sensor suchen und eine Fehlermeldung anzeigen lassen, wenn das Gerät keinen hat:

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

Dienst im Vordergrund erstellen

In einer einfachen Fitness-App haben Sie möglicherweise eine Schaltfläche, um Start- und Stoppereignisse vom Nutzer für das Tracking von Schritten zu empfangen.

Beachte die Best Practices für Sensoren. Insbesondere sollte der Schrittzählersensor nur Schritte zählen, wenn der Sensor-Listener registriert ist. Durch die Verknüpfung der Sensorregistrierung mit einem Dienst im Vordergrund wird der Sensor registriert, solange er benötigt wird. Er kann auch dann registriert bleiben, wenn sich die App nicht im Vordergrund befindet.

Mit dem folgenden Snippet können Sie die Registrierung des Sensors in der Methode onPause() Ihres Dienstes im Vordergrund aufheben:

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

Ereignisdaten analysieren

Implementieren Sie die SensorEventListener-Schnittstelle, um auf die Sensordaten zuzugreifen. Sie sollten die Sensorregistrierung dem Lebenszyklus Ihres Dienstes im Vordergrund zuordnen und die Registrierung des Sensors aufheben, wenn der Dienst pausiert oder beendet wird. Das folgende Snippet zeigt, wie die SensorEventListener-Schnittstelle für Sensor.TYPE_STEP_COUNTER implementiert wird:

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

Datenbank für die Sensorereignisse erstellen

Möglicherweise zeigt Ihre App einen Bildschirm an, auf dem der Nutzer seine Schritte im Zeitverlauf sehen kann. Sie können diese Funktion in Ihrer App mit der Chatroom-Persistenzbibliothek bereitstellen.

Mit dem folgenden Snippet wird eine Tabelle erstellt, die eine Reihe von Messungen der Schrittzahl enthält. Außerdem wird die Zeit angegeben, zu der die App auf die einzelnen Messungen zugegriffen hat:

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

Erstellen Sie ein Datenzugriffsobjekt (Data Access Object, DAO) zum Lesen und Schreiben der Daten:

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

Erstellen Sie ein RoomDatabase-Objekt, um den DAO zu instanziieren:

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

Sensordaten in der Datenbank speichern

ViewModel verwendet die neue StepCounter-Klasse, sodass Sie die Schritte speichern können, sobald Sie sie lesen:

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

Die Klasse repository würde so aussehen:

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


Regelmäßig Sensordaten abrufen

Wenn Sie einen Dienst im Vordergrund verwenden, müssen Sie WorkManager nicht konfigurieren. Während die App die Nutzerschritte aktiv erfasst, sollte die aktualisierte Gesamtschrittzahl in der App angezeigt werden.

Wenn Sie Ihre Schrittaufzeichnungen im Batch zusammenfassen möchten, können Sie WorkManager verwenden, um Schritte in einem bestimmten Intervall zu messen, z. B. einmal alle 15 Minuten. WorkManager ist die Komponente, die die Hintergrundarbeit für eine garantierte Ausführung durchführt. Weitere Informationen dazu finden Sie im WorkManager-Codelab.

Wenn das Worker-Objekt zum Abrufen der Daten konfiguriert werden soll, überschreiben Sie die Methode doWork(), wie im folgenden Code-Snippet gezeigt:

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

So richten Sie WorkManager so ein, dass die aktuelle Schrittzahl alle 15 Minuten gespeichert wird:

  1. Erweitern Sie die Klasse Application, um die Configuration.Provider-Schnittstelle zu implementieren.
  2. Fügen Sie in der Methode onCreate() ein PeriodicWorkRequestBuilder in die Warteschlange.

Dieser Vorgang wird im folgenden Code-Snippet dargestellt:

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

Um den Contentanbieter zu initialisieren, der den Zugriff auf die Schrittzähler-Datenbank Ihrer App direkt beim Start der App steuert, fügen Sie der Manifestdatei Ihrer App das folgende Element hinzu:

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