Туториалы
04 ноября 2021
Туториалы
04 ноября 2021
Туториал по Android Sleep API

Узнайте, как использовать Android Sleep API в ваших приложениях Kotlin, чтобы отслеживать, когда пользователь спит, бодрствует, как долго он спал, и достоверность результатов.

В этом туториале вы узнаете, как взаимодействовать с Android Sleep API и реагировать на поток событий, отправленный системой. Android Sleep API собирает такую информацию, как окружающая яркость и движение устройства, чтобы делать предположения о том, когда пользователь спит или бодрствует.

Данный API удобен для отслеживания качества сна пользователей, чтобы помочь им улучшить свои привычки сна.

Создавая простое приложение для отслеживания сна, вы научитесь как:

  • Запрашивать разрешение на Распознавание Действий для вашего приложения.
  • Регистрировать Sleep Receiver для фильтрации и анализа различных событий, воспринимаемых устройством.

Самое время начать! Постарайтесь не заснуть. ;)

Начало работы

Нажмите «Скачать Материалы», чтобы загрузить образец проекта.

Импортируйте стартовый проект в Android Studio. Скомпилируйте и запустите его. Вы увидите классический образец экрана:

 

В этом туториале вы не будете много взаимодействовать с UI, поэтому создание и запуск приложения больше связаны с проверкой правильности кода.

Заметка

Вам потребуется физическое устройство как минимум с Android 10, чтобы использовать Sleep API и протестировать код, который вы напишете в этом туториале.

 

Использование Android Sleep API

Чтобы использовать этот API, вам понадобятся две части кода:

  1. Клиент ActivityRecognition, который позволяет подписаться на обновления сна.
  2. BrodcastReceiver, чтобы получать обновления от клиента ActivityRecognition.

Кроме того, вам нужно запросить разрешение у пользователя прослушивать его данные о сне, которое он предоставляет за пределами приложения, на экране Настройки.

Этот API предоставит два набора информации:

  • Уровень достоверности того, что ваш пользователь спит, о котором API сообщает с интервалом в несколько минут.
  • Сегмент ежедневного сна, который API генерирует после того, как устройство обнаруживает, что пользователь проснулся.

Чтобы использовать этот API, сначала необходимо включить зависимости. Откройте приложение ‣ build.gradle и добавьте следующую строку в блок dependencies:

implementation 'com.google.android.gms:play-services-location:18.0.0'

Теперь вы готовы начать писать код.

Прослушивание данных сна

Прежде чем вы сможете делать что-либо еще, вам необходимо получить данные о сне. Компонент, который предоставляет эту информацию, - это BroadcastReceiver.

Откройте SleepReceiver.kt. Обратите внимание, что SleepReceiver уже расширяет BroadcastReceiver пустым onReceive. В этом методе вы добавите логику, которая будет фильтровать данные.

Откройте AndroidManifest.xml. Вы увидите, что SleepReceiver уже объявлен внутри, прямо под MainActivity. Это выглядит так:

Вам необходимо объявить SleepReceiver в AndroidManifest.xml, чтобы система знала, что BroadcastReceiver доступен в вашем приложении.

Теперь, когда у вас настроен SleepReceiver, пора написать логику, которая будет фильтровать события, поступающие из Intent.

 

Фильтрация данных о сне

Откройте SleepReceiver. Добавьте следующее условие внутри onReceive():

if (SleepSegmentEvent.hasEvents(intent)) {
} else if (SleepClassifyEvent.hasEvents(intent)) {
}

Здесь вы проверяете, содержит ли Intent SleepSegmentEvents или SleepClassifyEvents.

Затем добавьте следующий сопутствующий объект в конец класса:

companion object {
   private const val TAG = "SLEEP_RECEIVER"
}

Вы будете использовать этот тег для вывода событий в консоль и фильтрации текста, который будет отображаться в нескольких строках.

Теперь сосредоточимся на первой ветви условия if!
Сразу под строкой if добавьте:

val events =
    SleepSegmentEvent.extractEvents(intent)

Log.d(TAG, "Logging SleepSegmentEvents")

