Permission Handler е компонент на Android приложение, отговорен за проверка, заявка и обработка на резултати от runtime разрешения. Според Android Developer Guide, 2024, манипулаторът на разрешения централизира логиката на checkSelfPermission, requestPermissions и shouldShowRequestPermissionRationale в един клас или ViewModel. Това опростява поддръжката на кода и подобрява тестването.
Основни точки
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), а манипулаторът сам управлява проверката на статуса, показването на rationale, извикването на системния диалог и предаването на резултата на callback. Това реализира принципа на единична отговорност и отделя бизнес логиката от платформения код на разрешенията.
Permission Handler става необходим, когато приложението използва 3 или повече опасни разрешения. За прости приложения с едно разрешение (например камера за скенер на QR кодове) може да се използва директно извикване. Но за типично мобилно приложение с камера, геолокация, известия и хранилище — централизиран манипулатор е задължителен за поддръжка.
Типичен Permission Handler се състои от три нива: интерфейс-договор, реализация с ActivityResultLauncher и слой за ViewModel. Интерфейсът дефинира методи за заявка за всяко разрешение — requestCamera, requestLocation, requestStorage. Реализацията свързва тези методи със съответните договори ActivityResultContracts.RequestPermission.
Ключови компоненти на архитектурата:
Тази архитектура позволява лесна замяна на реализацията в тестове: вместо реален ActivityResultLauncher се използва mock, който връща предварително определен резултат без взаимодействие със системата. Това е критично за юнит тестване на UI логика, където стартирането на Activity за диалог на разрешения е невъзможно.
Permission Handler трябва да взема предвид жизнения цикъл на Activity и Fragment. Launcher-ите се регистрират в ActivityResultRegistry, който автоматично запазва и възстановява състоянието при завъртане на екрана и повторно създаване на Activity. Handler не трябва да съхранява директни референции към Activity или Fragment — вместо това използвайте WeakReference или предайте registry чрез конструктора. Това предотвратява изтичане на памет и сривове при промени в конфигурацията.
Основната реализация на Permission Handler се гради на ActivityResultContracts.RequestPermission. Handler получава ActivityResultRegistry от ComponentActivity или Fragment и регистрира launcher-и за всяко разрешение. Всеки launcher приема ламбда-callback, който се извиква след отговора на потребителя.
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
}
}
Handler се инициализира в onCreate на Activity чрез registerForActivityResult, който предоставя достъп до ActivityResultRegistry. След инициализация манипулаторът е готов да обработва заявки през целия жизнен цикъл на Activity. Важно е да извикате initialize преди първата заявка, в противен случай launcher-ът няма да бъде регистриран.
Интеграция на Permission Handler с ViewModel е най-напредналият подход. ViewModel управлява състоянието на заявките, а Handler изпълнява само платформени извиквания. ViewModel съдържа StateFlow<PermissionUiState>, където UiState описва кое разрешение се заявява и какъв резултат е получен. Activity се абонира за този StateFlow и делегира заявката на Handler.
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 е възможно благодарение на интерфейса PermissionHandler. В тестовете се създава FakePermissionHandler, който симулира различни сценарии: разрешение предоставено, отказано, Never Ask Again. Всеки сценарий се тества независимо. Това е особено важно за тестване на UI логика, която трябва правилно да реагира на всичките три резултата.
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 такива теста за цялото приложение — останалите сценарии са покрити от юнит тестове с fake реализации.
Чести грешки при работа с Permission Handler включват: липса на проверка на checkSelfPermission преди всяко API извикване, игнориране на shouldShowRequestPermissionRationale, повторно извикване на requestPermissions при Never Ask Again и съхраняване на launcher-и без отчитане на жизнения цикъл на Activity. Нека разгледаме всеки проблем и неговото решение.
Най-честата грешка — извикване на API без проверка на статуса на разрешението. Разработчиците предполагат, че ако разрешение е било предоставено веднъж, то ще остане завинаги. Въпреки това, потребителят може да го оттегли по всяко време чрез настройките. Permission Handler трябва винаги да извиква isPermissionGranted преди изпълнение на чувствителна операция. Втората честа грешка — игнориране на shouldShowRequestPermissionRationale и повторна заявка, която води до незабавен отказ без диалог при Never Ask Again.
Най-добрите практики включват: създаване на един екземпляр на Handler за целия жизнен цикъл на Activity, използване на SharedFlow за предаване на резултати към ViewModel, логване на всички заявки и откази за аналитика, както и показване на персонализиран rationale диалог преди системния при първия отказ. Спазването на тези правила гарантира стабилна работа с разрешения на всички версии на Android.
Често задавани въпроси
Permission Handler — компонент за централизирано управление на runtime разрешения, капсулиращ checkSelfPermission, requestPermissions и shouldShowRequestPermissionRationale. Опростява поддръжката на кода и подобрява тестването.
Препоръчва се ActivityResultContracts.RequestPermission от библиотеката androidx.activity. Той заменя остарелия onRequestPermissionsResult и предоставя чист callback API с Boolean резултат.
За едно разрешение Handler не е задължителен — можете да използвате директно извикване на RequestPermission launcher в Activity. Handler става необходим при 3 или повече разрешения, за да се избегне дублиране на код.
Създайте интерфейс PermissionHandler и негова fake реализация за юнит тестове. Fake връща предварително определени резултати без системни извиквания. Това позволява тестване на ViewModel и UI логика без емулатор.
След отказ проверете shouldShowRequestPermissionRationale. Ако методът е върнал false — режимът Never Ask Again е активиран. Handler трябва да върне PermissionResult.DENIED(false), а UI да покаже бутон за преход към Настройки.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също