استخدِم Sensor Manager لملء بيانات الخطوات في تطبيق على الأجهزة الجوّالة، كما هو موضّح في هذا الدليل. لمزيد من المعلومات حول كيفية تصميم واجهة مستخدم لتطبيق تمارين رياضية وإدارتها، يُرجى الرجوع إلى إنشاء تطبيق لياقة بدنية أساسي.
خطوات البدء:
لبدء قياس خطوات عدّاد الخطوات الأساسي من جهازك الجوّال، عليك إضافة التبعيات إلى ملف 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")
الحصول على مستشعر عدّاد الخطوات
بعد أن يمنح المستخدم إذن التعرّف على النشاط اللازم، يمكنك الوصول إلى مستشعر عدّ الخطوات:
- احصل على العنصر
SensorManager
منgetSystemService()
. - احصل على أداة استشعار عدّاد الخطوات من
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")
}
}
إنشاء قاعدة بيانات لأحداث المستشعر
قد يعرض تطبيقك شاشة يمكن للمستخدم من خلالها الاطّلاع على خطواته بمرور الوقت. لاستخدام هذه الإمكانية في تطبيقك، استخدِم مكتبة Room لتخزين البيانات بشكل دائم.
تنشئ المقتطفة البرمجية التالية جدولاً يحتوي على مجموعة من قياسات عدد الخطوات، بالإضافة إلى الوقت الذي وصل فيه تطبيقك إلى كل قياس:
@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 التعليمي.
لضبط الكائن 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 دقيقة، اتّبِع الخطوات التالية:
- وسِّع الفئة
Application
لتنفيذ الواجهةConfiguration.Provider
. - في طريقة
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" />