Android의 Permission Handler: 작동 방식, 요청 처리 및 구현

저자: IT Sectr 게시일: 2026-05-20 읽는 시간: 8 분

Permission Handler는 런타임 권한을 확인, 요청 및 결과를 처리하는 Android 애플리케이션 구성 요소입니다. Android Developer Guide, 2024에 따르면, 권한 핸들러는 checkSelfPermission, requestPermissions 및 shouldShowRequestPermissionRationale의 로직을 단일 클래스 또는 ViewModel에 중앙 집중화합니다. 이는 코드 유지 관리를 간소화하고 테스트를 개선합니다.

주요 내용

  • Permission Handler — Android 런타임 권한을 중앙 집중식으로 관리하기 위한 특화된 구성 요소입니다.
  • checkSelfPermission, requestPermissions 및 shouldShowRequestPermissionRationale의 로직을 캡슐화합니다.
  • 최신 구현은 androidx.activity의 ActivityResultContracts를 기반으로 합니다.
  • 의존성 역전 및 플랫폼 코드 격리를 통해 단위 테스트를 간소화합니다.
  • 모범 사례에는 Activity당 하나의 핸들러와 DI 컨테이너를 통한 재사용이 포함됩니다.

Android에서 Permission Handler란

Permission Handler는 Android 런타임 권한을 중앙 집중식으로 관리하기 위한 아키텍처 패턴입니다. 애플리케이션 코드 전체에 흩어져 있는 ContextCompat.checkSelfPermission 및 ActivityCompat.requestPermissions 호출 대신, 모든 요청 및 결과 처리 로직이 단일 클래스에 집중됩니다. 이는 중복을 줄이고 유지 관리를 간소화하며 코드를 더 예측 가능하게 만듭니다.

Permission Handler의 필요성은 Android 6.0에서 런타임 권한이 도입되면서 생겨났습니다. 그 이전에는 모든 권한이 설치 시 요청되었고, 애플리케이션 코드는 확인 없이 모든 API를 사용할 수 있었습니다. 런타임 모델로 전환한 후, 위험한 권한을 사용할 때마다 checkSelfPermission, requestPermissions, onRequestPermissionsResult의 세 단계 확인이 필요합니다. 이 로직을 Activity와 Fragment에 분산시키면 인라인 중복과 오류가 발생합니다. Google I/O 2019에 따르면, 권한 처리를 중앙 집중화하면 Permission Denial 관련 버그 수가 평균 60% 감소합니다.

좋은 Permission Handler는 호출 코드에 깔끔한 인터페이스를 제공합니다. Activity나 Fragment는 요청의 세부 사항을 알 필요가 없습니다. requestCamera(callback)과 같은 메서드를 호출하면, 핸들러 자체가 상태 확인, 이유 표시, 시스템 대화 상자 호출 및 콜백으로 결과 전달을 관리합니다. 이는 단일 책임 원칙을 구현하고 비즈니스 로직을 플랫폼 권한 코드와 분리합니다.

Permission Handler가 필요한 경우

애플리케이션이 3개 이상의 위험한 권한을 사용할 때 Permission Handler가 필요합니다. 하나의 권한(예: QR 코드 스캐너용 카메라)만 있는 간단한 애플리케이션의 경우 직접 호출로 충분할 수 있습니다. 그러나 카메라, 위치 정보, 알림 및 저장소를 사용하는 일반적인 모바일 애플리케이션의 경우 중앙 집중식 핸들러가 유지 관리에 필수적입니다.

Permission Handler 아키텍처

일반적인 Permission Handler는 인터페이스 계약, ActivityResultLauncher를 사용한 구현, ViewModel 계층의 세 계층으로 구성됩니다. 인터페이스는 각 권한에 대한 요청 메서드를 정의합니다 — requestCamera, requestLocation, requestStorage. 구현은 이러한 메서드를 해당 ActivityResultContracts.RequestPermission 계약에 바인딩합니다.

