Monitorowanie danych w tle

Pasywne aktualizacje danych są odpowiednie dla aplikacji, które muszą monitorować dane Usług zdrowotnych w tle. Są one przeznaczone do zastosowań, które trwają wiele godzin, dni, a nawet dłużej. Jeśli musisz przechowywać lub przetwarzać dane dotyczące zdrowia i aktywności fizycznej, gdy aplikacja nie jest uruchomiona, a użytkownik nie wykonuje ćwiczeń, użyj pasywnego klienta usługi Health Service.

Przykłady użycia danych pasywnych znajdziesz w próbkach Passive DataPassive Goals w GitHubie.

Dodawanie zależności

Aby dodać zależność od Usług zdrowotnych, musisz dodać do projektu repozytorium Google Maven. Więcej informacji znajdziesz w repozytorium Maven Google.

W pliku build.gradle na poziomie modułu dodaj tę zależność:

Groovy

dependencies {
    implementation "androidx.health:health-services-client:1.1.0-alpha05"
}

Kotlin

dependencies {
    implementation("androidx.health:health-services-client:1.1.0-alpha05")
}

Sprawdzanie możliwości

Zanim zarejestrujesz się w celu otrzymywania aktualizacji danych, sprawdź, czy urządzenie może dostarczać typ danych, którego potrzebuje Twoja aplikacja. Sprawdzanie możliwości pozwala włączać i wyłączać niektóre funkcje lub modyfikować interfejs aplikacji, aby zrekompensować brak dostępnych funkcji.

val healthClient = HealthServices.getClient(this /*context*/)
val passiveMonitoringClient = healthClient.passiveMonitoringClient
lifecycleScope.launchWhenCreated {
    val capabilities = passiveMonitoringClient.capabilities.await()
    // Supported types for passive data collection
    supportsHeartRate =
        DataType.HEART_RATE_BPM in capabilities.supportedDataTypesPassiveMonitoring
    // Supported types for PassiveGoals
    supportsStepsGoal =
        DataType.STEPS_DAILY in capabilities.supportedDataTypesPassiveGoals
}

Rejestracja danych pasywnych

Dane pasywne możesz otrzymywać za pomocą usługi, wywołania zwrotnego lub obu tych metod. Usługa umożliwia aplikacji odbieranie danych w tle, gdy żadna jej część nie jest widoczna na pierwszym planie. Gdy otrzymujesz dane w tle, są one dostarczane w partiach. Wywołanie zwrotne odbiera dane nieco szybciej, ale tylko wtedy, gdy aplikacja jest uruchomiona i wywołanie zwrotne zostało powiadomione.

Niezależnie od wybranej metody najpierw utwórz PassiveListenerConfig, który określa, jakie typy danych mają być odbierane, jak pokazano w tym przykładzie:

val passiveListenerConfig = PassiveListenerConfig.builder()
    .setDataTypes(setOf(DataType.HEART_RATE_BPM))
    .build()

Aby otrzymywać dane za pomocą wywołania zwrotnego, zdefiniuj i zarejestruj wywołanie zwrotne, jak pokazano w tym przykładzie:

val passiveListenerCallback: PassiveListenerCallback = object : PassiveListenerCallback {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
        // TODO: Do something with dataPoints
    }
}

passiveMonitoringClient.setPassiveListenerCallback(
    passiveListenerConfig,
    passiveListenerCallback
)

// To remove the listener
passiveMonitoringClient.clearPassiveListenerCallbackAsync()

Korzystanie z usługi jest podobne, ale zamiast tworzyć klasę pochodną z PassiveListenerCallback, utwórz klasę pochodną z PassiveListenerService, jak pokazano w tym przykładzie:

class PassiveDataService : PassiveListenerService() {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
        // TODO: Do something with dataPoints
    }
}

passiveMonitoringClient.setPassiveListenerServiceAsync(
    PassiveDataService::class.java,
    passiveListenerConfig
)

Następnie zadeklaruj usługę w pliku AndroidManifest.xml. Wymagaj uprawnienia Health Services, które potwierdza, że tylko Usługi zdrowotne mogą powiązać się z usługą:

<service android:name=".PassiveDataService"
    android:permission="com.google.android.wearable.healthservices.permission.PASSIVE_DATA_BINDING"
    android:exported="true" />

Interpretacja czasu

Dane otrzymywane z usług zdrowotnych są przesyłane w pakietach, więc w tym samym pakiecie możesz otrzymać punkty danych różnych typów lub wiele punktów danych tego samego typu. Aby określić prawidłową kolejność zdarzeń, używaj sygnatur czasowych zawartych w tych obiektach, a nie czasu, w którym aplikacja je otrzymała.

