Sử dụng Trình quản lý cảm biến để điền dữ liệu về số bước vào ứng dụng di động như mô tả trong hướng dẫn này. Để biết thêm thông tin về cách thiết kế và quản lý giao diện người dùng của ứng dụng tập thể dục, hãy tham khảo bài viết Tạo ứng dụng thể dục cơ bản.
Bắt đầu
Để bắt đầu đo các bước của bộ đếm bước cơ bản trên thiết bị di động, bạn cần thêm các phần phụ thuộc vào tệp build.gradle
của mô-đun ứng dụng. Đảm bảo rằng bạn sử dụng phiên bản mới nhất của phần phụ thuộc.
Ngoài ra, khi mở rộng khả năng hỗ trợ ứng dụng sang các hệ số hình dạng khác, chẳng hạn như Wear OS, hãy thêm các phần phụ thuộc mà các hệ số hình dạng này yêu cầu.
Dưới đây là một vài ví dụ về một số phần phụ thuộc giao diện người dùng. Để xem danh sách đầy đủ, hãy tham khảo hướng dẫn về Các thành phần trên giao diện người dùng.
implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.activity:activity-compose")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material")
Lấy cảm biến bộ đếm bước
Sau khi người dùng cấp quyền nhận dạng hoạt động cần thiết, bạn có thể truy cập vào cảm biến bộ đếm bước:
- Lấy đối tượng
SensorManager
từgetSystemService()
. - Có được cảm biến bộ đếm bước từ
SensorManager
:
private val sensorManager by lazy {
getSystemService(Context.SENSOR_SERVICE) as SensorManager }
private val sensor: Sensor? by lazy {
sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) }
Một số thiết bị không có cảm biến bộ đếm bước. Bạn nên kiểm tra cảm biến và hiện thông báo lỗi nếu thiết bị không có cảm biến:
if (sensor == null) {
Text(text = "Step counter sensor is not present on this device")
}
Tạo dịch vụ trên nền trước
Trong một ứng dụng thể dục cơ bản, bạn có thể có một nút để nhận các sự kiện bắt đầu và kết thúc từ người dùng cho số bước theo dõi.
Hãy lưu ý đến các phương pháp hay nhất về cảm biến. Cụ thể, cảm biến bộ đếm bước chỉ nên đếm số bước trong khi trình nghe cảm biến được đăng ký. Bằng cách liên kết hoạt động đăng ký cảm biến với dịch vụ trên nền trước, cảm biến sẽ được đăng ký ngay khi cần thiết và cảm biến có thể vẫn được đăng ký khi ứng dụng không chạy ở nền trước.
Hãy sử dụng đoạn mã sau để huỷ đăng ký cảm biến trong phương thức onPause()
của dịch vụ trên nền trước:
override fun onPause() {
super.onPause()
sensorManager.unregisterListener(this)
}
Phân tích dữ liệu cho sự kiện
Để truy cập vào dữ liệu cảm biến, hãy triển khai giao diện SensorEventListener
. Xin lưu ý rằng bạn nên liên kết việc đăng ký cảm biến với vòng đời của dịch vụ trên nền trước, huỷ đăng ký cảm biến khi dịch vụ bị tạm dừng hoặc kết thúc. Đoạn mã sau đây cho biết cách triển khai giao diện SensorEventListener
cho 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")
}
}
Tạo cơ sở dữ liệu cho các sự kiện cảm biến
Ứng dụng của bạn có thể hiển thị một màn hình để người dùng xem số bước của họ theo thời gian. Để cung cấp chức năng này trong ứng dụng của bạn, hãy dùng Thư viện lưu trữ Room.
Đoạn mã sau đây sẽ tạo một bảng chứa một tập hợp các thông tin đo lường số bước, cùng với thời điểm ứng dụng của bạn truy cập vào từng thông tin đo lường:
@Entity(tableName = "steps")
data class StepCount(
@ColumnInfo(name = "steps") val steps: Long,
@ColumnInfo(name = "created_at") val createdAt: String,
)
Tạo một đối tượng truy cập dữ liệu (DAO) để đọc và ghi dữ liệu:
@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)
}
Để tạo thực thể DAO, hãy tạo một đối tượng RoomDatabase
:
@Database(entities = [StepCount::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun stepsDao(): StepsDao
}
Lưu trữ dữ liệu cảm biến vào cơ sở dữ liệu
ViewModel sử dụng lớp StepCounter mới để bạn có thể lưu trữ các bước ngay khi bạn đọc:
viewModelScope.launch {
val stepsFromLastBoot = stepCounter.steps()
repository.storeSteps(stepsFromLastBoot)
}
Lớp repository
sẽ có dạng như sau:
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
}
}
}
}
Định kỳ truy xuất dữ liệu cảm biến
Nếu sử dụng dịch vụ trên nền trước, bạn không cần định cấu hình WorkManager
vì trong thời gian ứng dụng chủ động theo dõi số bước của người dùng, tổng số bước đã cập nhật sẽ xuất hiện trong ứng dụng.
Tuy nhiên, nếu muốn phân lô các bản ghi số bước, bạn có thể sử dụng WorkManager
để đo lường các bước trong một khoảng thời gian cụ thể, chẳng hạn như 15 phút một lần.
WorkManager
là thành phần thực hiện công việc trong nền để thực thi được đảm bảo. Tìm hiểu thêm trong lớp học lập trình WorkManager.
Để định cấu hình đối tượng Worker
nhằm truy xuất dữ liệu, hãy ghi đè phương thức doWork()
, như trong đoạn mã sau:
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()
}
}
Để thiết lập WorkManager
nhằm lưu trữ số bước hiện tại 15 phút một lần, hãy làm như sau:
- Mở rộng lớp
Application
để triển khai giao diệnConfiguration.Provider
. - Trong phương thức
onCreate()
, hãy thêm mộtPeriodicWorkRequestBuilder
vào hàng đợi.
Quy trình này xuất hiện trong đoạn mã sau:
@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()
}
Để khởi chạy trình cung cấp nội dung kiểm soát quyền truy cập vào cơ sở dữ liệu bộ đếm bước của ứng dụng ngay khi khởi động ứng dụng, hãy thêm phần tử sau vào tệp kê khai của ứng dụng:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />