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

Verwenden Sie den Sensor Manager, um Schrittdaten in einer mobilen App zu erfassen, wie hier beschrieben . Weitere Informationen zum Entwerfen und Verwalten der Benutzeroberfläche einer Trainings-App siehe Erstellen Sie eine einfache Fitness-App.

Erste Schritte

Um mit dem Messen der Schritte deines Basis-Schrittzählers zu beginnen, Mobilgerät verwenden, müssen Sie die Abhängigkeiten zu Ihrem App-Modul hinzufügen. build.gradle-Datei. 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 ausweitest, und fügen Sie die Abhängigkeiten hinzu, die diese Formfaktoren erfordern.

Im Folgenden finden Sie einige Beispiele für einige UI-Abhängigkeiten. Eine vollständige Liste Weitere Informationen finden Sie in diesem 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ähler-Sensor abrufen

Nachdem der Nutzer die erforderliche Berechtigung zur Aktivitätserkennung gewährt hat, können Sie auf den Sensor des Schrittzählers zugreifen:

  1. Rufen Sie das SensorManager-Objekt aus getSystemService() ab.
  2. Erwerben Sie den Schrittzähler-Sensor von 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ähler-Sensor. Du solltest nach dem Sensor suchen und eine Fehlermeldung anzeigen, wenn auf dem Gerät keine vorhanden ist:

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

Dienst im Vordergrund erstellen

In einer einfachen Fitness-App gibt es vielleicht eine Schaltfläche um Start- und Stopp-Ereignisse vom Nutzer zum Verfolgen von Schritten zu empfangen.

Beachte die Best Practices für Sensoren. Insbesondere sollte der Schrittzähler-Sensor nur Schritte zählen, während der Sensor Listener registriert ist. Durch Verknüpfung der Sensorregistrierung mit einem Vordergrund wird der Sensor registriert, solange er benötigt wird, bleiben registriert, wenn die App nicht im Vordergrund ausgeführt wird.

Verwenden Sie das folgende Snippet, um die Registrierung des Sensors in der Methode onPause() von Ihren Dienst im Vordergrund:

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

Daten für Ereignisse analysieren

Implementieren Sie die SensorEventListener-Schnittstelle, um auf die Sensordaten zuzugreifen. Hinweis müssen Sie die Sensorregistrierung Dadurch wird die Registrierung des Sensors aufgehoben, wenn der Dienst pausiert oder beendet wird. Die folgendes Snippet zeigt, wie die SensorEventListener-Schnittstelle implementiert wird für 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")
    }
}

Datenbank für Sensorereignisse erstellen

In Ihrer App wird möglicherweise ein Bildschirm angezeigt, auf dem der Nutzer seine Schritte im Zeitverlauf sehen kann. Verwenden Sie die Raumpersistenzbibliothek, um diese Funktion in Ihrer App bereitzustellen.

Mit dem folgenden Snippet wird eine Tabelle erstellt, die eine Anzahl von Schritten enthält Messungen sowie die Zeit, 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,
)

Datenzugriffsobjekt (Data Access Object, DAO) erstellen 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 zum Instanziieren des DAO ein RoomDatabase-Objekt:

@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 so schnell wie möglich speichern können. während 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äßiges Abrufen von Sensordaten

Wenn Sie einen Dienst im Vordergrund verwenden, müssen Sie WorkManager nicht konfigurieren Denn während der Zeit, in der Ihre App aktiv die Schritte des Nutzers erfasst, sollte die aktualisierte Gesamtschrittzahl in deiner App angezeigt werden.

Wenn Sie Ihre Schrittdatensätze im Batch zusammenfassen möchten, können Sie WorkManager verwenden, um die Schritte in einem bestimmten Intervall messen, zum Beispiel alle 15 Minuten. WorkManager ist die Komponente, die den Hintergrund ausführt. dass die Umsetzung garantiert ist. Weitere Informationen findest du im WorkManager-Codelab.

Wenn Sie das Worker-Objekt zum Abrufen der Daten konfigurieren möchten, überschreiben Sie den doWork() an, 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()
    }
}

Um WorkManager so einzurichten, dass die aktuelle Schrittzahl alle 15 Minuten gespeichert wird, musst du Folgendes tun: Folgendes:

  1. Application-Klasse erweitern, um Configuration.Provider zu implementieren .
  2. Stelle in der Methode onCreate() eine PeriodicWorkRequestBuilder in die Warteschlange ein.

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

Initialisieren Sie den Contentanbieter, der den Zugriff auf den Schritt Ihrer App steuert Zähler-Datenbank sofort beim Start der Anwendung das folgende Element in die Manifestdatei Ihrer App ein:

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