Намерения и фильтры намерений

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

для запуска явлений:
Явления (объект типа Activity) представляют отдельный экран приложения. Вы можете запустить новый экземпляр явления, передав объект Intent в метод startActivity(). Объект Intent описывает явление для запуска и другие необходимые данные.

Если вы хотите получить результат выполнения явления, используйте метод startActivityForResult(). Ваше явление получит результат в виде другого объекта типа Intent, который будет передан в метод обратного вызова onActivityResult(). Подробная информация содержится в разделе Явления.
для запуска сервисов:
Сервис (объект типа Service) это компонент, который выполняет работу в фоновом режиме и не предоставляет пользовательский интерфейс. Вы можете запустить сервис для выполнения одноразовой операции (вроде загрузки файла), передав объект Intent в метод startService(). Намерение описывает сервис для запуска и другие необходимые данные. Подробная информация содержится в разделе Сервисы.
для передачи широковещательных сообщений:
Широковещательные сообщения могут быть получены любым приложением. Система передает различные сообщения о системных событиях, например при загрузке устройства или при подключении зарядного устройства. Вы можете передавать широковещательные сообщения другим приложениям, передав объект Intent в метод sendBroadcast(), sendOrderedBroadcast() или sendStickyBroadcast().

Типы намерений

Есть два типа намерений:

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

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

Рисунок 1. на рисунке показано, как неявные намерения проходят через систему для запуска других явлений: [1] Явление А создает намерение и передает его в метод startActivity(). [2] Система Android ищет приложения, которые имеют фильтры, совпадающие с переданным намерением и если находит, [3] система запускает явление В с помощью вызова onCreate() и передав в него намерение.

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

Фильтры намерений – это выражения в файле манифеста, который указывают на типы намерений, которые компонент может получать. Например, создав фильтры намерений для явления, вы даете другим приложениям возможность напрямую запустить это явление с помощью соответствующего намерения. Кроме того, если вы не объявите ни одного фильтра, явление сможет быть запущено только с использованием явного намерения.

Внимание: чтобы гарантировать безопасность приложения, всегда используйте явные намерения для запуска сервисов и не объявляйте для сервисов фильтры. Использование неявных намерений для запуска сервисов небезопасно, поскольку вы не сможете быть уверены, что сервис реагирует на намерения, а пользователь не сможет увидеть какой сервис запущен. Начиная с Android 5.0 (API 21), система выбрасывает исключение при передаче в метод bindService() неявного намерения.

Создание намерения

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

Вот основная информация в намерениях:

Имя компонента

Имя компонента, который должен быть запущен.

Это необязательная, но очень критичная часть информации, которая делает намерение явным, подразумевая, что намерение должно быть передано только компоненту с указанным именем. Если имя компонента не указано, намерение считается неявным и система решает, какой компонент должен получить это намерение. Решение принимается на основе информации, которую несет намерение (действие, данные и категория – о них написано ниже). Если вам нужно запустить конкретный компонент вашего приложения, вы должны указать его имя.

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

Это поле намерения является объектом типа ComponentName, в котором может быть указано полное имя класса компонента, включая название пакета. Например, com.example.ExampleActivity. Вы можете установить имя компонента с помощью методов setComponent(), setClass(), setClassName() или используя конструктор класса Intent.

Действие

Строка, которая указывает общее действие для выполнения (например “показать” или “выбрать”)

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

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

ACTION_VIEW
Используется для получения информации, которая может быть отображена явлением, например фотографии или точка на карте.
ACTION_SEND
Также известное как намерение “share” (поделиться), которое вы можете использовать для того, чтобы поделиться некоторыми данными через другое приложение, например добавить вложение в почтовом клиенте или фотографию в приложении социальной сети.

Остальные константы действий смотрите в документации по классу Intent. Есть также действия, которые описаны в других классах. Например класс Settings содержит описание действий для открывания конкретных страниц приложения Настройки.

Вы можете указать действие для намерения с помощью метода setAction() или в конструкторе.

Если вы описываете собственные действия, убедитесь, что добавили в качестве префикса название пакета, например:

Данные

URI (объект типа Uri) ссылается на данные, которые должны быть обработаны, и на MIME тип этих данных. Тип предоставленных данных, как правило, зависит от указанного действия. Например, если указано действие ACTION_EDIT, данные должны содержать URI документа для редактирования.

