Sử dụng Trình quản lý cảm biến để điền sẵn dữ liệu về số bước trong một ứ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 một ứng dụng thể dục cơ bản.
Bắt đầu
Để bắt đầu đo lường số 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. Xác minh rằng bạn sử dụng các phiên bản mới nhất của các phần phụ thuộc.
Ngoài ra, khi bạn mở rộng phạm vi hỗ trợ của ứng dụng sang các kiểu dáng khác, chẳng hạn như Wear OS, hãy thêm các phần phụ thuộc mà những kiểu dáng này yêu cầu.
Sau đây là một vài ví dụ về một số thành phần phụ thuộc của giao diện người dùng. Để xem danh sách đầy đủ, hãy tham khảo hướng dẫn về Phần tử trên giao diện người dùng này.
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()
. - Thu thập 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à dừng từ người dùng nhằm theo dõi số bước.
Hãy lưu ý 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 các bước trong khi trình nghe cảm biến được đăng ký. Bằng cách liên kết quá trình đăng ký cảm biến với một dịch vụ trên nền trước, cảm biến sẽ được đăng ký miễn là cần thiết và cảm biến có thể vẫn được đăng ký khi ứng dụng không ở trên nền trước.
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 các 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 quá trình đă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 nơi người dùng có thể xem số bước của họ theo thời gian. Để cung cấp chức năng này trong ứng dụng, hãy sử dụng thư viện lưu trữ Room.
Đoạn mã sau đây tạo một bảng chứa một nhóm các phép đo số bước, cùng với thời gian mà ứng dụng của bạn truy cập vào từng phép đo:
@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, vì vậy, bạn có thể lưu trữ các bước ngay khi đọc chúng:
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 của bạn đang tích cực theo dõi các bước của người dùng, tổng số bước được cập nhật sẽ xuất hiện trong ứng dụng của bạn.
Tuy nhiên, nếu muốn xử lý hàng loạt các bản ghi bước, bạn có thể sử dụng WorkManager
để đo số bước theo 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 ở chế độ nền để đảm bảo quá trình thực thi diễn ra đáng tin cậy. 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
lưu trữ số bước hiện tại sau mỗi 15 phút, 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 xếp hàng đợi mộtPeriodicWorkRequestBuilder
.
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 động 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" />