주요 아키텍처 구성 요소:

  • PermissionHandlerContract — 각 권한에 대한 메서드가 있는 인터페이스
  • PermissionHandlerImpl — ActivityResultRegistry에 연결되는 구현
  • PermissionResult — GRANTED, DENIED, NEVER_ASK_AGAIN 상태를 가진 sealed 클래스
  • RationaleHandler — 요청 전에 설명을 표시하는 구성 요소

이 아키텍처를 사용하면 테스트에서 구현을 쉽게 교체할 수 있습니다. 실제 ActivityResultLauncher 대신 시스템 상호 작용 없이 미리 정의된 결과를 반환하는 모의 객체가 사용됩니다. 이는 권한 대화 상자를 위해 Activity를 시작할 수 없는 UI 로직의 단위 테스트에 매우 중요합니다.

라이프사이클 관리

Permission Handler는 Activity와 Fragment의 라이프사이클을 고려해야 합니다. 런처는 ActivityResultRegistry에 등록되며, 화면 회전 및 Activity 재생성 시 상태를 자동으로 저장하고 복원합니다. 핸들러는 Activity나 Fragment에 대한 직접 참조를 저장해서는 안 됩니다. 대신 WeakReference를 사용하거나 생성자를 통해 registry를 전달하세요. 이는 구성 변경 중 메모리 누수 및 충돌을 방지합니다.

Kotlin에서 Permission Handler 구현

Permission Handler의 기본 구현은 ActivityResultContracts.RequestPermission을 기반으로 구축됩니다. 핸들러는 ComponentActivity 또는 Fragment에서 ActivityResultRegistry를 받아 각 권한에 대한 런처를 등록합니다. 각 런처는 사용자가 응답한 후 호출되는 콜백 람다를 허용합니다.

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에서 초기화

핸들러는 ActivityResultRegistry에 대한 액세스를 제공하는 registerForActivityResult를 통해 Activity의 onCreate에서 초기화됩니다. 초기화 후 핸들러는 전체 Activity 라이프사이클 동안 요청을 처리할 준비가 됩니다. 첫 번째 요청 전에 initialize를 호출하는 것이 중요합니다. 그렇지 않으면 런처가 등록되지 않습니다.

ViewModel과 함께 사용하는 Permission Handler

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는 시작 시 Handler를 통해 isPermissionGranted를 확인하고, ViewModel은 상태만 관리합니다. 권한이 부여되지 않은 경우 Activity는 uiState를 구독하고, Handler의 requestCamera를 호출하며, onPermissionResult를 통해 결과를 ViewModel에 다시 전달합니다. 플랫폼 코드와 비즈니스 로직을 분리하면 Android 종속성 없이 ViewModel을 테스트할 수 있습니다.

Permission Handler 테스트

PermissionHandler 인터페이스 덕분에 Permission Handler의 단위 테스트가 가능합니다. 테스트에서는 권한 부여, 거부, Never Ask Again 등 다양한 시나리오를 시뮬레이션하는 FakePermissionHandler가 생성됩니다. 각 시나리오는 독립적으로 테스트됩니다. 이는 세 가지 결과 모두에 올바르게 반응해야 하는 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
    }
}

페이크 구현을 사용하면 에뮬레이터 없이 ViewModel을 테스트할 수 있습니다. cameraResult를 원하는 값으로 설정하고 ViewModel이 UiState를 올바르게 업데이트하는지 확인하기만 하면 됩니다. 통합 테스트는 ActivityScenario로 실제 PermissionHandler를 확인하지만, 일반적으로 애플리케이션당 2~3개의 테스트만 있으며 나머지 시나리오는 페이크를 사용한 단위 테스트로 처리됩니다.

일반적인 패턴 및 실수