При создании намерения часто требуется кроме URI указать также тип данных. Например, явление, которое может показать картинку, вероятно не способно воспроизвести звуковой файл, даже если указать похожий URI. Указание MIME типа помогает системе Android искать подходящие компоненты для передачи в них намерения. Однако, иногда можно сделать вывод о MIME типе из URI. В частности, если предоставлен URI типа content:, который указывает, что данные находятся на устройстве и управляются поставщиком содержимого, это делает MIME тип видимым для системы.

Чтобы задать только данные, используйте метод setData(). Чтобы установить тип, используйте метод setType(). Разумеется, можно установить и то и другое с помощью метода setDataAndType().

Внимание: если вы хотите установить и URI и MIME тип, не используйте по отдельности методы setData() и setType(), поскольку они затирают значение друг друга! Всегда в таких случаях используйте метод setDataAndType().

Категория

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

Вот наиболее распространенные категории:

CATEGORY_BROWSABLE
Явление позволяет запускать себя из web-браузера для того, чтобы показать данные, на которые указывает ссылка, например картинку или email-cообщение.
CATEGORY_LAUNCHER

Явление является стартовым для приложения и должно быть показано в списке приложений.

Полный список категорий смотрите в документации класса Intent.

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

Однако, намерения могут нести в себе дополнительную информацию, которая не влияет на выбор компонента для запуска:

Расширенные данные (extras)

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

Вы можете добавить расширенные данные с помощью метода putExtra(), который принимает два параметра: название ключа и значение. Вы можете использовать объект типа Bundle, который содержит все данные, и добавить его к намерению с помощью метода putExtras().

Например, при создании намерения с действием ACTION_SEND для отправки письма, вы можете указать получателя, используя ключ EXTRA_EMAIL и тему, используя ключ EXTRA_SUBJECT.

Класс Intent содержит множество констант EXTRA_* для стандартных типов данных. Если вам нужен собственный ключ (для намерений вашего приложения), убедитесь, что добавили к его имени префикс с названием пакета:

Флаги

Флаги определены в классе Intent и нужны в качестве мета-данных для намерений. Флаги могут указывать системе Android как запускать явление (например, какая задача должна относиться к явлению) и как к нему относиться после запуска (например, нужно ли помещать его в список последних приложений).

Подробную информацию смотрите в документации к методу setFlags().

Пример явного намерения

Явное намерение это компонент, который вы используете для запуска конкретного компонента приложения, вроде явления или сервиса вашего приложения. Для создания явного намерение, укажите имя компонента для объекта типа Intent – остальные параметры необязательны.

Например, если вы создали сервис для приложения, который называется DownloadService и служит для скачивания файла из сети, вы можете запустить его с помощью следующего кода:

В данном примере используется конструктор Intent(Context, Class), который принимает в качестве параметров контекст (объект типа Context) приложения или объект типа Class. Таким образом, данное намерение запускает класс DownloadService в приложении.

Подробная информация содержится в разделе Сервисы.

Пример неявного намерения

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

Например, у вас есть данные, которым пользователь может поделиться с другими людьми. Вы создаете неявное намерение с действие ACTION_SEND и добавляете расширенные данные, указывающие на содержимое, которое должно быть расшарено. После вызова метода startActivity() пользователь сможет выбрать приложение, которое разместит общие данные.

Внимание: возможно на устройстве нет приложений, которые могут обработать неявное намерение, которое вы передали. В таких случаях приложение аварийно завершится. Поэтому необходимо использовать метод resolveActivity(), чтобы убедиться, что есть по крайней мере одно явление, которое может обработать указанное намерение. Если метод вернет значение, отличное от null, можете смело вызывать метод startActivity(). Если результат равен null, вы не должны использовать данное намерение и можете даже запретить функцию, которая его использует.

Примечание: в данном случае URI не используется, но объявлен тип расширенных данных.

При вызове startActivity(), система проверяет все установленные приложения, чтобы определить кто может обработать намерение. Если есть только одно приложение, оно будет немедленно запущено, иначе будет выведен диалог, в котором приложение должен выбрать пользователь.

Рисунок 2.Диалог выбора без флажка “по умолчанию”.

Обязательное использование диалога выбора приложения

Если существует более одного приложения, способного обработать намерение, выводится диалог для выбора приложения и пользователь может указать какое приложение использовать по умолчанию в таких случаях. Это удобно. Однако, есть ситуации, в которых пользователь может пожелать выбирать разные приложения в разное время. В данном случается используется диалог выбора, в котором нельзя установить флажок “использовать по умолчанию”. Например, если приложение использует действие “расшаривания” данных с помощью ACTION_SEND, пользователь может выбирать разные приложения в зависимости от ситуации. Пример на рисунке 2.

Чтобы показать диалог выбора, создайте объект Intent с помощью метод createChooser() и передайте его в метод startActivity():

