Bir mobil cihazdaki adımları ölçmek için Sensör Yöneticisi'ni kullanın

Bu kılavuzda açıklandığı gibi, bir mobil uygulamada adım verilerini doldurmak için Sensor Manager'ı kullanın. Egzersiz uygulaması kullanıcı arayüzü tasarlama ve yönetme hakkında daha fazla bilgi için Temel bir fitness uygulaması oluşturma başlıklı makaleyi inceleyin.

Başlarken

Temel adım sayacınızın adımlarını mobil cihazınızdan ölçmeye başlamak için bağımlılıkları uygulama modülü build.gradle dosyanıza eklemeniz gerekir. Bağımlılıkların en son sürümlerini kullandığınızı doğrulayın. Ayrıca, uygulamanızın desteğini Wear OS gibi diğer form faktörlerine genişlettiğinizde bu form faktörlerinin gerektirdiği bağımlılıkları ekleyin.

Aşağıda, kullanıcı arayüzü bağımlılıklarından bazılarına ilişkin örnekler verilmiştir. Tam liste için bu UI Elements (Kullanıcı Arayüzü Öğeleri) kılavuzuna bakın.

implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.activity:activity-compose")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material")

Adım sayacı sensörünü edinme

Kullanıcı gerekli hareket tanıma iznini verdikten sonra adım sayacı sensörüne erişebilirsiniz:

  1. getSystemService() kaynağından SensorManager nesnesini alın.
  2. Adım sayacı sensörünü SensorManager'dan edinin:
private val sensorManager by lazy {
        getSystemService(Context.SENSOR_SERVICE) as SensorManager }
private val sensor: Sensor? by lazy {
        sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) }

Bazı cihazlarda adım sayacı sensörü bulunmaz. Sensörü kontrol etmeli ve cihazda sensör yoksa hata mesajı göstermelisiniz:

if (sensor == null) {
    Text(text = "Step counter sensor is not present on this device")
}

Ön plan hizmetinizi oluşturma

Temel bir fitness uygulamasında, adımları izlemek için kullanıcıdan başlangıç ve durdurma etkinliklerini almak üzere bir düğme olabilir.

Sensör en iyi uygulamalarını göz önünde bulundurun. Özellikle adım sayacı sensörü, yalnızca sensör dinleyicisi kaydedilmişken adımları saymalıdır. Sensör kaydı bir ön plan hizmetiyle ilişkilendirildiğinde sensör, gerektiği sürece kaydedilir ve uygulama ön planda olmadığında da kayıtlı kalabilir.

Ön plan hizmetinizin onPause() yönteminde sensörün kaydını silmek için aşağıdaki snippet'i kullanın:

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

Etkinliklerle ilgili verileri analiz etme

Sensör verilerine erişmek için SensorEventListener arayüzünü uygulayın. Sensör kaydını ön plan hizmetinizin yaşam döngüsüyle ilişkilendirmeniz ve hizmet duraklatıldığında veya sona erdiğinde sensörün kaydını silmeniz gerektiğini unutmayın. Aşağıdaki snippet'te, SensorEventListener arayüzünün Sensor.TYPE_STEP_COUNTER için nasıl uygulanacağı gösterilmektedir:

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")
    }
}

Sensör etkinlikleri için veritabanı oluşturma

Uygulamanız, kullanıcının zaman içindeki adımlarını görüntüleyebileceği bir ekran gösterebilir. Uygulamanızda bu özelliği sağlamak için Room kalıcılık kitaplığını kullanın.

Aşağıdaki snippet, bir dizi adım sayısı ölçümü ve uygulamanızın her ölçüme eriştiği zamanı içeren bir tablo oluşturur:

@Entity(tableName = "steps")
data class StepCount(
  @ColumnInfo(name = "steps") val steps: Long,
  @ColumnInfo(name = "created_at") val createdAt: String,
)

Verileri okumak ve yazmak için bir veri erişim nesnesi (DAO) oluşturun:

@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'yu başlatmak için RoomDatabase nesnesi oluşturun:

@Database(entities = [StepCount::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun stepsDao(): StepsDao
}

Sensör verilerini veritabanında depolama

ViewModel, yeni StepCounter sınıfını kullandığı için okuduğunuz adımları hemen depolayabilirsiniz:

viewModelScope.launch {
    val stepsFromLastBoot = stepCounter.steps()
    repository.storeSteps(stepsFromLastBoot)
}

repository sınıfı şöyle görünür:

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
            }
        }
    }
}


Sensör verilerini düzenli olarak alma

Ön plan hizmeti kullanıyorsanız WorkManager yapılandırmanız gerekmez. Çünkü uygulamanız kullanıcının adımlarını etkin olarak izlerken güncellenen toplam adım sayısı uygulamanızda görünür.

Ancak adım kayıtlarınızı toplu olarak almak isterseniz WorkManager kullanarak adımları belirli bir aralıkta (ör. her 15 dakikada bir) ölçebilirsiniz. WorkManager, güvenilir yürütme için arka planda çalışan bileşendir. WorkManager codelab'inde daha fazla bilgi edinin.

Verileri almak için Worker nesnesini yapılandırmak üzere aşağıdaki kod snippet'inde gösterildiği gibi doWork() yöntemini geçersiz kılın:

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'yı her 15 dakikada bir mevcut adım sayısını saklayacak şekilde ayarlamak için aşağıdakileri yapın:

  1. Application sınıfını genişleterek Configuration.Provider arayüzünü uygulayın.
  2. onCreate() yönteminde PeriodicWorkRequestBuilder öğesini sıraya alın.

Bu işlem aşağıdaki kod snippet'inde gösterilmektedir:

@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()
}

Uygulama başlatıldığında uygulamanızın adım sayacı veritabanına erişimi kontrol eden içerik sağlayıcıyı hemen başlatmak için uygulamanızın manifest dosyasına aşağıdaki öğeyi ekleyin:

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