android:exported: спасение от краша в Android 12 на Kotlin

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему ваше приложение внезапно перестало запускаться на Android 12?

Вы обновили targetSdkVersion до 31 или выше, собрали APK, запускаете — и получаете краш с загадочным сообщением:

android:exported needs to be explicitly specified for <activity>. 
Apps targeting Android 12 and higher are required to specify 
an explicit value for `android:exported` when the corresponding 
component has an intent filter defined.

Знакомо? Если вы заглядывали на Stack Overflow, то видели, что этот вопрос собрал тысячи просмотров. Разработчики в панике, а пользователи — в недоумении. Но не спешите удалять проект. В этой статье мы разберёмся, что за зверь этот android:exported, почему Google ужесточил требования, и как быстро исправить ошибку на Kotlin. Поехали!

Что такое android:exported и зачем он нужен?

android:exported — это атрибут в AndroidManifest.xml, который управляет тем, могут ли другие приложения (или сама система) запускать ваш компонент (Activity, Service, BroadcastReceiver или ContentProvider). Если атрибут установлен в true — компонент доступен извне. Если false — только внутри вашего приложения.

До Android 12 (API 31) этот атрибут был опциональным. Если вы его не указывали, система сама решала, доступен ли компонент:

  • Если у Activity есть <intent-filter> — система считала, что exported = true.
  • Если нет фильтра — exported = false.

Но такое «автоматическое» поведение — дыра в безопасности. Представьте: вы написали Activity для входа в приложение и случайно добавили intent-filter. Любое стороннее приложение может вызвать вашу Activity и украсть токены. Поэтому Google решил: с Android 12 вы обязаны явно указать android:exported для любого компонента, у которого есть <intent-filter>.

Как выглядит ошибка на практике?

Допустим, у вас есть простой манифест:

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

Если targetSdkVersion = 31 или выше, вы получите ошибку при компиляции или запуске. Решение — добавить android:exported:

<activity 
    android:name=".MainActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

Для главной Activity, которая запускается с рабочего стола, exported должен быть true — иначе лаунчер не сможет её открыть.

Как правильно исправить: пошаговая инструкция

Шаг 1. Откройте AndroidManifest.xml

Найдите все <activity>, <service>, <receiver> и <provider>, у которых есть <intent-filter>. Для каждого из них добавьте android:exported.

Шаг 2. Определите значение атрибута

Вот простое правило:

  • true — если компонент должен быть доступен из других приложений (например, Activity для входа через Google, BroadcastReceiver для системных событий, главная Activity с фильтром LAUNCHER).
  • false — если компонент используется только внутри вашего приложения (например, Activity, которая открывается по нажатию кнопки в вашем же коде).

Шаг 3. Примеры для разных сценариев

Сценарий 1: Главная Activity (Launcher)

<activity 
    android:name=".MainActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

Сценарий 2: Activity, которая открывается только из вашего приложения (например, экран настроек)

<activity 
    android:name=".SettingsActivity"
    android:exported="false" />

Обратите внимание: у этой Activity нет <intent-filter>, и она помечена как false. Ошибки не будет, потому что без фильтра атрибут всё ещё опционален, но лучше указывать его везде для единообразия.

Сценарий 3: Deep Link (глубокие ссылки)

Если вы принимаете внешние ссылки, Activity должна быть доступна извне:

<activity 
    android:name=".DeepLinkActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" android:host="www.example.com" />
    </intent-filter>
</activity>

Сценарий 4: BroadcastReceiver (например, для Boot Completed)

<receiver 
    android:name=".BootReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

Как это выглядит в коде на Kotlin?

Хотя android:exported — это атрибут манифеста, в Kotlin вы можете программно проверять, доступен ли компонент. Например, при запуске Activity через Intent:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Проверяем, может ли система обработать Intent
        val intent = Intent(this, SettingsActivity::class.java)
        if (intent.resolveActivity(packageManager) != null) {
            startActivity(intent)
        } else {
            Toast.makeText(this, "Activity не найдена", Toast.LENGTH_SHORT).show()
        }
    }
}

Этот код не зависит от exported напрямую, но если вы забудете указать атрибут в манифесте, вы даже не дойдёте до этого момента — приложение упадёт при старте.

Что делать с библиотеками и сторонними SDK?

Часто ошибка возникает не в вашем коде, а в библиотеках, которые добавляют свои Activity в манифест. Например, <

Блоги

Рекомендаций по книгам