ใช้ตัวจัดการเซ็นเซอร์เพื่อวัดจำนวนก้าวจากอุปกรณ์เคลื่อนที่

ใช้ตัวจัดการเซ็นเซอร์เพื่อป้อนข้อมูลจำนวนก้าวในแอปบนอุปกรณ์เคลื่อนที่ตามที่อธิบายไว้ใน สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีออกแบบและจัดการ UI ของแอปออกกำลังกาย อ้างถึง สร้างแอปการออกกำลังกายพื้นฐาน

เริ่มต้นใช้งาน

ในการเริ่มต้นวัดจำนวนก้าวด้วยตัวนับก้าวเบื้องต้นจาก บนอุปกรณ์เคลื่อนที่ คุณจะต้องเพิ่มทรัพยากร Dependency ลงในโมดูลแอป build.gradle ไฟล์ ตรวจสอบว่าใช้ทรัพยากร Dependency เวอร์ชันล่าสุด นอกจากนี้ เมื่อคุณขยายการรองรับแอปไปยังอุปกรณ์รูปแบบอื่นๆ เช่น Wear OS เพิ่มทรัพยากร Dependency ที่รูปแบบของอุปกรณ์เหล่านี้ต้องการ

ด้านล่างนี้คือตัวอย่างบางส่วนของทรัพยากร Dependency ของ 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")

รับเซ็นเซอร์นับก้าว

หลังจากที่ผู้ใช้ให้สิทธิ์การจดจำกิจกรรมที่จำเป็นแล้ว คุณเข้าถึงเซ็นเซอร์นับก้าวได้โดยทำดังนี้

  1. รับออบเจ็กต์ SensorManager จาก getSystemService()
  2. รับเซ็นเซอร์นับก้าวจาก 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")
}

สร้างบริการที่ทำงานอยู่เบื้องหน้า

ในแอปฟิตเนสพื้นฐาน คุณอาจมีปุ่ม เพื่อรับเหตุการณ์เริ่มต้นและหยุดจากผู้ใช้เพื่อติดตามขั้นตอน

โปรดทราบแนวทางปฏิบัติแนะนำเกี่ยวกับเซ็นเซอร์ โดยเฉพาะอย่างยิ่ง เซ็นเซอร์นับก้าวควรนับก้าวขณะที่เซ็นเซอร์เท่านั้น Listener ที่ลงทะเบียนแล้ว เชื่อมโยงการลงทะเบียนเซ็นเซอร์กับเบื้องหน้า เซ็นเซอร์จะลงทะเบียนไว้ตราบเท่าที่จำเป็น และเซ็นเซอร์สามารถ ยังคงลงทะเบียนอยู่เมื่อแอปไม่ได้ทำงานอยู่ในเบื้องหน้า

ใช้ข้อมูลโค้ดต่อไปนี้เพื่อยกเลิกการลงทะเบียนเซ็นเซอร์ในเมธอด onPause() ของ บริการที่ทำงานอยู่เบื้องหน้า

override fun onPause() {
    super.onPause()
    sensorManager.unregisterListener(this)
}

วิเคราะห์ข้อมูลสำหรับเหตุการณ์

หากต้องการเข้าถึงข้อมูลเซ็นเซอร์ ให้ใช้อินเทอร์เฟซ SensorEventListener หมายเหตุ ว่าคุณควรเชื่อมโยงการลงทะเบียนเซ็นเซอร์กับบริการที่ทำงานอยู่เบื้องหน้า วงจร กำลังยกเลิกการลงทะเบียนเซ็นเซอร์เมื่อบริการหยุดชั่วคราวหรือสิ้นสุด ข้อมูลโค้ดต่อไปนี้แสดงวิธีใช้งานอินเทอร์เฟซ SensorEventListener สำหรับ 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")
    }
}

สร้างฐานข้อมูลสำหรับเหตุการณ์เซ็นเซอร์

แอปของคุณอาจแสดงหน้าจอที่ผู้ใช้สามารถดูจำนวนก้าวของตนเองเมื่อเวลาผ่านไป หากต้องการใช้ความสามารถนี้ในแอป ให้ใช้ไลบรารีความต่อเนื่องของห้อง

ข้อมูลโค้ดต่อไปนี้จะสร้างตารางที่มีชุดจำนวนก้าว การวัด พร้อมกับเวลาที่แอปของคุณเข้าถึงการวัดแต่ละครั้ง

@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 เพื่อทำสิ่งต่อไปนี้ได้ วัดจำนวนก้าวในช่วงเวลาที่ระบุ เช่น 1 ครั้งทุกๆ 15 นาที WorkManager เป็นคอมโพเนนต์ที่ทำงานอยู่เบื้องหลัง รับประกันการดำเนินการได้อย่างแน่นอน ดูข้อมูลเพิ่มเติมใน WorkManager Codelab

หากต้องการกำหนดค่าออบเจ็กต์ 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 นาที ให้ทำ ดังต่อไปนี้

  1. ขยายคลาส Application เพื่อนำ Configuration.Provider ไปใช้ ของ Google
  2. ในเมธอด 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()
}

วิธีเริ่มต้นผู้ให้บริการเนื้อหาที่ควบคุมการเข้าถึงขั้นตอนของแอป ฐานข้อมูลตัวนับทันทีที่เริ่มต้นแอป ให้เพิ่มองค์ประกอบต่อไปนี้ลงใน ไฟล์ Manifest ของแอป

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    tools:node="remove" />