Что такое pendingintent android

Всё о PendingIntents

PendingIntent являются важной частью фреймворка Android, но большинство доступных ресурсов для разработчиков сосредоточены на деталях их имплементации — «ссылка на токен, поддерживаемый системой» — а не на их использовании.

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

Что такое PendingIntent?

Объект PendingIntent оборачивает функциональность объекта Intent , позволяя вашему приложению указать, что другое приложение должно сделать от вашего имени в ответ на будущее действие. Например, обернутое намерение может быть вызвано при срабатывании будильника или когда пользователь нажимает на уведомление.

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

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

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

Распространенный случай

Самый распространенный и наиболее простой способ использования PendingIntent — это действие, связанное с уведомлением:

Мы видим, что создаем стандартный тип Intent , который будет открывать наше приложение, а затем просто оборачиваем его в PendingIntent , прежде чем добавить его в наше уведомление.

В данном случае, поскольку у нас есть точное действие, которое необходимо выполнить, то создаем PendingIntent , не подлежащий изменению приложением, куда мы его передаем, используя флаг FLAG_IMMUTABLE.

После вызова NotificationManagerCompat.notify() все готово. Система отобразит уведомление и, когда пользователь нажмет на него, вызовет PendingIntent.send() для нашего PendingIntent , запуская наше приложение.

Обновление неизменяемого PendingIntent

Вы можете подумать, что если приложению нужно обновить PendingIntent , то он должен быть изменяемым, но это не всегда так! Приложение, создающее PendingIntent , всегда может обновить его, передав флаг FLAG_UPDATE_CURRENT:

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

Технология Inter-app APIs

Распространенный случай полезен не только для взаимодействия с системой. Хотя для получения обратного вызова после выполнения действия чаще всего используются startActivityForResult() и onActivityResult(), это не единственный способ.

Представьте себе приложение для онлайн-заказа, которое предоставляет API для интеграции с ним приложений. Оно может принять PendingIntent как extra к своему собственному Intent , которое используется для запуска процесса заказа еды. Приложение заказа запускает PendingIntent только после того, как заказ будет доставлен.

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

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

Изменяемые PendingIntents

Что если мы будем разработчиками приложения для заказа и захотим добавить функцию, позволяющую пользователю набрать сообщение, отправляемое обратно в вызывающее приложение? Возможно, чтобы вызывающее приложение могло показать что-то вроде: «Сейчас время PIZZA!».

Ответом на этот вопрос является использование изменяемого PendingIntent .

Поскольку PendingIntent — это, по сути, обертка вокруг Intent , можно подумать, что существует метод PendingIntent.getIntent() , который можно вызвать для получения и обновления обернутого Intent , но это не так. Так как же это работает?

Читайте также:  Nokia android будет обновляться

Помимо метода send() в PendingIntent , который не принимает никаких параметров, есть несколько других версий, включая эту, которая принимает Intent :

Этот параметр intent не заменяет Intent , содержащийся в PendingIntent , а скорее используется для заполнения параметров из обернутого Intent , которые не были предоставлены при создании PendingIntent .

Давайте рассмотрим пример.

Этот PendingIntent может быть передан нашему приложению онлайн-заказа. После завершения доставки приложение заказа может принять сообщение customerMessage и отправить его обратно в качестве дополнительного намерения, как это сделано ниже:

Тогда вызывающее приложение увидит дополнительное EXTRA_CUSTOMER_MESSAGE в своем Intent и сможет отобразить сообщение.

Важные соображения при объявлении изменяемости отложенного намерения (pending intent)

При создании изменяемого PendingIntent ВСЕГДА явно задавайте компонент, который будет запущен в этом Intent . Это можно реализовать так, как мы сделали выше, явно задав точный класс, который будет его получать, либо с помощью вызова Intent.setComponent().

В вашем приложении может быть такой случай, когда проще вызвать Intent.setPackage() . Будьте очень осторожны с возможностью сопоставления нескольких компонентов, если вы сделаете это. Лучше указать конкретный компонент для получения Intent , если это вообще возможно.

Если вы попытаетесь переопределить значения в PendingIntent , который был создан с FLAG_IMMUTABLE , произойдет тихий сбой, и исходный обернутый Intent будет передан без изменений.

Помните, что приложение всегда может обновить свой собственный PendingIntent , даже если они неизменяемы. Единственная причина сделать PendingIntent изменяемым — если другое приложение будет иметь возможность каким-то образом обновить обернутый Intent .

Подробности о флагах

Мы немного рассказали о нескольких флагах, которые можно использовать при создании PendingIntent , но есть и другие, заслуживающие внимания.

FLAG_IMMUTABLE: Указывает, что Intent внутри PendingIntent не может быть изменен другими приложениями, которые передают Intent в PendingIntent.send() . Приложение всегда может использовать FLAG_UPDATE_CURRENT для изменения своих собственных PendingIntent .

