Permission Handler в Android: как устроено, обработка запросов и реализация

Автор: IT Sectr Опубликовано: 2026-05-20 Время чтения: 8 мин

Permission Handler — это компонент Android-приложения, отвечающий за проверку, запрос и обработку результатов runtime-разрешений. По данным Android Developer Guide, 2024, обработчик разрешений централизует логику checkSelfPermission, requestPermissions и shouldShowRequestPermissionRationale в едином классе или ViewModel. Это упрощает поддержку кода и улучшает тестирование.

Главное

  • Permission Handler — специализированный компонент для централизованного управления runtime-разрешениями Android.
  • Инкапсулирует логику checkSelfPermission, requestPermissions и shouldShowRequestPermissionRationale.
  • Современные реализации базируются на ActivityResultContracts из androidx.activity.
  • Упрощает юнит-тестирование благодаря инверсии зависимостей и изоляции платформенного кода.
  • Лучшие практики включают единый handler на Activity и переиспользование через DI-контейнер.

Что такое Permission Handler в Android

Permission Handler — это архитектурный паттерн для централизованного управления runtime-разрешениями Android. Вместо разрозненных вызовов ContextCompat.checkSelfPermission и ActivityCompat.requestPermissions по всему коду приложения вся логика запросов и обработки результатов концентрируется в одном классе. Это снижает дублирование, упрощает поддержку и делает код более предсказуемым.

Необходимость в Permission Handler возникла с введением runtime-разрешений в Android 6.0. До этого все разрешения запрашивались при установке, и код приложения мог использовать любые API без проверок. После перехода на runtime-модель каждое использование опасного разрешения требует трёхшаговой проверки: checkSelfPermission, requestPermissions, onRequestPermissionsResult. Размазывание этой логики по Activity и Fragment приводит к инлайн-дублированию и ошибкам. По данным Google I/O 2019, централизация обработки разрешений снижает количество багов, связанных с Permission Denial, в среднем на 60 процентов.

Хороший Permission Handler предоставляет чистый интерфейс для вызывающего кода. Activity или Fragment не должны знать детали запроса — они вызывают метод типа requestCamera(callback), а handler сам управляет проверкой статуса, отображением rationale, вызовом системного диалога и передачей результата в callback. Это реализует принцип единой ответственности и отделяет бизнес-логику от платформенного кода разрешений.

Когда нужен Permission Handler

Permission Handler становится необходимым, когда приложение использует 3 и более опасных разрешений. Для простых приложений с одним разрешением (например, камера для сканера QR-кодов) можно обойтись прямым вызовом. Но для типичного мобильного приложения с камерой, геолокацией, уведомлениями и хранилищем — централизованный handler обязателен для поддержки.

Архитектура Permission Handler

Типичный Permission Handler состоит из трёх уровней: интерфейс-контракт, реализация с ActivityResultLauncher и слой для ViewModel. Интерфейс определяет методы запроса для каждого разрешения — requestCamera, requestLocation, requestStorage. Реализация связывает эти методы с соответствующими контрактами ActivityResultContracts.RequestPermission.

Ключевые компоненты архитектуры:

  • PermissionHandlerContract — интерфейс с методами для каждого разрешения
  • PermissionHandlerImpl — реализация, связывающаяся с ActivityResultRegistry
  • PermissionResult — sealed class с состояниями GRANTED, DENIED, NEVER_ASK_AGAIN
  • RationaleHandler — компонент для отображения объяснений перед запросом

Такая архитектура позволяет легко подменять реализацию в тестах: вместо реального ActivityResultLauncher используется мок, который возвращает предопределённый результат без взаимодействия с системой. Это критически важно для юнит-тестирования UI-логики, где запуск Activity для диалога разрешений невозможен.

Управление жизненным циклом

Permission Handler должен учитывать жизненный цикл Activity и Fragment. Лаунчеры регистрируются в ActivityResultRegistry, который автоматически сохраняет и восстанавливает состояние при повороте экрана и пересоздании Activity. Handler не должен хранить прямые ссылки на Activity или Fragment — вместо этого используйте WeakReference или передавайте registry через конструктор. Это предотвращает утечки памяти и crash при configuration changes.

Реализация Permission Handler в Kotlin