Этот код выведет диалог выбора со списком приложений, которые откликнутся на намерение, переданное в метод createChooser().

Получение неявного намерения

Чтобы указать, какие намерения могут принимать компоненты вашего приложения, объявите один или несколько фильтров намерений для каждого из компонентов, используя элемент <intent-filter> в файле манифеста. Каждый фильтр указывает тип намерения на основе действия, данных и категории. Система отправляет неявное намерение компоненту вашего приложения, только если оно пройдет через один из фильтров.

Примечание: явные намерения всегда доставляются целевому компоненту, независимо от того, какие фильтры для него прописаны.

Компоненты должны содержать отдельные фильтры для каждого из действий. Например, явление галереи изображений может иметь два фильтра: один для отображения картинки, а второй для ее редактирования. При запуске этого явления поведение будет выбрано в зависимости от данных, содержащихся в объекте Intent.

Каждый фильтр намерения описывается с помощью тега <intent-filter> в файле манифеста, внутри соответствующего компонента. Внутри тега <intent-filter> вы можете указать тип намерения, используя один или несколько следующих элементов:

<action>
Описывает в атрибуте name действие, которое может быть обработано. Это строковое значение действия, а не константа класса!
<data>
Описывает тип принимаемых данных, используя один или более атрибутов для различных типов URI (scheme, host, path, и.т.д) и MIME типов.
<category>

Описывает категорию намерения в атрибуте name. Это строковое значение, а не константа класса!

Примечание: для того, чтобы принять неявное намерение, вы должны включить в фильтр категорию CATEGORY_DEFAULT.

Вот пример описания явления, включающего фильтры для получения намерения с действием ACTION_SEND и текстовыми данными:

Создавать фильтр, содержащий несколько элементов <action>, <data> или <category> это совершенно нормальная практика. Но если вы так делаете, убедитесь, что компонент может обработать любую из возможных комбинаций указанных элементов, включая ситуацию с одновременным использованием сразу всех элементов.

Запрещаем доступ к компонентам

Использование фильтров намерений для запрета запуска компонентов – это не безопасный способ. И хотя фильтры разрешают компонентам отвечать только на подходящие под описание неявные намерения, другое приложение может потенциально использовать явное намерение, если его разработчик узнает имя класса вашего компонента. Если важно, чтобы компонент мог быть запущен только из вашего приложения, установите для этого компонента атрибут exported в значение false.

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

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

Внимание: чтобы предотвратить случайный запуск сервисов других приложений, всегда используйте явное намерение для запуска ваших сервисов и не объявляйте фильтры намерений для ваших сервисов!

Примечание: вы должны указывать фильтры намерений для всех явлений в файле манифеста. Однако, фильтры для широковещательных приемников могут быть также зарегистрированы динамически с помощью метода registerReceiver(). Отменить регистрацию можно с помощью метода unregisterReceiver(). Разрешайте вашему приложению прослушивать сообщения только когда ваше приложение запущено!

Пример фильтров

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

Первое явление, MainActivity, это точка входа в приложение – явление открывается, когда пользователь щелкает по его значку в списке приложений:

  • Действие ACTION_MAIN показывает, что явление является главной точкой входа и не требует других данных от намерения.
  • Категория CATEGORY_LAUNCHER показывает, что иконка явления должна быть помещена в список приложений. Если элемент <activity> не содержит атрибут icon, будет использована иконка из элемента <application>.

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

Второе явление, ShareActivity, вызывается для расшаривания текста и медиа-файлов. Пользователи могут запустить это явление из MainActivity или напрямую из другого приложения, которое выдает неявное намерение, соответствующее одному из фильтров.

Примечание: MIME тип application/vng.google.panorama360+jpg это специальный тип для указания панорамных фотографий, с которыми можно работать посредством API Google panorama.

Использование отложенных намерений (pending intent)

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

Основные случаи использования отложенных намерений включают в себя:

  • Объявление намерения, которое должно быть выполнено, если пользователь выполняет действия с вашими уведомлениями – Notification (намерение выполняет системный NotificationManager).
  • Объявление намерения, которое должно быть выполнено, если пользователь выполняет действия с вашими виджетами (намерение выполняет домашний экран).
  • Объявление намерения, которое должно быть выполнено в указанное время в будущем (намерение выполняет системный AlarmManager).

Поскольку каждый объект Intent обрабатывается конкретным типом компонентов (будь то явление, сервис или широковещательный приемник), объекты PendingIntent создаются исходя из тех же соображений. Для запуска отложенных намерений на используется метод startActivity(). Вместо этого вы должны объявить тип компонента, вызвав соответствующий метод:

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

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

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