Permission Handler 작업 시 일반적인 실수로는 각 API 호출 전 checkSelfPermission 확인 누락, shouldShowRequestPermissionRationale 무시, Never Ask Again 후 requestPermissions 재호출, Activity 라이프사이클을 고려하지 않고 런처 저장 등이 있습니다. 각 문제와 해결 방법을 살펴보겠습니다.

가장 흔한 실수는 권한 상태를 확인하지 않고 API를 호출하는 것입니다. 개발자는 한 번 권한이 부여되면 영원히 유지될 것이라고 가정합니다. 그러나 사용자는 언제든지 설정을 통해 권한을 취소할 수 있습니다. Permission Handler는 민감한 작업을 수행하기 전에 항상 isPermissionGranted를 호출해야 합니다. 두 번째로 흔한 실수는 shouldShowRequestPermissionRationale를 무시하고 요청을 반복하는 것이며, Never Ask Again 모드에서는 대화 상자 없이 즉시 거부됩니다.

모범 사례에는 전체 Activity 라이프사이클에 대해 단일 Handler 인스턴스 생성, 결과를 ViewModel에 전달하기 위해 SharedFlow 사용, 분석을 위한 모든 요청 및 거부 기록, 첫 번째 거부 시 시스템 대화 상자 전에 커스텀 이유 대화 상자 표시가 포함됩니다. 이러한 규칙을 따르면 모든 Android 버전에서 안정적인 권한 처리가 보장됩니다.

자주 묻는 질문

Android에서 Permission Handler란 무엇인가요?

Permission Handler는 런타임 권한을 중앙 집중식으로 관리하기 위한 구성 요소로, checkSelfPermission, requestPermissions 및 shouldShowRequestPermissionRationale를 캡슐화합니다. 코드 유지 관리를 간소화하고 테스트를 개선합니다.

2024년에 Handler에 어떤 API를 사용하나요?

androidx.activity 라이브러리의 ActivityResultContracts.RequestPermission을 사용하는 것이 좋습니다. 이는 더 이상 사용되지 않는 onRequestPermissionsResult를 대체하고 Boolean 결과와 함께 깔끔한 콜백 API를 제공합니다.

단일 권한에 Handler가 필요한가요?

단일 권한의 경우 Handler는 필수가 아닙니다. Activity에서 직접 RequestPermission 런처 호출을 사용할 수 있습니다. 코드 중복을 피하기 위해 3개 이상의 권한이 있을 때 Handler가 필요합니다.

Permission Handler를 어떻게 테스트하나요?

단위 테스트를 위해 PermissionHandler 인터페이스와 그 페이크 구현을 만드세요. 페이크는 시스템 호출 없이 미리 정의된 결과를 반환합니다. 이를 통해 에뮬레이터 없이 ViewModel 및 UI 로직을 테스트할 수 있습니다.

Handler에서 Never Ask Again을 어떻게 처리하나요?

거부 후 shouldShowRequestPermissionRationale를 확인하세요. 메서드가 false를 반환하면 Never Ask Again 모드가 활성화된 것입니다. Handler는 PermissionResult.DENIED(false)를 반환하고, UI는 설정으로 이동하는 버튼을 표시해야 합니다.

요약

  • Permission Handler는 Android 런타임 권한을 중앙 집중식으로 관리하기 위한 아키텍처 구성 요소입니다.
  • androidx.activity 라이브러리의 ActivityResultContracts.RequestPermission을 기반으로 합니다.
  • 각 권한에 대한 메서드가 있는 인터페이스는 페이크 구현을 통해 단위 테스트를 간소화합니다.
  • StateFlow를 통한 ViewModel과의 통합은 플랫폼 코드를 비즈니스 로직과 분리합니다.
  • 일반적인 실수: checkSelfPermission 누락, 이유 무시 및 Never Ask Again.
  • 모범 사례 — onCreate에 등록된 Activity당 하나의 Handler.
  • 중앙 집중화는 일반적인 프로젝트에서 Permission Denial 버그를 60% 줄입니다.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기