Базовая реализация Permission Handler строится на ActivityResultContracts.RequestPermission. Handler получает ActivityResultRegistry из ComponentActivity или Fragment и регистрирует лаунчеры для каждого разрешения. Каждый лаунчер принимает лямбду-колбэк, которая вызывается после ответа пользователя.

kotlin
sealed class PermissionResult {
    object GRANTED : PermissionResult()
    data class DENIED(
        val shouldShowRationale: Boolean
    ) : PermissionResult()
}

interface PermissionHandler {
    fun requestCamera(
        callback: (PermissionResult) -> Unit
    )
    fun requestLocation(
        callback: (PermissionResult) -> Unit
    )
    fun isPermissionGranted(
        permission: String
    ): Boolean
}

class AndroidPermissionHandler(
    private val registry: ActivityResultRegistry,
    private val context: Context
) : PermissionHandler {

    private var cameraLauncher: ActivityResultLauncher<String>? = null

    fun initialize() {
        cameraLauncher = registry.register(
            "camera_permission",
            ActivityResultContracts.RequestPermission()
        ) { isGranted ->
            if (isGranted) {
                pendingCameraCallback?.invoke(
                    PermissionResult.GRANTED
                )
            } else {
                val rationale = ActivityCompat.shouldShowRequestPermissionRationale(
                    context as Activity,
                    Manifest.permission.CAMERA
                )
                pendingCameraCallback?.invoke(
                    PermissionResult.DENIED(rationale)
                )
            }
        }
    }

    private var pendingCameraCallback:
        ((PermissionResult) -> Unit)? = null

    override fun requestCamera(
        callback: (PermissionResult) -> Unit
    ) {
        if (isPermissionGranted(
                Manifest.permission.CAMERA
        )) {
            callback.invoke(PermissionResult.GRANTED)
            return
        }
        pendingCameraCallback = callback
        cameraLauncher?.launch(
            Manifest.permission.CAMERA
        )
    }

    override fun isPermissionGranted(
        permission: String
    ): Boolean {
        return ContextCompat.checkSelfPermission(
            context, permission
        ) == PackageManager.PERMISSION_GRANTED
    }
}

Инициализация в Activity

Handler инициализируется в onCreate Activity через registerForActivityResult, который предоставляет доступ к ActivityResultRegistry. После инициализации handler готов обрабатывать запросы на протяжении всего жизненного цикла Activity. Важно вызывать initialize до первого запроса, иначе лаунчер не будет зарегистрирован.

Permission Handler с ViewModel

Интеграция Permission Handler с ViewModel — наиболее продвинутый подход. ViewModel управляет состоянием запросов, а Handler выполняет только платформенные вызовы. ViewModel содержит StateFlow<PermissionUiState>, где UiState описывает, какое разрешение запрашивается и какой результат получен. Activity подписывается на этот StateFlow и делегирует запрос Handler.

kotlin
class PermissionsViewModel : ViewModel() {

    private val _uiState =
        MutableStateFlow<PermissionUiState>(
            PermissionUiState.Idle
        )
    val uiState: StateFlow<PermissionUiState> = _uiState.asStateFlow()

    fun onCameraRequested() {
        _uiState.value = PermissionUiState.RequestingCamera
    }

    fun onPermissionResult(
        permission: String,
        result: PermissionResult
    ) {
        when (result) {
            PermissionResult.GRANTED -> {
                _uiState.value = PermissionUiState.Granted(permission)
            }
            is PermissionResult.DENIED -> {
                _uiState.value = PermissionUiState.Denied(
                    permission,
                    result.shouldShowRationale
                )
            }
        }
    }
}

sealed class PermissionUiState {
    object Idle : PermissionUiState()
    object RequestingCamera : PermissionUiState()
    data class Granted(val permission: String) : PermissionUiState()
    data class Denied(
        val permission: String,
        val shouldShowRationale: Boolean
    ) : PermissionUiState()
}

В этой модели Activity проверяет isPermissionGranted через Handler при старте, а ViewModel управляет только состоянием. Если разрешение не предоставлено — Activity подписывается на uiState, вызывает requestCamera из Handler и передаёт результат обратно в ViewModel через onPermissionResult. Разделение платформенного кода и бизнес-логики позволяет тестировать ViewModel без Android-зависимостей.

Тестирование Permission Handler