Механизм выбора компонента для обработки намерения

При получении неявного намерения на запуск явления, система выбирает наилучшее явление из доступных на основе трех аспектов:

  • Действие намерения
  • Данные намерения (URI и тип данных)
  • Категория намерения

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

Проверка действия

Для указания действия, фильтры могут включать несколько элементов <action> или не включать ни одного такого элемента. Пример:

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

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

Проверка категории

Для указания категории, фильтры могут включать несколько элементов <category> или не включать ни одного такого элемента. Пример:

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

Примечание: Android автоматически применяет категорию CATEGORY_DEFAULT для всех неявных намерений, переданных в методы startActivity() и startActivityForResult(). Если вы хотите, чтобы ваше явление принимало неявные намерения, вы должны добавить категорию "android.intent.category.DEFAULT" в фильтр.

Проверка данных

Для указания данных, фильтры могут включать несколько элементов <data> или не включать ни одного такого элемента. Пример:

Каждый элемент <data> может указывать структуру URI и тип данных. Есть отдельные атрибуты: scheme(схема), host(хост), port(порт) и path(путь) для каждой части URI:

Например:

В приведенном выше примере content – это схема, com.example.project – хост, 200 – порт и folder/subfolder/etc – путь.

Каждый из этих атрибутов не является обязательным для элемента <data>, но они зависят друг от друга следующим образом:

  • Если не указана схема, хост игнорируется.
  • Если не указан хост, игнорируется порт.
  • Путь игнорируется, если не указаны схема и хост.

При проверке URI, сравниваются только те его части, которые указаны в фильтре. Например:

  • Если в фильтре указана только схема, все URI с такой схемой пройдут проверку.
  • Если в фильтре указаны схема и authority, но не указан путь, все URI с такой схемой и authority пройдут проверку, независимо от их пути.
  • Если в фильтре указана схема, authority и путь, проверку пройдут только те URI, у которых одновременно совпадают все три элемента.

Примечание: путь может содержать звездочку (*), чтобы требовать частичного совпадения пути.

При проверке данных сравнивается как URI, так и MIME тип. Правила работают таким образом:

  • a. Намерение, которое не содержит ни URI, ни MIME тип, пройдет проверку, только если в фильтре не указаны идентификаторы URI или MIME.
  • b. Намерение, содержащее URI, но не содержащее MIME тип (ни явного, ни доступного из URI), пройдет проверку, только если в фильтре указан такой URI и не указан MIME тип.
  • c. Намерение, содержащее MIME тип, но не содержащее URI пройдет проверку, только если фильтр указывает на такой MIME тип и не указывает URI.
  • d. Намерение, содержащее и URI и MIME тип (явно или доступный из URI) пройдет проверку MIME типа только если такой тип содержится в фильтре. Проверку URI оно пройдет в одном из двух случаев:
    1. если URI намерения совпадает с URI, указанным в фильтре
    2. если намерение содержит URI типа content: или file:, а URI в фильтре не указан.
    Другими словами, предполагается, что компонент поддерживает данные типа content: и file:, если в фильтре указан только MIME тип.

Последнее правило (d), отражает ожидание того, что компоненты могут получить локальные данные из файла или поставщика содержимого. Таким образом, их фильтры могут только перечислять типы данных и нет необходимости явно указывать имя схема content: или file:. Это типичная ситуация. В примере ниже элемент <data> сообщает Android, что компонент может получать изображения из поставщика содержимого и отображать их:

Поскольку большинство имеющихся данных распределено между поставщиками содержимого, фильтры, которые определяют только тип данных (без указания URI), возможно самые распространенные.

Еще одна распространенная конфигурация с фильтрами, указывающими схему и тип данных. Например, элемент &ltdata> в следующем примере сообщает Android, что компонент для выполнения задачи может получить видео из сети:

Для чего еще нужна проверка намерений

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

Ваше приложение может использовать проверку намерений в похожих ситуациях. Класс PackageManager содержит набор методов query...(), которые возвращают все компоненты, способные принять определенное намерение, а также набор методов resolve...(), которые определяют лучший компонент для работы с данным намерением. Например, метод queryIntentActivities() возвращает список всех явлений, которые могут обработать переданное в качестве аргумента намерение, а метод queryIntentServices() вернет соответственно список подобных сервисов. Эти методы не запускают компоненты! Они просто выводят список таких компонентов. Есть также похожий метод для широковещательных приемников queryBroadcastReceivers().

Добавить комментарий