استخدام "مدير أدوات الاستشعار" لقياس الخطوات من جهاز جوّال

استخدِم "مدير أدوات الاستشعار" لتعبئة بيانات الخطوات في تطبيق متوافق مع الأجهزة الجوّالة، كما هو موضّح في هذه الشريحة. الدليل. لمزيد من المعلومات حول كيفية تصميم وإدارة واجهة مستخدم تطبيق التمرين، مراجعة إنشاء تطبيق أساسي للياقة البدنية

الخطوات الأولى

للبدء بقياس خطوات عدّاد الخطوات الأساسية من الجهاز المحمول، فستحتاج إلى إضافة التبعيات إلى وحدة تطبيقك ملف build.gradle. تأكد من استخدام أحدث إصدارات التبعيات. بالإضافة إلى ذلك، عند توسيع نطاق دعم تطبيقك ليشمل أشكال الأجهزة الأخرى، مثل Wear OS، وإضافة التبعيات التي تتطلبها عوامل الشكل هذه.

فيما يلي بعض الأمثلة على بعض تبعيات واجهة المستخدم. للحصول على قائمة كاملة، راجِع دليل عناصر واجهة المستخدم هذا.

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 فئة عدد الخطوات الجديدة، لذا يمكنك تخزين الخطوات في أقرب وقت بينما تقرأها:

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.

لضبط كائن 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 من واجهة pyplot.
  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" />