Введение
Базовый разбор манифеста — это «паспорт приложения». Глубокий — это понимание, как система запускает компоненты, резолвит интенты и защищает их разрешениями. Именно второй уровень отличает junior от middle.
На собеседовании на middle вопрос про манифест не заканчивается на «там объявлены Activity и разрешения». Интервьюер копает глубже: что будет, если интент не найдёт ни одного компонента; зачем нужна категория DEFAULT; почему intent-filter — не защита; как Android 11+ ограничил видимость приложений.
Эта статья — продолжение базового разбора AndroidManifest.xml. Здесь идём глубже: компоненты и их атрибуты, механизм резолвинга intent-filter, защита компонентов permissions и versioning.
Компоненты: что именно объявляет манифест
Документация сводит манифест к четырём типам компонентов: для каждого подкласса Activity — <activity>, Service — <service>, BroadcastReceiver — <receiver>, ContentProvider — <provider>. Объявить компонент — значит указать его имя и свойства: имя класса в android:name, иконку и label, разрешения, intent-filter.
Детали, которые всплывают на собеседованиях:
.MainActivity— сокращение. Имя с точкой разворачивается через namespace из build.gradle модуля: при namespacecom.example.appимя.MainActivityпревращается вcom.example.app.MainActivity.- Иконка и label наследуются. Иконка и label на уровне
<application>— значения по умолчанию для всех компонентов. Отдельный intent-filter тоже может задавать свою иконку и label: они показываются пользователю, когда компонент предлагается как вариант выполнения интента в диалоге выбора. - Обязательны только
<manifest>и<application>. Остальные элементы могут повторяться, но без них манифест бесполезен. - Все значения — атрибуты. В манифесте нет текстовых значений внутри элементов: всё задаётся атрибутами.
Ещё один нюанс имени: компоненты в подпакетах требуют полного пути. Если класс лежит в com.example.app.purchases, в манифесте пишут .purchases.PayActivity — или полностью квалифицированное имя.
Плюс неочевидный, но частый вопрос — <activity-alias>: альтернативное имя для существующей Activity. Например, чтобы дать компоненту вторую точку входа с другим intent-filter, не создавая новый класс. Класс один — а точек входа у системы несколько.
Ключевая мысль
Компонент, не объявленный в манифесте, система запустить не может — это база. Но на глубоком уровне важно другое: как объявленный компонент связывается с интентами и кто вообще может его запустить.
Intent-filter: как система резолвит интенты
Intent-filter — это объявление «какие имплицитные интенты компонент готов обрабатывать». Когда приложение вызывает startActivity() с имплицитным интентом, система ищет среди установленных приложений компоненты, чьи фильтры подходят. Если кандидат один — запускается он, если несколько — пользователь выбирает, если ни одного — выбрасывается ActivityNotFoundException.
Как именно система проверяет фильтр, описано в Intents and Intent Filters. Три теста:
- Action test. Действие интента должно совпасть с одним из
<action>в фильтре. - Category test. Каждая категория интента должна присутствовать в фильтре. Обратное не требуется: фильтр может объявлять больше категорий, чем в интенте. Интент без категорий проходит всегда.
- Data test. Если в интенте есть URI или MIME-тип, они должны совпасть с
<data>фильтра. Если интент несёт данные, а в фильтре<data>нет — тест не пройден.
Из этого следуют два практических вывода, которые любят проверять:
Классическая ловушка: Activity объявлена с intent-filter, но без категории android.intent.category.DEFAULT. Система добавляет CATEGORY_DEFAULT ко всем имплицитным интентам, передаваемым в startActivity() — и фильтр без DEFAULT не пройдёт тест. Имплицитные интенты до такой Activity просто не доходят. Для приёма имплицитных интентов категория DEFAULT обязательна.
И ещё два нюанса про безопасность:
- Intent-filter — не защита. Фильтр ограничивает, какие имплицитные интенты принимает компонент, но другое приложение может запустить ваш компонент явным интентом по имени класса. Если компонент должен быть доступен только вашему приложению — не объявляйте intent-filter и ставьте
android:exported="false". - android:exported — явно. Для компонентов с intent-filter атрибут обязан быть объявлен явно, иначе приложение не установится на Android 12+. LAUNCHER-активность —
true, внутренние экраны —false.
Пример фильтра, который принимает шаринг текста:
<activity
android:name=".ShareActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>Заметьте: DEFAULT на месте, данные — только MIME без URI-схемы. Это ровно тот фильтр, что используют приложения «поделиться текстом».
Два поведения резолвинга, которые стоит знать из практики:
- Диалог выбора. Если пользователю может понадобиться разное приложение каждый раз (например, шаринг), показывают диалог через
Intent.createChooser()— в нём нельзя закрепить приложение по умолчанию. А при открытии ссылки система запоминает выбор по умолчанию — поэтому браузер открывается «сам». - Лаунчер — это тоже резолвинг. Home-экран ищет активности с
ACTION_MAINиCATEGORY_LAUNCHER. Именно поэтому комбинация MAIN + LAUNCHER делает Activity точкой входа в приложение: это не магия, а обычный поиск по фильтрам.
Permissions: защита компонентов своими руками
Базовый разбор покрыл <uses-permission> и рантайм-запросы. Глубокий уровень — защита собственных компонентов.
Компонент можно закрыть разрешением через атрибут android:permission: запустить его сможет только приложение, у которого это разрешение есть. В качестве разрешения годится системное из android.Manifest.permission или собственное, объявленное через <permission>.
<permission
android:name="com.example.app.permission.EXPORT_DATA"
android:protectionLevel="signature" />
<service
android:name=".ExportService"
android:permission="com.example.app.permission.EXPORT_DATA"
android:exported="true" />Так строят межприложенческие API: сервис экспорта вызывается только приложениями, которым вы выдали разрешение. На собеседовании стоит проговорить, что protectionLevel — это уровень защиты: normal, dangerous, signature и другие. Разрешение с уровнем signature выдаётся только приложениям, подписанным тем же ключом — распространённый способ ограничить доступ своим приложениям.
- Свои компоненты защищаются
android:permissionна элементе компонента. - Собственные разрешения объявляются через
<permission>сprotectionLevel. - signature — доступ только для приложений с тем же ключом подписи.
- Объявление разрешения в манифесте обязательно всегда, рантайм-запрос с API 23 — отдельный шаг.
Атрибут android:permission работает на всех четырёх типах компонентов: receiver и provider тоже можно закрыть разрешением. Например, провайдер данных, который отдаёт контент другим приложениям, часто защищают системным разрешением — так доступ к данным контролируется на уровне ОС, а не проверкой в коде.
Важно не путать два направления: <uses-permission> — это «что нужно мне», а android:permission на компоненте — «что нужно тому, кто запускает мой компонент». Путаница этих направлений — одна из самых частых ошибок на собеседовании.
Versioning и видимость приложений
Третья тема глубокого разбора — как манифест связан с версиями. Формально в манифесте есть <uses-sdk> с minSdkVersion, но с тех пор, как проекты собираются через Gradle, эти значения переопределяются свойствами в build.gradle: minSdk, targetSdk, compileSdk. Поэтому современный ответ: версии SDK живут в build.gradle, а не в манифесте.
Отдельная история — версия приложения. versionCode и versionName тоже в build.gradle: versionCode — целое число для внутренних проверок (должно расти с каждым релизом), versionName — читаемая версия «2.1.0» для пользователя.
Совместимость с устройствами тоже описывается в манифесте — через <uses-feature>. Если приложение не может работать без конкретного оборудования, его объявляют обязательным:
<uses-feature
android:name="android.hardware.sensor.compass"
android:required="true" />Google Play не даст установить такое приложение на устройство без компаса. А вот <uses-feature> без required — просто информация для Play: установить можно, но приложение должно корректно проверять наличие функции в рантайме.
И самая современная тема — package visibility. Начиная с Android 11 (API level 30) приложение видит другие установленные приложения, только если они объявили подходящий intent-filter или вы явно перечислили их в <queries>. Практический эффект: queryIntentActivities() не найдёт приложение, которое не объявлено в <queries> и не имеет видимого фильтра. На Android 13+ (API level 33) то же ограничение влияет на доставку интентов: приложение, таргетирующее 33+, обработает ваш имплицитный интент только при совпадении с его фильтром — иначе ActivityNotFoundException.
Когда я провожу собеседования, вопросы про манифест — мой любимый фильтр глубины: перечисление тегов заучивается за вечер, а вот резолвинг интентов, защита компонентов и package visibility требуют понимания системы. Кандидаты, которые отвечают на такие вопросы связно, как правило, закрывают и остальные платформенные темы.
Связка тем
Манифест связывает три уровня: компоненты (что система может запустить), permissions (кто может запустить) и versioning (на каких устройствах и с какими ограничениями). Сильный ответ показывает связку, а не перечисление тегов.
Хотите уверенно отвечать на платформенные вопросы?
Манифест, интенты, permissions — на мок-собеседованиях тренируем не заученные ответы, а механику: как система резолвит, защищает и ограничивает.
Итоги
Глубокое понимание манифеста — это три связки: компоненты и их атрибуты, резолвинг интентов через три теста intent-filter, защита и ограничения через permissions и versioning.
Три тезиса, которые стоит унести с собой:
- Intent-filter резолвится тремя тестами. Action, category (включая обязательный DEFAULT для имплицитных интентов), data. Нет кандидата —
ActivityNotFoundException. - Фильтр — не защита. Для ограничения доступа —
android:exported="false"и отсутствие intent-filter; межприложенческий доступ — черезandroid:permission. - Версии живут в Gradle. minSdk, targetSdk, compileSdk и versionCode, versionName — в build.gradle, а с Android 11+ видимость других приложений регулируется
<queries>.
Если резолвинг интентов и exported кажутся кашей — вернитесь к базовому разбору AndroidManifest.xml, а потом перечитайте эту статью. Но если и после этого платформенные вопросы сыпятся — не нужно зубрить дальше. Напишите в Telegram, разберём вашу ситуацию и составим план до уверенного middle.