如要使用感應器管理工具在行動應用程式中填入步數資料,請參閱本指南。如要進一步瞭解如何設計及管理運動應用程式 UI,請參閱「建構基本健身應用程式」。
開始使用
如要開始從行動裝置測量基本步數計數器的步數,您需要在應用程式模組 build.gradle
檔案中新增依附元件。確認您使用的是最新版本的依附元件。
此外,如果想將應用程式支援範圍擴展至其他板型規格 (例如 Wear OS),請新增這些板型規格所需的依附元件。
以下列舉幾個 UI 依附元件的範例。如需完整清單,請參閱這份 UI 元素指南。
implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.activity:activity-compose")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material")
取得步數感應器
使用者授予必要的活動辨識權限後,您就能存取步數計數器感應器:
- 從
getSystemService()
取得SensorManager
物件。 - 從
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
介面。請注意,您應將感應器註冊作業與前景服務的生命週期建立關聯,並在服務暫停或結束時取消註冊感應器。下列程式碼片段說明如何為 Sensor.TYPE_STEP_COUNTER
實作 SensorEventListener
介面:
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 持續性資料庫。
以下程式碼片段會建立資料表,其中包含一組步數測量值,以及應用程式存取每項測量值的時間:
@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 分鐘儲存目前的步數,請執行下列操作:
- 擴充
Application
類別,實作Configuration.Provider
介面。 - 在
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" />