Юнит-тестирование Permission Handler возможно благодаря интерфейсу PermissionHandler. В тестах создаётся FakePermissionHandler, который имитирует различные сценарии: разрешение предоставлено, отклонено, Never Ask Again. Каждый сценарий проверяется независимо. Это особенно важно для тестирования UI-логики, которая должна корректно реагировать на все три исхода.

kotlin
class FakePermissionHandler : PermissionHandler {

    var cameraResult: PermissionResult =
        PermissionResult.GRANTED
    var grantedPermissions: Set<String> =
        setOf(Manifest.permission.CAMERA)

    override fun requestCamera(
        callback: (PermissionResult) -> Unit
    ) {
        callback.invoke(cameraResult)
    }

    override fun isPermissionGranted(
        permission: String
    ): Boolean {
        return permission in grantedPermissions
    }
}

Fake-реализация позволяет тестировать ViewModel без эмулятора. Достаточно установить cameraResult в нужное значение и проверить, что ViewModel корректно обновляет UiState. Интеграционные тесты проверяют реальный PermissionHandler с ActivityScenario, но таких тестов обычно 2-3 на всё приложение — остальные сценарии покрываются юнит-тестами с фейками.

Распространённые паттерны и ошибки

Типичные ошибки при работе с Permission Handler включают: отсутствие проверки checkSelfPermission перед каждым вызовом API, игнорирование shouldShowRequestPermissionRationale, повторный вызов requestPermissions при Never Ask Again и хранение лаунчеров без учёта жизненного цикла Activity. Рассмотрим каждую проблему и решение.

Самая частая ошибка — вызов API без проверки статуса разрешения. Разработчики предполагают, что если разрешение было предоставлено однажды, оно останется навсегда. Однако пользователь может отозвать его через настройки в любой момент. Permission Handler должен всегда вызывать isPermissionGranted перед выполнением чувствительной операции. Вторая популярная ошибка — игнорирование shouldShowRequestPermissionRationale и повторный запрос, который приводит к мгновенному отказу без диалога при Never Ask Again.

Лучшие практики включают: создание одного экземпляра Handler на весь жизненный цикл Activity, использование SharedFlow для передачи результатов в ViewModel, логирование всех запросов и отказов для аналитики, а также показ кастомного rationale-диалога перед системным при первом отказе. Следование этим правилам гарантирует стабильную работу с разрешениями на всех версиях Android.

Часто задаваемые вопросы

Что такое Permission Handler в Android?

Permission Handler — это компонент для централизованного управления runtime-разрешениями, инкапсулирующий checkSelfPermission, requestPermissions и shouldShowRequestPermissionRationale. Он упрощает поддержку кода и улучшает тестирование.

Какой API использовать для Handler в 2024?

Рекомендуется ActivityResultContracts.RequestPermission из библиотеки androidx.activity. Он заменяет устаревший onRequestPermissionsResult и предоставляет чистый callback API с результатом Boolean.

Нужен ли Handler для одного разрешения?

Для одного разрешения Handler не обязателен — можно использовать прямой вызов RequestPermission лаунчера в Activity. Handler становится необходимым при 3 и более разрешениях, чтобы избежать дублирования кода.

Как тестировать Permission Handler?

Создайте интерфейс PermissionHandler и его fake-реализацию для юнит-тестов. Fake возвращает предопределённые результаты без системных вызовов. Это позволяет тестировать ViewModel и UI-логику без эмулятора.

Как обработать Never Ask Again в Handler?

После отказа проверьте shouldShowRequestPermissionRationale. Если метод вернул false — активирован режим Never Ask Again. Handler должен вернуть PermissionResult.DENIED(false), а UI — показать кнопку перехода в Settings.

Итоги

  • Permission Handler — архитектурный компонент для централизованного управления runtime-разрешениями Android.
  • Базируется на ActivityResultContracts.RequestPermission из библиотеки androidx.activity.
  • Интерфейс с методами для каждого разрешения упрощает юнит-тестирование через fake-реализации.
  • Интеграция с ViewModel через StateFlow разделяет платформенный код и бизнес-логику.
  • Типичные ошибки: отсутствие checkSelfPermission, игнорирование rationale и Never Ask Again.
  • Лучшая практика — один Handler на Activity с регистрацией в onCreate.
  • Централизация снижает количество Permission Denial-багов на 60 процентов в типовых проектах.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также