Введение
«Как система понимает, какому приложению отдать неявный intent?» — вопрос, после которого собеседование обычно уходит вглубь. И здесь большинство кандидатов заканчивается на фразе «ну, есть intent filter, и оно как-то матчится».
Intent Filter — это объявление в манифесте, которое говорит системе: «мой компонент готов принимать такие-то неявные intent». Без него компонент можно запустить только явно, по имени класса. С ним — вашу Activity может найти и запустить любое приложение на устройстве.
Понимание этой механики важно не только для собеседований: фильтры — это точки входа в ваше приложение извне (deep links, share-меню, открытие файлов), а значит, и поверхность для ошибок и атак. На собеседовании по этой теме проверяют не память, а способность рассуждать о том, что система сделает с вашим intent в конкретной ситуации.
Разберём по шагам: что такое фильтр, из чего он состоит, как система сопоставляет intent с фильтром и где здесь подводные камни.
Что такое Intent Filter
Официальная документация определяет intent filter как выражение в манифесте приложения, которое описывает типы intent, которые компонент хотел бы получать. Объявляется он внутри компонента — Activity, Service или Broadcast Receiver — тегом <intent-filter>.
Ключевое следствие: если у Activity нет ни одного intent-filter, она может быть запущена только явным intent — по имени класса. Никакой другой компонент не сможет её найти для неявного вызова. Это простое правило часто всплывает на собеседовании в формулировке «зачем нужен intent filter, если есть явные intent?» — ответ: чтобы вас могли находить другие приложения.
При этом важно понимать разницу между типами intent:
- явный intent называет конкретный компонент (
ComponentName) — система запускает его без всякого поиска; - неявный intent не называет компонент, а описывает действие: action, данные, категорию. Система сравнивает его с intent filters всех приложений на устройстве и запускает подходящие.
Если подходящих компонентов несколько — система показывает пользователю диалог выбора. Если ни одного — бросается ActivityNotFoundException. Если один — запускается он.
Главное
Intent filter — это реклама компонента: «я умею обрабатывать такие-то intent». Явный intent всегда попадает в цель без рекламы; неявный intent находит цель только по фильтрам.
Из чего состоит фильтр: action, category, data
Каждый <intent-filter> может содержать три вида элементов, и все они необязательны: <action>, <category>, <data>. Внутри одного фильтра можно объявить несколько экземпляров каждого элемента — это работает как «ИЛИ»: подойдёт любой из перечисленных.
Action — строка, описывающая общее действие: «посмотреть», «отправить», «редактировать». Стандартные действия — константы класса Intent: ACTION_VIEW, ACTION_SEND, ACTION_EDIT. Свои действия принято называть с префиксом имени пакета приложения, например com.example.action.TIMETRAVEL, чтобы не пересечься с другими приложениями.
Category — дополнительная характеристика компонента. Классика: CATEGORY_LAUNCHER — Activity показывается в лаунчере, CATEGORY_BROWSABLE — Activity может запускаться браузером по ссылке. Большинству intent категории не нужны вовсе.
Data — описание данных, с которыми умеет работать компонент: URI (scheme://host:port/path) и MIME-тип. Например, android:mimeType="text/plain" — компонент умеет работать с текстом, android:scheme="https" — с https-ссылками.
Пример из документации — Activity для приёма share-intent:
<activity android:name="ShareActivity" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>Обратите внимание на CATEGORY_DEFAULT — без него фильтр не сработает для неявных intent. Почему — в следующем разделе.
Если компоненту нужно принимать разные комбинации action и data — объявляют несколько отдельных <intent-filter>: каждый фильтр — это самостоятельное «И»-условие, а фильтры между собой работают как «ИЛИ».
Как система резолвит неявный intent: три теста
Когда система получает неявный intent, она сравнивает его с каждым фильтром по трём аспектам: action, category, data. Чтобы intent доставили компоненту, он должен пройти все три теста — провал хотя бы одного означает, что компонент не получит intent (но, возможно, пройдёт через другой его фильтр).
Action-тест. Action из intent должен совпасть с одним из <action> в фильтре. Если в фильтре нет ни одного action — проваливаются все intent. Если же action нет в самом intent, тест пройден, лишь бы в фильтре был хотя бы один action.
Category-тест. Каждая категория из intent должна присутствовать в фильтре. Обратное не требуется: фильтр может объявлять больше категорий, чем intent, — это нормально. Intent без категорий проходит тест всегда.
Здесь и прячется правило про CATEGORY_DEFAULT: startActivity() автоматически добавляет категорию CATEGORY_DEFAULT ко всем неявным intent. Поэтому Activity, которая хочет принимать неявные intent, обязана объявить <category android:name="android.intent.category.DEFAULT"/> в фильтре — иначе её категорийный тест не пройдёт никогда.
Data-тест. Сравниваются URI и MIME-тип. URI сравнивается только по тем частям, которые указаны в фильтре, — с линейной зависимостью: если не указан scheme, host игнорируется; если не указан host, игнорируется port; если нет scheme и host, игнорируется path. Фильтр с одним android:scheme="https" подойдёт всем https-ссылкам с любым хостом. В path можно использовать wildcard *.
Отдельное правило, которое часто проверяют на собеседованиях: если intent содержит URI или MIME-тип, а в фильтре нет ни одного <data> — data-тест провален.
val sendIntent = Intent().apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "Привет с androidbooster.ru!")
}
if (sendIntent.resolveActivity(packageManager) != null) {
startActivity(sendIntent)
}Итого по резолвингу: один подходящий компонент — запускается; несколько — диалог выбора; ноль — исключение, поэтому перед запуском неявного intent стоит проверять resolveActivity(). Если вы хотите показывать диалог выбора всегда — используйте Intent.createChooser().
Ошибки и безопасность
Теперь о том, где фильтры ломаются в реальных проектах и почему это тема для собеседования.
- Забывают
CATEGORY_DEFAULT— самая частая ошибка: фильтр объявлен, а неявный intent не доходит. Диагностируется долго и больно. - Путают «И» и «ИЛИ» — элементы внутри одного фильтра требуют совпадения по всем трём тестам; разные комбинации action/data нужно раскладывать по отдельным фильтрам.
- Считают фильтр защитой — это не так: другой компонент может запустить вашу Activity явным intent, если узнает имя класса. Для ограничения доступа есть
android:exported="false". - Объявляют фильтры у Service — начиная с Android 5.0 (API 21)
bindService()с неявным intent бросает исключение. Сервисы нужно запускать только явными intent и фильтры им не объявлять. - Не проверяют
resolveActivity()— и получаютActivityNotFoundExceptionна устройствах, где нет ни одного обработчика.
Безопасность — отдельная тема. Каждый intent-filter делает компонент видимым для других приложений, а значит, расширяет поверхность атаки: вашу Activity могут запускать со странными данными, и она должна это переживать. На Android 12+ для компонентов с фильтрами атрибут android:exported обязателен — и это хороший повод задуматься, кому вы реально открываете вход.
Ещё один практический совет: intent-фильтры для Activity — это точки входа вроде deep links и share-меню, поэтому логику внутри принимающей Activity стоит писать как для «недоверенного» входа: валидировать данные из intent, не полагаться на то, что экстра точно есть, и не выводить пользовательские данные без обработки.
Меня зовут Рустем Бикбулатов, я senior Android-разработчик, провожу технические собеседования и менторю в Яндекс Практикуме. Более 100 учеников и разборов: вопросы про intent и фильтры я задаю почти каждому кандидату на middle — по ответу видно, собирал ли человек приложение вручную или только по гайдам.
Хотите, чтобы я посмотрел ваш код?
Бесплатный разбор: пришлите код или резюме — посмотрю и скажу, что мешает перейти на следующий грейд. Без обязательств.
Итоги
Intent filter — это объявление в манифесте, которое решает, какие неявные intent получит ваш компонент. Главное, что нужно вынести:
- Фильтр состоит из
<action>,<category>и<data>, элементы внутри одного фильтра работают как «И», несколько фильтров — как «ИЛИ». Без фильтра компонент запускается только явным intent. - Система резолвит intent тремя тестами: action, category, data. Все три должны пройти.
startActivity()добавляет к intentCATEGORY_DEFAULT, поэтому фильтр безCATEGORY_DEFAULTне получит ни одного неявного intent. - Резолвинг — не гарантия запуска: подходящих компонентов может быть несколько (диалог выбора) или ноль (
ActivityNotFoundException). ПроверкаresolveActivity()передstartActivity()— обязательная привычка.
Перед собеседованием
Отрепетируйте рассказ на примере: фильтр с ACTION_SEND + CATEGORY_DEFAULT + text/plain и intent, который его запускает. Если вы можете объяснить, почему неявный intent доходит именно до этого фильтра, — тема закрыта.
Если чувствуете, что такие вопросы «вроде знаю, но сформулировать не могу» копятся — не нужно готовиться в одиночку. Напишите в Telegram, разберём вашу ситуацию и составим план подготовки.