До Android 12 PendingIntent , созданный без этого флага, по умолчанию был изменяемым.

В версиях Android до Android 6 (API 23) PendingIntents всегда изменяемы.

FLAG_MUTABLE: Указывает, что Intent внутри PendingIntent должен позволять приложению обновлять его содержимое путем объединения значений из параметра намерения PendingIntent.send() .

Всегда заполняйте ComponentName обернутого Intent любого изменяемого PendingIntent . Невыполнение этого требования может привести к уязвимостям в системе безопасности!

Этот флаг был добавлен в Android 12. До Android 12 любые PendingIntents , созданные без флага FLAG_IMMUTABLE , были неявно изменяемыми.

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

FLAG_ONE_SHOT: Позволяет отправить PendingIntent только один раз (через PendingIntent.send() ). Это может быть важно при передаче PendingIntent другому приложению, если содержащийся в нем Intent может быть отправлен только один раз. Такое требование обусловлено удобством или необходимостью предотвратить многократное выполнение приложением какого-либо действия.

Использование FLAG_ONE_SHOT предотвращает такие проблемы, как «атаки повторного воспроизведения (replay attacks)«.

FLAG_CANCEL_CURRENT: Отменяет текущий PendingIntent , если он уже существует, перед регистрацией нового. Это может быть важно, если определенный PendingIntent был отправлен одному приложению, а вы хотите отправить его другому приложению, потенциально обновляя данные. Используя FLAG_CANCEL_CURRENT , первое приложение больше не сможет вызвать отправку, но второе приложение сможет.

Получение PendingIntents

Иногда система или другие фреймворки предоставляют PendingIntent как ответ на вызов API. Одним из примеров является метод MediaStore.createWriteRequest(), который был добавлен в Android 11.

Резюме

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

Мы также говорили о том, что PendingIntents обычно должен быть неизменяемым и что это не мешает приложению обновлять свои собственные объекты PendingIntent . Это можно сделать, используя флаг FLAG_UPDATE_CURRENT в дополнение к FLAG_IMMUTABLE .

Мы также говорили о мерах предосторожности, которые необходимо предпринять — заполнить ComponentName обернутого Intent — если PendingIntent должен быть изменяемым.

Читайте также:  Софт авто для андроида

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

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

Хотите еще больше? Мы призываем вас протестировать свои приложения на новой предварительной версии ОС для разработчиков и поделиться с нами своими впечатлениями!

Перевод материала подготовлен в рамках курса «Android Developer. Basic». Если вам интересно узнать о курсе подробнее, приходите на день открытых дверей онлайн, где преподаватель расскажет о формате и программе обучения.

Источник

Полный список

— получаем из сервиса результат с помощью PendingIntent

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

Я особо не буду вдаваться в объяснение, что такое PendingIntent и что он умеет — урок не о нем. Я покажу, как с его помощью можно в Activity получать результаты работы сервиса.

— в Activity создаем PendingIntent с помощью метода createPendingResult

— кладем этот PendingIntent в обычный Intent, который используем для старта сервиса и вызываем startService

— в сервисе извлекаем PendingIntent из полученного в методе onStartCommand объекта Intent

— когда нам необходимо передать результаты работы из сервиса в Activity, вызываем метод send для объекта PendingIntent

— эти результаты из сервиса ловим в Activity в методе onActivityResult

Т.е. фишка PendingIntent здесь в том, что он содержит некую связь с Activity (в котором он был создан) и, когда вызывается метод send, он идет в это Activity и несет данные, если необходимо. В общем, этакий почтовый голубь, который точно знает, как ему вернуться домой.

Схема в целом несложная. Попробуем нарисовать пример по ней. У нас будет приложение, которое будет отправлять в сервис на выполнение три задачи. А сервис будет информировать, когда он начал каждую задачу выполнять, когда закончил и с каким результатом. Все это будем выводить на экран Activity.

Кстати, чтобы легче было воспринимать это все, замечу, что алгоритм очень похож на работу startActivityForResult. Только там мы взаимодействуем не с сервисом, а с Activity. Если подзабыли, посмотрите уроки 29 и 30. Воспринимать текущий материал будет гораздо легче.

Project name: P0951_ServiceBackPendingIntent
Build Target: Android 2.3.3
Application name: ServiceBackPendingIntent
Package name: ru.startandroid.develop.p0951servicebackpendingintent
Create Activity: MainActivity

Добавим в strings.xml строки:

Три TextView, в которые будем выводить инфу, поступающую из сервиса. И кнопка старта сервиса.

Создаем класс для сервиса — MyService.java. И пропишем его в манифесте. Пока в нем ничего не кодим.

В onCreate мы находим TextView и присваиваем им первоначальный текст. Для каждой задачи свой TextView.