for (event in events) {
  Log.d(TAG,
      "${event.startTimeMillis} to ${event.endTimeMillis} with status ${event.status}")
}

В случае, когда Intent содержит SleepSegmentEvent, вы извлекаете его и выводите в консоль его начальную и конечную отметки времени и его статус. Он представляет собой временную метку UNIX, когда устройство обнаружило, что пользователь начал спать, проснулся и статус-код, указывающий, удалось ли системе собрать данные сна, соответственно.

Теперь вы можете получить сегменты сна, но вы также хотите классифицировать события. Вы можете сделать это в ветке else, добавив:

val events = SleepClassifyEvent.extractEvents(intent)

Log.d(TAG, "Logging SleepClassifyEvents")

for (event in events) {
    Log.d(TAG,
        "Confidence: ${event.confidence} - Light: ${event.light} - Motion: ${event.motion}"
    )
}

Еще раз, вам нужно извлечь информацию о событии и вывести его данные в консоль, выделив уровень достоверности классификации, количество света и значение движения.

На этом этапе, если вы скомпилируете и запустите приложение, вы не увидите никаких выходных данных в консоли. Вам всё еще нужно сделать несколько дополнительных шагов, прежде чем вы сможете получить их!

 

Запрос разрешений на распознавание действий

Чтобы получить данные о сне, вам необходимо иметь разрешение пользователя на доступ к ним. Это не так просто, как с другими разрешениями, поскольку вам нужно открыть настройки для данных, а пользователь должен предоставить вашему приложению разрешения на распознавание действий. Но и не так сложно.

Сначала откройте AndroidManifest.xml и добавьте следующее разрешение внутри manifest:

Далее вам нужно создать метод внутри MainActivity.kt, который откроет экран настроек.
Откройте MainActivity и добавьте следующее, импортировав android.provider.Settings:

private fun requestActivityRecognitionPermission() {

   val intent = Intent().apply {
        action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
        data = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
        flags = Intent.FLAG_ACTIVITY_NEW_TASK
   }

   startActivity(intent)
}

Здесь вы создаете Настройки Intent, добавляя действие Распознавания Активности и дополнительный пакет вашего приложения. Затем вы запускаете Intent.

Теперь, когда вам нужно запрашивать разрешения, вы можете использовать ActivityResultContracts API, который поможет уменьшить шаблон для прослушивания результатов активности.

Начните с добавления ktx библиотеки активности в приложение ‣ build.gradle:

implementation 'androidx.activity:activity-ktx:1.2.3'

Синхронизируйте ваш gradle-файл. Это добавит ActivityResultContracts и ActivityResultLauncher в ваш проект.

Затем добавьте это свойство в MainActivity:

private val permissionRequester: ActivityResultLauncher =
     registerForActivityResult(
         ActivityResultContracts.RequestPermission()
    ) { isGranted ->

       if (!isGranted) {
         requestActivityRecognitionPermission()
       } else {
         // Request goes here
       }
     }

Здесь вы создаете объект, который прослушает результат Activity из вашего запроса на разрешение и передаст его вам, как только этот объект проанализирует его.

Еще не время создавать и запускать свое приложение. Если вы попытаетесь, оно все равно не покажет никаких данных. Но это произойдет скоро!

Подписка на обновления данных о сне

Далее вы подпишетесь на обновления данных о сне. Для этого вам нужно создать новый класс. Создайте SleepRequestsManager в том же пакете, что и MainActivity. Объявление класса должно выглядеть так:

class SleepRequestsManager(private val context: Context) {

}

Этот класс будет управлять подпиской и отменой подписки на обновление. Сначала разберемся только с подпиской. Для этого вам необходимо передать Context в качестве параметра конструктора.

Далее вы добавляете метод, который позволит вам подписаться на обновления данных о сне. Добавьте этот метод в свой новый класс:

fun subscribeToSleepUpdates() {

}

Теперь у вас есть место для добавления подписки.

Откройте SleepReceiver и прокрутите вниз до объявления константы TAG внутри сопутствующего объекта. Внутри сопутствующего объекта вставьте этот код, который создаст PendingIntent из Context, переданного в качестве параметра:

