از «مدیر حسگر» برای اندازه‌گیری مراحل یک دستگاه تلفن همراه استفاده کنید

همانطور که در این راهنما توضیح داده شده است، از مدیر حسگر برای پر کردن داده های مراحل در یک برنامه تلفن همراه استفاده کنید. برای اطلاعات بیشتر در مورد نحوه طراحی و مدیریت رابط کاربری برنامه ورزشی، به ساخت یک برنامه تناسب اندام پایه مراجعه کنید.

شروع کردن

برای شروع با اندازه‌گیری مراحل گام شمار اصلی خود از دستگاه تلفن همراه خود، باید وابستگی‌ها را به فایل build.gradle ماژول برنامه خود اضافه کنید. اطمینان حاصل کنید که از آخرین نسخه های وابستگی استفاده می کنید. همچنین، هنگامی که پشتیبانی برنامه خود را به سایر فاکتورهای شکل مانند Wear OS گسترش می دهید، وابستگی هایی را که این عوامل فرم نیاز دارند اضافه کنید.

در زیر چند نمونه از برخی از وابستگی های 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")
}

سرویس پیش زمینه خود را ایجاد کنید

در یک برنامه اصلی تناسب اندام، ممکن است دکمه ای برای دریافت شروع و توقف رویدادها از کاربر برای پیگیری مراحل داشته باشید.

بهترین شیوه های سنسور را به خاطر داشته باشید. به ویژه، سنسور گام شمار تنها باید در زمانی که شنونده حسگر ثبت شده است، قدم ها را شمارش کند. با مرتبط کردن ثبت سنسور با یک سرویس پیش زمینه، سنسور تا زمانی که نیاز باشد ثبت می شود و زمانی که برنامه در پیش زمینه نباشد، حسگر می تواند ثبت شود.

از قطعه زیر برای لغو ثبت سنسور در روش 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 برای اندازه‌گیری گام‌ها در یک بازه زمانی خاص، مثلاً هر 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 گسترش دهید.
  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()
}

برای مقداردهی اولیه ارائه‌دهنده محتوا که دسترسی به پایگاه داده شمارنده گام برنامه شما را بلافاصله پس از راه‌اندازی برنامه کنترل می‌کند، عنصر زیر را به فایل مانیفست برنامه خود اضافه کنید:

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