В onClickStart мы создаем PendingIntent методом createPendingResult. На вход методу передаем только код запроса – можно считать это идентификатором. По этому коду мы потом будем определять, какая именно задача вернула ответ из сервиса. Два остальных параметра – это Intent и флаги. Нам они сейчас не нужны, передаем соответственно null и 0. (Выяснилось, что в 4-й версии Андроид не прокатывает использование null вместо Intent. Поэтому можно создать пустой Intent и использовать его.)

Далее создаем Intent для вызова сервиса MyService, помещаем туда параметр времени (который будем использовать для паузы в сервисе) и PendingIntent. После чего, отправляем это все в сервис.

Аналогичные действия производим для Task2 и Task3.

В onActivityResult проверяем, какой тип сообщения пришел из сервиса.

Если о том, что задача начала работу (STATUS_START), то определяем, какая именно задача, и пишем об этом в соответствующий TextView.

Читайте также:  Мои восточные ночи для андроид

А если о том, что задача завершена (STATUS_FINISH), то читаем из Intent-а результат выполнения, определяем, какая именно задача, и пишем инфу об этом в соответствующий TextView.

Всю инфу о поступающих сообщениях пишем в лог.

Как вы понимаете, коды STATUS_START и STATUS_FINISH, а также результат мы сейчас будем формировать в сервисе.

Кодим сервис MyService.java:

Мы снова используем знакомую по прошлым урокам схему экзекьютора и Runnable.

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

В onStartCommand вытаскиваем из Intent-а параметр времени для паузы и PendingIntent. Создаем MyRun и передаем ему эти данные. Передаем MyRun экзекьютору на выполнение.

MyRun при запуске вызывает метод send для PendingIntent и передает туда тип сообщения STATUS_START. Это приходит в метод onActivityResult в Activity и на экране мы увидим, как в одном из TextView появится текст, что задача начала работать.

Далее мы эмулируем работу, как обычно, просто поставив паузу. А после этого создаем Intent с результатом работы (просто время * 100), и вызываем немного другую реализацию метода send. Кроме типа сообщения (STATUS_FINISH), мы передаем туда Intent с результатом и указываем контекст. Это идет в метод onActivityResult в Activity и на экране мы увидим, как в одном из TextView появится текст, что задача закончила работу с неким результатом.

Далее вызываем метод stop, в котором вызывается метод stopSelfResult.

Все сохраняем и запускаем приложение.

Видим, что две задачи начали работать, т.к. экзекьютор настроен на два потока.

Одна задача завершилась и показала результат, поток освободился, стартует оставшаяся задача.

Еще одна задача завершилась.

Смотрим логи (у вас может быть немного другая последовательность записей в логах):

MyService onCreate
MyService onStartCommand
MyRun#1 create
MyService onStartCommand
MyRun#2 create
MyRun#1 start, time = 7
MyService onStartCommand
MyRun#3 create

Сервис создался и получил все три вызова.

requestCode = 1, resultCode = 100

MyRun1 начал работать и отправил сообщение об этом: requestCode = TASK1_CODE, resultCode = STATUS_START.

MyRun#2 start, time = 4
requestCode = 2, resultCode = 100

MyRun2 начал работать и отправил сообщение об этом: requestCode = TASK2_CODE, resultCode = STATUS_START.

MyRun#2 end, stopSelfResult(2) = false
MyRun#3 start, time = 6
requestCode = 2, resultCode = 200
requestCode = 3, resultCode = 100

MyRun2 завершил работу и отправил соответствующее сообщение: requestCode = TASK2_CODE, resultCode = STATUS_FINISH. Поток в экзекьюторе освободился и стартует MyRun3. MyRun3 начал работать и отправил сообщение об этом: requestCode = TASK3_CODE, resultCode = STATUS_START.

MyRun#1 end, stopSelfResult(1) = false
requestCode = 1, resultCode = 200

MyRun1 завершил работу и отправил соответствующее сообщение: requestCode = TASK1_CODE, resultCode = STATUS_FINISH.

requestCode = 3, resultCode = 200
MyRun#3 end, stopSelfResult(3) = true

MyRun3 завершил работу и отправил соответствующее сообщение: requestCode = TASK3_CODE, resultCode = STATUS_FINISH.

Последний поступивший вызов выполнил метод stopSelfResult, сервис останавливается.

Изначально хотел пример попроще сделать, но чет увлекся, и получилось посложнее, но и поинтереснее.

Еще раз проговорю, что мы здесь поверхностно использовали PendingIntent и не стали копать его подробно, т.к. урок не о нем. Скоро мы еще раз встретимся с этим объектом, когда будем изучать уведомления (Notifications).

На следующем уроке:

— получаем из сервиса результат с помощью BroadcastReceiver

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

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