Mit dem Sensor Manager können Sie Schrittdaten in einer mobilen App erfassen, wie in diesem Leitfaden beschrieben. Weitere Informationen zum Entwerfen und Verwalten der Benutzeroberfläche einer Trainings-App findest du unter Einfache Fitness-App erstellen.
Erste Schritte
Wenn Sie die Schritte Ihres einfachen Schrittzählers auf Ihrem Mobilgerät messen möchten, müssen Sie die Abhängigkeiten der build.gradle
-Datei Ihres App-Moduls hinzufügen. Prüfen Sie, ob Sie die neuesten Versionen der Abhängigkeiten verwenden.
Wenn Sie die Unterstützung Ihrer App auf andere Formfaktoren wie Wear OS ausweiten, fügen Sie die für diese Formfaktoren erforderlichen Abhängigkeiten hinzu.
Im Folgenden finden Sie einige Beispiele für UI-Abhängigkeiten. Eine vollständige Liste 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 erteilt hat, können Sie auf den Schrittzähler zugreifen:
- Rufen Sie das Objekt
SensorManager
ausgetSystemService()
ab. - Rufen Sie den Schrittzähler-Sensor über
SensorManager
ab:
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. Sie sollten nach dem Sensor suchen und eine Fehlermeldung anzeigen, wenn das Gerät keinen hat:
if (sensor == null) {
Text(text = "Step counter sensor is not present on this device")
}
Vordergrunddienst erstellen
In einer einfachen Fitness-App haben Sie möglicherweise eine Schaltfläche, über die Start- und Stopp-Ereignisse vom Nutzer empfangen werden, um Schritte zu erfassen.
Beachten Sie die Best Practices für Sensoren. Insbesondere sollte der Schrittzähler nur Schritte zählen, wenn der Sensor-Listener registriert ist. Durch die Verknüpfung der Sensorregistrierung mit einem Vordergrunddienst wird der Sensor so lange registriert, wie er benötigt wird. Er kann auch dann registriert bleiben, wenn die App nicht im Vordergrund ausgeführt wird.
Verwenden Sie das folgende Snippet, um den Sensor in der Methode onPause()
Ihres Vordergrunddiensts zu deregistrieren:
override fun onPause() {
super.onPause()
sensorManager.unregisterListener(this)
}
Daten für Ereignisse analysieren
Implementieren Sie die SensorEventListener
-Schnittstelle, um auf die Sensordaten zuzugreifen. Die Sensorregistrierung sollte mit dem Lebenszyklus Ihres Vordergrunddienstes verknüpft werden. Heben Sie die Registrierung des Sensors auf, 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
Ihre App zeigt möglicherweise einen Bildschirm an, auf dem der Nutzer seine Schritte im Zeitverlauf sehen kann. Wenn Sie diese Funktion in Ihrer App anbieten möchten, verwenden Sie die Room-Persistenzbibliothek.
Im folgenden Snippet wird eine Tabelle mit einer Reihe von Schrittzählermessungen erstellt, zusammen mit der Uhrzeit, zu der Ihre 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 DAO (Data Access Object), um die Daten zu lesen und zu schreiben:
@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
Das ViewModel verwendet die neue StepCounter-Klasse, sodass du die Schritte sofort nach dem Lesen speichern kannst:
viewModelScope.launch {
val stepsFromLastBoot = stepCounter.steps()
repository.storeSteps(stepsFromLastBoot)
}
Die repository
-Klasse 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, da die aktualisierte Gesamtzahl der Schritte in Ihrer App angezeigt werden sollte, während Ihre App die Schritte des Nutzers aktiv erfasst.
Wenn Sie Ihre Schrittaufzeichnungen in Batches erfassen möchten, können Sie WorkManager
verwenden, um Schritte in einem bestimmten Intervall zu messen, z. B. alle 15 Minuten.
WorkManager
ist die Komponente, die die Hintergrundarbeit für eine zuverlässige Ausführung übernimmt. Weitere Informationen finden Sie im WorkManager-Codelab.
Wenn Sie das Worker
-Objekt zum Abrufen der Daten konfigurieren möchten, ü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 richtest du WorkManager
ein, damit die aktuelle Schrittzahl alle 15 Minuten gespeichert wird:
- Erweitern Sie die Klasse
Application
, um die SchnittstelleConfiguration.Provider
zu implementieren. - Stellen Sie in der Methode
onCreate()
einePeriodicWorkRequestBuilder
in die Warteschlange.
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()
}
Wenn Sie den Content-Provider, der den Zugriff auf die Schrittzählerdatenbank Ihrer App steuert, sofort nach dem Start der App initialisieren möchten, 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" />