Изучите, как отслеживать свои действия в Android приложении, создав фитнес-приложение, используя Activity Recognition API.
Отслеживание активности не является чем то новым в мире приложений. Хотя Google уже давно создал Activity Recognition API, недавно эта компания улучшила его производительность с помощью ряда API, сгруппированных в ActivityRecognitionClient.
API-интерфейсы, содержащиеся в ActivityRecognitionClient включают:
1. Activity Recognition Transition API для отслеживания переходов между активностями.
2. Activity Recognition Sampling API для получения более полных результатов для последующих действий.
3. Sleep API для определения время сна пользователя.
В этом туториале вы узнаете, как использовать API Transition и API Sampling, создав приложение Pet Buddy, отслеживающее изменения физической активности.
Зачем использовать Activity Recognition Client
Мобильные устройства стали неотъемлемой частью нашей жизни. Пользователи не расстаются со своими телефонами в течение всего дня. Это даёт возможность вашему приложению адаптироваться и помогать вашим пользователям в их повседневных делах.
Activity Recognition Client ведёт сбор данных с датчиков, встроенных в мобильные устройства. Эти датчики помогают вашему приложению лучше понимать пользователя.
Повышенная точность, меньшее потребление заряда батареи и увеличенная производительность - вот некоторые из преимуществ Transition API. Он сообщает приложению, когда пользователь начинает или прекращает действие, будь то ходьба, бег или езда на автомобиле.
Для более частых запросов или необработанных данных об активности вы будете использовать Sampling API. Конечно, более частые запросы означают больший расход заряда батареи, и в зависимости от варианта использования вы несете ответственность за управление этим ресурсом. В то время как Transition API управляет потреблением заряда батареи за вас, Sampling API дает вам возможность большего контроля над данными.
И последнее, но не менее важное: Sleep API - это отдельный API для определения режима сна пользователя. Чтобы определить необходимость во сне, он использует данные датчика акселерометра и датчика внешней освещенности. Он также предоставляет диапазоны времени сна каждый день после обнаружения пробуждения с высокой долей вероятности.
Теперь, когда вы знакомы с API, вы можете приступить к работе над своим первым приложением для распознавания действий.
Приступим
Загрузите стартовый проект, щелкнув Download Materials. Затем откройте стартовый проект в Android Studio 3.5 или более поздней версии.
Посмотрите на код. Вы найдете стартовый проект с активити, макетом, permission helper (помощник разрешений) и множеством задач.
Соберите и запустите стартовый проект. Вы увидите кнопки для запуска (START) и остановки (STOP) отслеживания активности, а также ярлык с надписью, означающей, что отслеживание еще не началось. Также есть ImageView для изображения, которое представляет текущее действие.
Попробуйте использовать приложение. Нажмите START, и вы получите сообщение о том, что отслеживание началось.
Но когда вы начинаете ходить, ничего не происходит, потому что отслеживание активности еще не реализовано. Не волнуйтесь: это проще, чем вы думаете!
Если вы разработали приложение, в котором используются датчики, вы знаете, что вам нужно запросить разрешение на их использование. В следующем разделе вы узнаете, как запросить разрешение, необходимое для отслеживания ваших действий.
Раскрываем возможности датчиков
Все разработчики Android должны знать, как обрабатывать разрешения. Чтобы использовать такие функции Android, как хранение, локация и камера, вам необходимо запросить разрешение.
Тем не менее, не все разрешения требуют явного запроса для всех версий Android. Иногда все, что вам нужно сделать, это добавить его в файл-манифест.
Вам необходимо запросить разрешение на использование Activity Recognition Client на устройствах под управлением Android Q, уровень API 29 или более поздних версий. Для более ранних версий одна строка в файле манифеста сделает за вас чудо.
Для начала откройте файл AndroidManifest.xml. Замените строку в коде, содержащую TODO 1 (эта строка закомментирована) на следующий фрагмент кода:
Устройства под управлением Android Q используют первое разрешение. Чтобы включить Activity Recognition Client для Android Q или более поздней версии, вам потребуется второе разрешение. Но осталось еще многое сделать, чтобы включить датчики на устройствах с Android Q или более поздней версии.
Заметка
Используйте комбинацию клавиш Option + Return на компьютере Мас и Alt + Enter на ПК для устранения любых недостающих зависимостей в процессе работы над проектом.
Откройте класс ActivityPermissionHelper.kt. Замените строку в коде, содержащую TODO 2 (эта строка закомментирована) на следующий блок кода:
val isAndroidQOrLater: Boolean =
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q
return if (isAndroidQOrLater.not()) {
true
} else {
PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACTIVITY_RECOGNITION
)
}
Разбираем пошагово логику этого блока:
1. isAndroidQOrLater предоставит вам информацию, если вам нужно запросить разрешение.
2. Для устройств с версией ниже Android Q проверять статус разрешений не нужно. Достаточно вернуть true.
3. Для устройств под управлением Android Q или более поздней версии необходимо проверить разрешение.
Теперь Pet Buddy может проверять статус разрешения. Затем вы запросите разрешение, если оно не предоставлено.
В классе ActivityPermissionHelper.kt. найдите строку в коде, содержащую TODO 3 (эта строка закомментирована), и замените на следующий блок кода:
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACTIVITY_RECOGNITION).not()) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACTIVITY_RECOGNITION), PERMISSION_REQUEST_ACTIVITY_RECOGNITION)
} else {
showRationalDialog(this)
}
Проверка Permission rationale (обоснования разрешения) возвращается при следующей проверке после отказа в разрешении. Обратите внимание, что условие - это отрицательное значение результата.
Если это условие true, откроется диалоговое окно с запросом на собственное разрешение. Если результат false, пользователь видит настраиваемое диалоговое окно с обоснованием.
Сделайте сборку и запустите. Нажмите START и откажите в разрешении. Затем снова нажмите START, и ваш экран будет выглядеть так:
В диалоге обоснования вы объясняете, почему приложению требуется это разрешение. Проверка Permission rationale возвращает false после двух запросов на разрешение. Затем вам нужно использовать результат запроса разрешения.
Откройте MainActivity.kt. Затем найдите строку в коде, содержащую TODO 4 (эта строка закомментирована) и замените на этот фрагмент кода:
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACTIVITY_RECOGNITION).not() &&
grantResults.size == 1 &&
grantResults[0] == PackageManager.PERMISSION_DENIED) {
showSettingsDialog(this)
} else if (requestCode == PERMISSION_REQUEST_ACTIVITY_RECOGNITION &&
permissions.contains(Manifest.permission.ACTIVITY_RECOGNITION) &&
grantResults.size == 1 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d("permission_result", "permission granted")
isTrackingStarted = true
}
Приведенный выше код предоставляет разрешение, если выполняются следующие условия:
- Код запроса соответствует запросу.
- Разрешения содержат Manifest.permission.ACTIVITY_RECOGNITION.
- grantResults содержит PackageManager.PERMISSION_GRANTED.
Сделайте сборку и запустите. Сначала нажмите START. Затем дважды откажитесь от разрешения и обратите внимание на экран:
После отказа в разрешении во второй раз появляется диалоговое окно с просьбой предоставить пользователю разрешение в настройках.
Теперь переустановите приложение. Снова нажмите START, но на этот раз разрешите. Взгляните на logcat, и вы увидите:
com.raywenderlich.android.petbuddy D/permission_result: permission granted
Поздравляю! Вы готовы приступить к реализации функций Activity Recognition Client.
В следующем разделе вы узнаете больше о типах действий, поддерживаемых Activity Recognition Client.
Исследуем действия
Прежде чем продолжить разработку Pet Buddy, ознакомьтесь со списком поддерживаемых действий в Transition API:
- STILL: Когда датчики устройства не обнаруживают движения, например, когда устройство находится на столе.
- WALKING: Разновидность действия ON_FOOT, определяется, когда пользователь ходит с устройством.
- RUNNING: Разновидность действия ON_FOOT, определяется, когда пользователь бегает с устройством.
- ON_BICYCLE: Действие определяется, когда пользователь катается на велосипеде.
- IN_VEHICLE: Действие определяется, когда датчики устройства обнаруживают движение с более высокой скоростью, например, когда пользователь находится в машине.
Sampling API также поддерживает следующие действия:
- ON_FOOT: Датчики устройства обнаруживают движение с нормальной скоростью, когда пользователь идет или бежит.
- TILTING: Изменился угол наклона устройства относительно вектора силы тяжести.
- UNKNOWN: Когда устройство не может обнаружить или определить какие-либо действия.
Но есть еще одно различие между этими двумя API, помимо поддерживаемых ими действий. В то время как Transition API возвращает список событий, например WALKING - EXIT и STILL - ENTER, Sampling API возвращает список всех действий, отсортированных по наиболее вероятному (надежному) состоянию.
В следующем разделе вы узнаете, как задействовать все датчики и отслеживать переходы. Подготовьте обувь и своего питомца к прогулке!
Определение начала и конца действия
Ваш питомец взволнован, но попросите его подождать еще немного. Вы собираетесь начать работу с Transition API.
Прежде чем вы сможете использовать компоненты Activity Recognition Client, вы должны добавить зависимость. Откройте приложение build.gradle и замените строку, содержащую TODO 5 (эта строка закомментирована) на эту строку:
implementation 'com.google.android.gms:play-services-location:18.0.0'
Теперь синхронизируйте gradle. Вуаля, теперь ваше приложение может получить доступ к компонентам Activity Recognition Client.
В Transition API вы решаете, какие переходы вы хотите, чтобы ваше приложение обнаруживало. Например, предположим, что вы создаете приложение для обмена сообщениями, которое устанавливает для вас статус "занято", когда вы находитесь за рулем. В этом случае вам нужно только подписаться на переходы ENTER и EXIT действий IN_VEHICLE, создав экземпляр ActivityTransitionRequest.
Но ваш питомец не в восторге от поездок на машине. Итак, Pet Buddy будет отслеживать только:
- STILL
- WALKING
- RUNNING
Теперь, когда у вас есть доступ к компонентам Activity Recognition Client, пора приступить к работе.
Откройте SupportedActivity.kt. Затем найдите строку TODO 6 и раскомментируйте ниже в коде функцию в companion object. Теперь companion object выглядит так:
companion object {
fun fromActivityType(type: Int): SupportedActivity = when (type) {
DetectedActivity.STILL -> STILL
DetectedActivity.WALKING -> WALKING
DetectedActivity.RUNNING -> RUNNING
else -> throw IllegalArgumentException("activity $type not supported")
}
}
Представление перечисления DetectedActivity содержит предопределенные текстовые и графические ресурсы.
Затем вы создадите ActivityTransitionRequest для поддерживаемых действий. Откройте TransitionHelper.kt и замените строку, содержащую TODO 7 на эту функцию:
private fun getActivitiesToTrack(): List =
// 1
mutableListOf()
.apply {
// 2
add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.STILL)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build())
add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.STILL)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build())
add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.WALKING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build())
add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.WALKING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build())
add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.RUNNING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build())
add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.RUNNING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build())
}
Данная функция выполняет следующее:
1. Создает список типов ActivityTransition.
2. Затем добавляет все типы активности, которые вы хотите отслеживать, с типами перехода ENTER и EXIT.
Не забудьте использовать комбинацию клавиш Option + Return на компьютере Mac или Alt + Enter на ПК, чтобы разрешить любые недостающие зависимости.
Теперь ваш ActivityTransitionRequest готов. Затем вы сделаете запрос.
Чтобы запросить Transition updates, вам необходимо использовать ActivityRecognitionClient. В TransitionHelper.kt найдите строку, содержащую
TODO 8 и замените её на фрагмент кода:
val request = ActivityTransitionRequest(getActivitiesToTrack())
val task = ActivityRecognitionClient(this).requestActivityTransitionUpdates(request,
TransitionsReceiver.getPendingIntent(this))
task.run {
addOnSuccessListener {
Log.d("TransitionUpdate", getString(R.string.transition_update_request_success))
}
addOnFailureListener {
Log.d("TransitionUpdate", getString(R.string.transition_update_request_failed))
}
}
Затем добавьте следующий импорт:
import com.google.android.gms.location.ActivityRecognitionClient
Чтобы запустить обновления перехода, вам потребуются ActivityTransitionRequest и PendingIntent.
В этом случае PendingIntent указывает на TransitionReceiver, реализацию BroadcastReceiver.
Чтобы сэкономить заряд батареи пользователя, вам нужно перестать отслеживать обновления. В том же классе найдите строку, содержащую TODO 9 и замените её на фрагмент кода:
val task = ActivityRecognitionClient(this).removeActivityTransitionUpdates(
TransitionsReceiver.getPendingIntent(this))
task.run {
addOnSuccessListener {
Log.d("TransitionUpdate", getString(R.string.transition_update_remove_success))
}
addOnFailureListener {
Log.d("TransitionUpdate", getString(R.string.transition_update_remove_failed))
}
}
Этот код перестает прослушивать обновления.
Сделайте сборку и запустите. Поднимите logcat и коснитесь START. Вы увидите следующий лог:
com.raywenderlich.android.petbuddy D/TransitionUpdate: Transition updates requested
Теперь нажмите STOP. Прослушивание остановится, и вы увидите следующий лог:
com.raywenderlich.android.petbuddy D/TransitionUpdate: Transition updates removed
Теперь Pet Buddy готов получать обновления о ваших действиях. Затем вы извлечете эти обновления и покажете их в MainActivity.kt.
Отслеживание переходов активности
Сначала откройте TransitionsReceiver.kt и замените строку, содержащую TODO 10 на фрагмент кода:
// 1
if (ActivityTransitionResult.hasResult(intent)) {
// 2
val result = ActivityTransitionResult.extractResult(intent)
result?.let { handleTransitionEvents(it.transitionEvents) }
}
В коде будет ошибка, которую вы устраните на следующем шаге. Теперь замените строку, содержащую TODO 11 на фрагмент кода:
private fun handleTransitionEvents(transitionEvents: List) {
transitionEvents
// 3
.filter { it.transitionType == ActivityTransition.ACTIVITY_TRANSITION_ENTER }
// 4
.forEach { action?.invoke(SupportedActivity.fromActivityType(it.activityType)) }
}
Используйте упомянутые выше комбинации клавиш для устранения любых отсутствующих импортов или зависимостей.
Объяснение кода:
1. ActivityRecognitionClient предоставляет ActivityTransitionResult. Result подтверждает, что намерение имеет правильный тип.
2. Он также предоставляет функцию для извлечения ActivityTransitionResult из намерения.
3. В handleTransitionsEvents вы фильтруете только события ACTIVITY_TRANSITION_ENTER.
4. Затем вы вызываете действие с переходом ввода действия, сопоставленным с SupportedActivity.
Вы почти готовы отображать обновления перехода на главном экране.
В MainActivity.kt найдите строку, содержащую TODO 12 и замените её на фрагмент кода:
registerReceiver(transitionBroadcastReceiver, IntentFilter(TRANSITIONS_RECEIVER_ACTION))
Затем замените строку, содержащую TODO 13 на фрагмент кода:
unregisterReceiver(transitionBroadcastReceiver)
Теперь MainActivity.kt готов к приему переходов. Найдите строку, содержащую TODO 14 и замените её на фрагмент кода:
activityImage.setImageDrawable(ContextCompat.getDrawable(this, supportedActivity.activityImage))
activityTitle.text = getString(supportedActivity.activityText)
Теперь ваше приложение готово к использованию! Ваш питомец еще готов к прогулке?
Сделайте сборку и запустите. Выведите своего питомца на прогулку. Начните отслеживать обновления перехода, и вы увидите:
Теперь вы можете отслеживать изменения перехода активности. Прежде чем приступить к реализации Sampling API, вы узнаете больше об оценках достоверности.
Заметка
Если вы не получаете никаких результатов, наберитесь терпения. Попробуйте встряхнуть телефон или выйдите на улицу и прогуляйтесь несколько минут, чтобы разбудить датчики.
Оценка достоверности
Как вы читали ранее, Transition API всегда выполняет внутренние вычисления. Как только система будет уверена, что вы выполняете какое-то действие, вы получите сообщение.
API Sampling немного отличается. Здесь вы получаете список возможных действий с оценками достоверности. Оценка достоверности для всех возможных действий варьируется от нуля до 100.
Вам необходимо установить оценку достоверности, которая вам подходит. Например, вы можете считать действие подтвержденным, если оценка достоверности выше 75. Но есть некоторые случаи, когда многие похожие действия могут иметь высокую степень оценки.
В этом случае датчики не могут различить два близких действия. Например, если вы бежите достаточно медленно, ходьба (WALKING) и бег (RUNNING) будут иметь высокую оценку, очень близкую друг к другу. Но если вы ведете машину, ходьба (WALKING) будет оцениваться на очень низком уровне.
В следующем разделе вы узнаете, как реализовать Sampling API и использовать его, пока ваше приложение работает в фоновом режиме.
Отслеживание действий в фоновом режиме
Приложения, отслеживающие вашу активность, должны работать в фоновом режиме, чтобы вы по-прежнему могли использовать другие приложения на своем мобильном устройстве. В этом разделе вы узнаете, как использовать Sampling API и запускать его в background service.
Помните, как вы запрашивали обновления перехода от ActivityRecognitionClient? Запрос обновлений выборки действий - это почти такой же процесс.
Найдите DetectedActivityService.kt. Затем замените строку, содержащую TODO 15 на фрагмент кода:
val task = ActivityRecognitionClient(this).requestActivityUpdates(ACTIVITY_UPDATES_INTERVAL,
DetectedActivityReceiver.getPendingIntent(this))
task.run {
addOnSuccessListener {
Log.d("ActivityUpdate", getString(R.string.activity_update_request_success))
}
addOnFailureListener {
Log.d("ActivityUpdate", getString(R.string.activity_update_request_failed))
}
}
В приведенном выше коде вы можете видеть, что процесс почти такой же, как запрос обновлений перехода. Вы создаете задачу и применяете к ней слушателей действий (событий). Разница в том, что теперь вы вызываете requestActivityUpdates, который принимает в качестве параметров detectionInterval и ожидаемое намерение действия и обновляет PendingIntent получателя.
Теперь ActivityRecognitionClient будет отправлять периодические обновления вашему приложению.
Затем добавьте код, чтобы остановить обновления, если пользователь закроет приложение. Найдите строку, содержащую TODO 16 и замените её на фрагмент кода:
val task = ActivityRecognitionClient(this).removeActivityUpdates(
DetectedActivityReceiver.getPendingIntent(this))
task.run {
addOnSuccessListener {
Log.d("ActivityUpdate", getString(R.string.activity_update_remove_success))
}
addOnFailureListener {
Log.d("ActivityUpdate", getString(R.string.activity_update_remove_failed))
}
}
Этот код удаляет все обновления активности, указанные для PendingIntent. Это также помогает контролировать расход заряда батареи.
Теперь вы зарегистрируете службу и широковещательный приемник в AndroidManifest.xml.
Откройте файл AndroidManifest.xml. Найдите строку, содержащую TODO 17 и замените её на фрагмент кода:
Этот код позволяет вашей службе и приемнику работать в фоновом режиме.
Теперь переключитесь на DetectedActivityReceiver.kt. Здесь вы будете обрабатывать сообщения, полученные от ActivityRecognitionClient.
Сначала найдите строку, содержащую TODO 18 и раскомментируйте функции handleDetectedActivities и showNotification. Затем добавьте импорт:
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.google.android.gms.location.DetectedActivity
import com.raywenderlich.android.petbuddy.MainActivity
import com.raywenderlich.android.petbuddy.R
import com.raywenderlich.android.petbuddy.SUPPORTED_ACTIVITY_KEY
import com.raywenderlich.android.petbuddy.SupportedActivity
Теперь вы обработаете намерение, полученное от ActivityRecognitionClient, с помощью тех же шагов, которые вы использовали для обновлений перехода. Найдите строку, содержащую TODO 19 и замените её на фрагмент кода:
// 1
if (ActivityRecognitionResult.hasResult(intent)) {
// 2
val result = ActivityRecognitionResult.extractResult(intent)
// 3
result?.let { handleDetectedActivities(it.probableActivities, context) }
}
Логика этого кода:
1. Проверяет, содержит ли полученное намерение желаемый результат.
2. Затем извлекает ActivityRecognitionResult из намерения (intent).
3. Если результат не равен нулю, он обрабатывает возможные действия.
probableActivites - это отсортированный список действий, основанный на оценке достоверности. Поскольку ваш питомец любит гулять и бегать, вы не хотите отслеживать другие действия. Ладно, тебе тоже стоит подумать о том, чтобы стоять на месте.
В DetectedActivityReceiver.kt найдите строку, содержащую TODO 20. Замените эту строку на фрагмент кода:
detectedActivities
.filter {
it.type == DetectedActivity.STILL ||
it.type == DetectedActivity.WALKING ||
it.type == DetectedActivity.RUNNING
}
.filter { it.confidence > RELIABLE_CONFIDENCE }
.run {
if (isNotEmpty()) {
showNotification(this[0], context)
}
}
Сначала вы фильтруете действия, которые вам интересны. Затем вы фильтруете действия с оценкой достоверности более 75%, потому что вас интересует только наиболее вероятная деятельность.
Вуаля! Уведомление здесь.
Когда вы запрашивали разрешение, вы регистрировали сообщение о предоставленном разрешении. В MainActivity.kt найдите строку, содержащую
TODO 21. В ветке else над ним добавьте:
startService(Intent(this, DetectedActivityService::class.java))
requestActivityTransitionUpdates()
Когда пользователь нажимает START, отображается диалоговое окно разрешений. Когда пользователь дает разрешение, он начинает отслеживать действия.
Отличная работа! Вы готовы использовать Sampling API.
Сделайте сборку и запустите. Выйдите на улицу, начните отслеживать и обратите внимание на строку состояния. Вы увидите такое уведомление:
Поздравляю! Вы создали свое первое приложение для отслеживания активности. Выведите собаку на прогулку и насладитесь природой.
В этом руководстве вы узнали, зачем и как использовать Activity Recognition API. Вы использовали Activity Recognition Client, чтобы определять начало и конец действия и отслеживать переходы между действиями. Вы также узнали об оценках достоверности и отслеживании действий в фоновом режиме.