fun createPendingIntent(context: Context): PendingIntent {
     val intent = Intent(context, SleepReceiver::class.java)

     return PendingIntent.getBroadcast(
         context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)
   }

Чтобы подписаться на обновления, вы создаете PendingIntent из BroadcastReceiver, который будет получать обновления. Вы уже создали этот ресивер, но вам все еще нужен PendingIntent.

На этом этапе у вас есть все необходимое для создания логики подписки. Вернитесь в SleepRequestManager и создайте PendingIntent прямо между конструктором класса и пустым subscribeToSleepUpdates():

private val sleepReceiverPendingIntent by lazy {
   SleepReceiver.createPendingIntent(context)
}

Данный PendingIntent - это то, что вы будете использовать в следующем шаге, чтобы подписаться на обновления.

Теперь у вас есть все необходимое для подписки на обновления и вы можете попросить API отправлять данные вашему получателю. Внутри subscribeToSleepUpdates() добавьте:

ActivityRecognition.getClient(context)
    .requestSleepSegmentUpdates (sleepReceiverPendingIntent,
        SleepSegmentRequest.getDefaultSleepSegmentRequest())

В этом коде вы вызываете клиент ActivityRecognition и запрашиваете обновления о сегментах сна с запросом по умолчанию.

Однако, прежде чем вы сможете подписаться, вам все равно нужно проверить наличие разрешения. В конце концов, ваш пользователь всегда может его удалить.

Вам необходимо проверить, можете ли вы получить доступ к данным. И подписаться на обновления, если это еще возможно. Если вы не можете этого сделать, вам нужно запросить разрешение.

Сначала добавьте этот код выше subscribeToSleepUpdates():

fun requestSleepUpdates(requestPermission: () -> Unit = {}) {
   if (ContextCompat.checkSelfPermission(
       context, Manifest.permission.ACTIVITY_RECOGNITION) == 
        PackageManager.PERMISSION_GRANTED) {
     subscribeToSleepUpdates()
   } else {
     requestPermission()
   }
}

Здесь разбивка кода:

Первое, что вы видите, - это лямбда-параметр requestPermission. Это обратный вызов, который потребуется вам, если у вас нет разрешения на получение данных. Вы получаете его как параметр, поскольку вам все равно нужно зарегистрировать обратный вызов внутри Activity, поэтому лямбду легче передать снаружи метода.

В первой строке метода вы используете ContextCompat, для проверки наличия разрешения на Распознавание Действий (Activity Recognition). Если разрешение есть, вы вызываете subscribeToSleepUpdates() для получения обновлений. Если нет, вы запрашиваете разрешение снова.

Теперь вернитесь в MainActivity. Добавьте объявление для SleepRequestManager, который вы только что создали:

private val sleepRequestManager by lazy{
   SleepRequestsManager(this)
}

Теперь у вас есть SleepRequestManager в MainActivity для запроса обновлений сна.

Прокрутите до строки, где вы видите // Request goes here в permissionRequester, и замените его вызовом подписки:

sleepRequestManager.subscribeToSleepUpdates()

Так как пользователь предоставил разрешение вашему приложению, вы вызываете обновление напрямую и выполняется условие в else. Итак, в этом конкретном случае вы можете запустить обновление безопасно.

Наконец, вам нужно запустить SleepRequestManager, когда пользователь открывает приложение. Прокрутите вниз до строки, где вы видите // Your code внизу onCreate() и добавьте:

sleepRequestManager.requestSleepUpdates(requestPermission = {
     permissionRequester.launch(ACTIVITY_RECOGNITION)
   })

Здесь вы просите SleepRequestManager запрашивать обновления и указываете, как он должен себя вести, если у него нет разрешения на получение данных.

Теперь скомпилируйте и запустите. Вы заметите, что на вкладке Logcat начинают появляться некоторые события! Убедитесь, что первым вы принимаете разрешение.

 

Отказ от подписки на обновления

Сейчас ваше приложение не перестает прослушивать события обновления сна. Однако вам может понадобиться отказ от подписки, чтобы позволить пользователям запускать и останавливать приложение, когда они захотят.

