Sử dụng Trình quản lý cảm biến để điền dữ liệu về số bước vào một ứng dụng di động theo mô tả trong của chúng tôi. Để 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, tham chiếu đến Tạo ứng dụng thể dục cơ bản.
Bắt đầu
Để bắt đầu đo lường các bước của bộ đếm bước cơ bản từ
thiết bị di động, bạn sẽ cần thêm các phần phụ thuộc vào mô-đun ứng dụng của mình
Tệp build.gradle
. Hãy đảm bảo rằng bạn sử dụng phiên bản phần phụ thuộc mới nhất.
Ngoài ra, khi bạn mở rộng khả năng hỗ trợ của ứng dụng sang các kiểu dáng khác, chẳng hạn như Wear OS,
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 này về 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 cảm biến bộ đếm bước:
- Lấy đối tượng
SensorManager
từgetSystemService()
. - Thu nạ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ị thông báo lỗi nếu thiết bị không có:
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 ứng dụng thể dục cơ bản, bạn có thể thấy một nút để nhận các sự kiện bắt đầu và dừng từ người dùng để theo dõi các bước.
Bạn cũng cần 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ỉ sẽ đếm số bước khi cảm biến trình nghe đã được đăng ký. Bằng cách liên kết quy trình đăng ký cảm biến với nền trước dịch vụ, cảm biến sẽ được đăng ký trong 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 trên 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 của bạn:
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
. Ghi chú
rằng bạn nên liên kết việc đăng ký cảm biến với
vòng đời, huỷ đăng ký cảm biến khi dịch vụ bị tạm dừng hoặc kết thúc. Chiến lược phát hành đĩa đơn
đ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 sử dụng Thư viện lưu trữ Room.
Đoạn mã sau đây sẽ tạo một bảng chứa tập hợp số bước các chỉ số đo lường, cùng với thời gian ứ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 đố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ể cho 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ần khi bạn đọ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 khoảng thời gian ứng dụng của bạn 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 của bạn.
Tuy nhiên, nếu muốn phân nhóm các bản ghi số bước, bạn có thể sử dụng WorkManager
để
đo các bước tại 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 chức năng nền
đảm bảo quá trình thực thi. 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 đè doWork()
như minh hoạ trong đoạn mã sau đây:
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 15 phút một lần, hãy
như sau:
- Mở rộng lớp
Application
để triển khaiConfiguration.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 bước trong ứng dụng cơ sở dữ liệu bộ đếm ngay khi khởi động ứng dụng, hãy thêm phần tử sau đây 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" />