Uzyskaj sygnatury czasowe dla każdego DataPoint, obliczając najpierw sygnaturę czasową rozruchu, jak pokazano w tym przykładzie:

val bootInstant =
    Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime())

Tę wartość można następnie przekazać do getStartInstant() lub getEndInstant().

Przywracanie rejestracji po uruchomieniu

Pasywne rejestracje danych nie są zachowywane po ponownym uruchomieniu. Aby otrzymywać dane po ponownym uruchomieniu urządzenia, ponownie utwórz rejestracje za pomocą BroadcastReceiver, które nasłuchuje transmisji systemowej ACTION_BOOT_COMPLETED.

Na urządzeniu odbierającym nie próbuj bezpośrednio przywracać rejestracji. Zamiast tego przekaż tę funkcję pracownikowi WorkManager. Podczas uruchamiania urządzenia Usługi dotyczące zdrowia mogą potrzebować 10 sekund lub więcej na potwierdzenie żądania rejestracji danych pasywnych, co może przekroczyć dopuszczalny czas wykonania BroadcastReceiver. Natomiast WorkManager procesy robocze mają 10-minutowy limit czasu wykonywania.

Poniższy fragment kodu pokazuje, jak może wyglądać znak BroadcastReceiver:

class StartupReceiver : BroadcastReceiver() {

   override fun onReceive(context: Context, intent: Intent) {
       if (intent.action != Intent.ACTION_BOOT_COMPLETED) return


       // TODO: Check permissions first
       WorkManager.getInstance(context).enqueue(
           OneTimeWorkRequestBuilder<RegisterForPassiveDataWorker>().build()
       )
   }
}

class RegisterForPassiveDataWorker(
   private val appContext: Context,
   workerParams: WorkerParameters
) : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       runBlocking {
           HealthServices.getClient(appContext)
                .passiveMonitoringClient
                .setPassiveListenerCallback(...)
       }
       return Result.success()
   }
}

Aby system wykonał ten kod podczas uruchamiania urządzenia, wprowadź 2 zmiany w pliku AndroidManifest.xml.

Najpierw dodaj to uprawnienie jako element podrzędny <manifest>:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

Następnie dodaj ten filtr intencji odbiorcy jako element podrzędny elementu <application>:

<receiver
    android:name=".StartupReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

Stan aktywności

Klient pasywny może też przekazywać ogólne informacje o stanie użytkownika, np. czy śpi. Aby otrzymywać te aktualizacje, wykonaj te czynności:

  1. Poproś o uprawnienia ACTIVITY_RECOGNITION.
  2. Wywołaj metodę setShouldUserActivityInfoBeRequested(true) w konstruktorze PassiveListenerConfig.

Zastąp metodę onUserActivityInfoReceived() w wywołaniu zwrotnym lub usłudze i skorzystaj ze zwróconego parametru UserActivityInfo, jak pokazano w tym przykładzie:

override fun onUserActivityInfoReceived(info: UserActivityInfo) {
    val stateChangeTime: Instant = info.stateChangeTime // may be in the past!
    val userActivityState: UserActivityState = info.userActivityState
    if (userActivityState == UserActivityState.USER_ACTIVITY_ASLEEP) {
        // ...
    }
}

Cele pasywne

Możesz skonfigurować klienta pasywnego tak, aby powiadamiał aplikację o osiągnięciu pasywnych celów,np. gdy użytkownik zrobi 10 000 kroków w ciągu dnia.

Aby to zrobić, utwórz cel, jak pokazano w tym przykładzie:

val dailyStepsGoal by lazy {
    val condition = DataTypeCondition(
        dataType = DataType.STEPS_DAILY,
        threshold = 10_000, // Trigger every 10000 steps
        comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
    )
    PassiveGoal(condition)
}

Dodaj ten cel do PassiveListenerConfig, jak pokazano w tym przykładzie:

val passiveListenerConfig = PassiveListenerConfig.builder()
    .setDailyGoals(setOf(dailyStepsGoal))
    .build()

Zastąp metodę onGoalCompleted() w wywołaniu zwrotnym lub usłudze i skorzystaj ze zwróconego parametru PassiveGoal, jak pokazano w tym przykładzie:

override fun onGoalCompleted(goal: PassiveGoal) {
    when (goal.dataTypeCondition.dataType) {
        DataType.STEPS_DAILY -> {
            // ...
        }
    }
}