Чтобы создать логику отказа от подписки на обновления, откройте SleepRequestManager и ниже subscribeToSleepUpdates добавьте:

fun unsubscribeFromSleepUpdates() {
   ActivityRecognition.getClient(context)
       .removeSleepSegmentUpdates(sleepReceiverPendingIntent)
}

Здесь вы удаляете подписку из клиента ActivityRecognition.

Теперь вернитесь в MainActivity. Переопределите onDestroy() и вызовите метод отмены подписки:

override fun onDestroy() {
   super.onDestroy()
   sleepRequestManager.unsubscribeFromSleepUpdates()
}

Этот код гарантирует, что приложение прекращает прослушивание и запись лог-файлов при выходе пользователя.

Теперь скомпилируйте и запустите. Обратите внимание, что запись лог-файлов прекратится как только вы выйдите!

 

Обработка перезагрузок

Когда устройство перезагрузится, система Android убьет ваше приложение, и ваше программное обеспечение не запустится, пока ваш пользователь не откроет его снова. Это важный момент, потому что вам необходимо записывать модели сна пользователей, когда они спят, что обычно происходит ночью.

К сожалению, обновление большинства устройств также происходит ночью, за которым часто следует перезагрузка. Эти перезагрузки потенциально могут привести к дням без данных, если ваше приложение не может запуститься автоматически после завершения перезагрузки.

Вы можете запустить приложение двумя способами в процессе загрузки:

  1. Первый, режим Direct Boot, происходит после перезагрузки системы, но перед тем, как пользователь войдет в свое устройство.
  2. Другой происходит после завершения загрузки и разблокировки устройства пользователем.

В этом туториале вы узнаете, как реагировать на вход пользователя на своем устройстве после перезагрузки.

 

Прослушивание событий перезагрузки

Если вы хотите, чтобы ваше приложение прослушивало события после перезагрузки устройства, вам нужно добавить еще один BroadcastReceiver. Откройте BootReceiver, и вы увидите, что он пуст и готов к заполнению.

Данный ресивер запустится при перезагрузке устройства и зарегистрируется для обновлений данных о сне. Прямо под onReceive() добавьте companion как здесь:

companion object {
   private const val TAG = "SLEEP_BOOT_RECEIVER"
}

Сначала вы объявляете другую константу TAG String, которую будете использовать для лог-файлов с ошибками. Она отличается от предыдущей при фильтрации вывода в Logcat.

Теперь замените onReceive() на:

override fun onReceive(context: Context, intent: Intent) {
   val sleepRequestManager = SleepRequestsManager(context)

   sleepRequestManager.requestSleepUpdates(requestPermission = {
     Log.d(TAG, "Permission to listen for Sleep Activity has been removed")
   })
 }

Здесь вы создаете новый экземпляр SleepRequestManager и вызываете requestSleepUpdate().

На этот раз вы не сможете запросить разрешение у пользователей, если они его отменили, так как ваше приложение может быть запущено до того, как пользователь войдет в систему. В этом случае они не смогут изменить какие-либо настройки до разблокировки устройства. Вы мало что можете сделать, чтобы предотвратить эту ситуацию, но вы можете записать ошибку и выполнить стратегию сохранения позже.

Для безопасного запуска при загрузке вам необходимо зарегистрировать ресивер в AndroidManifest.xml с определенными флажками, которые сообщат системе, что ваше приложение может запускаться во время загрузки устройства. Для вас это уже есть в AndroidManifest.xml:


  
    

    

    
  

Обратите внимание, что вам необходимо добавить разрешение RECEIVE_BOOT_COMPLETED в объявление Receiver. В противном случае система Android не уведомит ваше приложение о завершении загрузки и оно не запустится автоматически.

Скомпилируйте и запустите. Поздравляю! Теперь вы можете успешно отслеживать данные о сне.

Теперь вы знаете, как взаимодействовать с Android Sleep API и получать обновления о сегментах сна пользователя.

Оригинал статьи


Оцените статью
0
0
0
0
0

Чтобы добавить комментарий, авторизуйтесь
Войти
Акулов Иван Борисович
Пишет и переводит статьи для SwiftBook