Android 앱 권한 처리 완벽 가이드 — Android 13~16 알림·사진·위치·카메라 권한 실전 구현

Android 앱을 개발하다 보면 카메라, 사진, 위치, 알림 등 다양한 기기 기능에 접근해야 합니다.

과거에는 AndroidManifest.xml에 권한을 선언하는 것만으로 사용할 수 있는 기능도 많았습니다.

하지만 Android 버전이 올라가면서 개인정보 보호와 사용자 선택권이 강화되었고, 권한을 요청하는 방법도 달라졌습니다.

특히 Android 13부터는 알림 권한이 런타임 권한으로 변경되었고, Android 14에서는 사진과 동영상에 대한 부분 접근 권한이 추가되었습니다.

이러한 변화를 제대로 처리하지 않으면 다음과 같은 문제가 발생할 수 있습니다.

  • Android 13 이상에서 알림이 표시되지 않음

  • 사진을 선택했는데 앱에서 파일에 접근하지 못함

  • 위치 권한을 허용했지만 정확한 위치를 얻지 못함

  • 권한을 거부한 뒤 기능이 동작하지 않음

  • 카메라 권한 요청이 불필요하게 반복됨

  • Google Play 심사에서 권한 사용 목적을 지적받음

이번 글에서는 Kotlin + XML 기반 Android 앱에서 ActivityResultContracts를 이용해 권한을 처리하는 방법을 알아보겠습니다.

작성 기준: 2026년 10월. 예제는 AndroidX Activity Result API를 사용하며, Android 13(API 33), Android 14(API 34), Android 15(API 35), Android 16(API 36) 환경을 고려합니다. 권한 동작은 OS 버전뿐 아니라 targetSdk, 기기 설정, 앱의 사용 방식에 따라 달라질 수 있습니다.


1. Android 권한의 종류

Android 권한은 크게 일반 권한과 위험 권한으로 구분할 수 있습니다.

일반 권한은 사용자의 개인정보나 민감한 기기 기능에 직접적인 영향을 주지 않는 경우가 많습니다.

위험 권한은 사용자의 개인정보나 민감한 기능에 접근하기 때문에 런타임 권한 요청이 필요할 수 있습니다.

권한주요 용도런타임 요청
INTERNET네트워크 통신불필요
ACCESS_NETWORK_STATE네트워크 상태 확인불필요
CAMERA카메라 직접 사용필요
ACCESS_FINE_LOCATION정밀 위치필요
ACCESS_COARSE_LOCATION대략적인 위치필요
POST_NOTIFICATIONS알림 표시Android 13 이상에서 필요
READ_MEDIA_IMAGES이미지 미디어 접근Android 13 이상에서 필요할 수 있음
READ_MEDIA_VIDEO동영상 미디어 접근Android 13 이상에서 필요할 수 있음
RECORD_AUDIO마이크 녹음필요

중요한 점은 모든 앱이 모든 권한을 요청해야 하는 것은 아니라는 사실입니다.

사용하는 기능에 필요한 권한만 선언하고 요청하는 것이 가장 좋습니다.

2. 권한 처리의 기본 흐름

런타임 권한을 사용하는 기능은 다음과 같은 순서로 구현합니다.

사용자가 기능 선택
        │
        ▼
현재 권한 상태 확인
        │
        ├── 허용됨
        │      └── 기능 실행
        │
        └── 허용되지 않음
               │
               ▼
       필요한 이유 안내
               │
               ▼
          권한 요청
               │
               ├── 허용
               │     └── 기능 실행
               │
               └── 거부
                     └── 대체 기능 또는 안내

앱을 실행하자마자 모든 권한을 한꺼번에 요청하는 방식은 권장하지 않습니다.

사용자가 해당 기능을 사용하려는 시점에 권한을 요청하는 것이 자연스럽습니다.


3. ActivityResultContracts를 사용하는 이유

과거 Android에서는 requestPermissions()와 onRequestPermissionsResult()를 이용해 권한을 처리했습니다.

현재는 AndroidX Activity Result API를 사용하는 것이 권장됩니다.

대표적으로 다음 계약을 사용할 수 있습니다.

  • ActivityResultContracts.RequestPermission()

  • ActivityResultContracts.RequestMultiplePermissions()

  • ActivityResultContracts.TakePicture()

  • ActivityResultContracts.PickVisualMedia()

  • ActivityResultContracts.GetContent()

ActivityResultContracts를 사용하면 권한 요청과 결과 처리를 하나의 Launcher로 구성할 수 있습니다.

4. 단일 권한 요청 구현하기

다음은 카메라 권한을 요청하는 기본 예제입니다.

private val cameraPermissionLauncher =
    registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { granted ->

        if (granted) {
            openCamera()
        } else {
            showCameraPermissionGuide()
        }
    }

권한을 요청할 때는 다음과 같이 호출합니다.

cameraPermissionLauncher.launch(
    Manifest.permission.CAMERA
)

registerForActivityResult()는 Activity 또는 Fragment의 적절한 초기화 단계에서 등록해야 합니다.

사용자가 버튼을 누를 때마다 Launcher를 새로 등록하는 방식은 피해야 합니다.


5. 권한 상태 확인하기

권한을 요청하기 전에 현재 상태를 확인합니다.

fun hasCameraPermission(
    context: Context
): Boolean {

    return ContextCompat.checkSelfPermission(
        context,
        Manifest.permission.CAMERA
    ) == PackageManager.PERMISSION_GRANTED
}

버튼 클릭 시 다음과 같이 처리합니다.

binding.buttonCamera.setOnClickListener {

    if (hasCameraPermission(requireContext())) {

        openCamera()

    } else {

        cameraPermissionLauncher.launch(
            Manifest.permission.CAMERA
        )
    }
}

이미 권한이 허용된 상태라면 불필요한 요청을 반복하지 않습니다.


6. Android 13 알림 권한 변경

Android 13(API 33)부터 일반적인 알림을 표시하려면 POST_NOTIFICATIONS 런타임 권한을 요청해야 합니다.

Manifest에 다음 권한을 선언합니다.

<uses-permission
    android:name="android.permission.POST_NOTIFICATIONS" />

단, 이 권한은 Android 13 이상에서 런타임으로 요청합니다.

Android 12 이하에서는 동일한 런타임 권한 요청이 필요하지 않습니다.

7. 알림 권한 요청 코드

private val notificationPermissionLauncher =
    registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { granted ->

        if (granted) {
            enableNotificationFeature()
        } else {
            showNotificationPermissionGuide()
        }
    }

권한 확인 함수입니다.

fun hasNotificationPermission(
    context: Context
): Boolean {

    return if (
        Build.VERSION.SDK_INT >=
        Build.VERSION_CODES.TIRAMISU
    ) {

        ContextCompat.checkSelfPermission(
            context,
            Manifest.permission.POST_NOTIFICATIONS
        ) == PackageManager.PERMISSION_GRANTED

    } else {
        true
    }
}

이 함수는 Android 13의 런타임 권한 허용 여부를 확인하는 예제입니다.

실제 알림 표시 가능 여부를 확인하려면 앱 전체 알림 차단 상태도 확인해야 합니다.

val enabled =
    NotificationManagerCompat
        .from(context)
        .areNotificationsEnabled()

Android 8.0 이상에서는 개별 NotificationChannel이 차단되어 있는지도 확인할 수 있습니다.


8. 알림 권한은 언제 요청해야 할까?

알림 권한은 앱을 처음 실행하자마자 요청하기보다 사용자가 알림 기능을 활성화하려는 시점에 요청하는 것이 좋습니다.

예를 들어 기념일 관리 앱이라면 다음과 같은 흐름이 자연스럽습니다.

사용자가 기념일 등록
        │
        ▼
'기념일 알림 받기' 선택
        │
        ▼
알림 기능 설명
        │
        ▼
POST_NOTIFICATIONS 요청

사용자가 알림을 거부했다면 반복적으로 권한 팝업을 띄우기보다 설정 화면에서 다시 활성화할 수 있도록 안내합니다.

또한 DataStore에 저장한 '알림 사용' 설정과 Android 시스템의 알림 권한은 서로 다른 상태입니다.

앱 설정이 켜져 있어도 시스템 권한이 거부되어 있으면 일반적인 알림을 표시할 수 없습니다.


9. Android 13 사진 접근 권한

Android 13에서는 기존 외부 저장소 읽기 권한 대신 미디어 유형별 권한이 도입되었습니다.

대표적으로 다음과 같습니다.

<uses-permission
    android:name="android.permission.READ_MEDIA_IMAGES" />

<uses-permission
    android:name="android.permission.READ_MEDIA_VIDEO" />

하지만 사진을 한두 장 선택하는 기능이라면 이러한 권한을 요청하지 않아도 되는 경우가 많습니다.

Android에서는 시스템 Photo Picker를 제공하기 때문입니다.

사진 선택만 필요한 앱이라면 Photo Picker를 우선 고려하는 것이 좋습니다.

10. Photo Picker의 장점

Photo Picker는 사용자가 선택한 사진이나 동영상에 앱이 접근할 수 있도록 지원합니다.

주요 장점은 다음과 같습니다.

  • 광범위한 사진 접근 권한 없이 사용 가능

  • 사용자가 직접 사진 선택

  • 앱이 전체 사진 라이브러리에 접근할 필요 없음

  • 개인정보 보호 측면에서 유리

  • Google Play 권한 정책 대응에 도움

사진 콜라주, 프로필 이미지 등록, 게시글 이미지 첨부와 같은 기능에 적합합니다.


11. Photo Picker 구현하기

ActivityResultContracts의 PickVisualMedia()를 사용할 수 있습니다.

private val photoPickerLauncher =
    registerForActivityResult(
        ActivityResultContracts.PickVisualMedia()
    ) { uri ->

        if (uri != null) {
            displaySelectedImage(uri)
        }
    }

사진 선택기를 실행합니다.

photoPickerLauncher.launch(
    PickVisualMediaRequest(
        ActivityResultContracts
            .PickVisualMedia
            .ImageOnly
    )
)

이 방식은 사용자가 선택한 이미지의 Uri를 반환합니다.

반환된 Uri는 파일 경로 문자열과 동일하지 않습니다.

따라서 직접 파일 경로로 변환하려 하기보다 ContentResolver를 이용해 접근하는 것이 좋습니다.

12. 선택한 이미지를 Glide로 표시하기

Glide를 사용한다면 Uri를 그대로 전달할 수 있습니다.

fun displaySelectedImage(
    uri: Uri
) {

    Glide.with(binding.imagePreview)
        .load(uri)
        .placeholder(
            R.drawable.ic_image_placeholder
        )
        .into(binding.imagePreview)
}

이렇게 하면 사용자가 선택한 이미지를 화면에 표시할 수 있습니다.

다만 Uri 접근 권한의 지속 기간은 선택 방식과 제공자에 따라 달라질 수 있습니다.

앱에서 장기간 사용해야 하는 이미지라면 적절한 지속 접근 권한을 확보하거나 앱 내부 저장소로 복사하는 방식을 검토해야 합니다.


13. 여러 장의 사진 선택하기

사진 콜라주 앱처럼 여러 장의 이미지를 선택해야 한다면 PickMultipleVisualMedia()를 사용할 수 있습니다.

private val multiplePhotoPickerLauncher =
    registerForActivityResult(
        ActivityResultContracts
            .PickMultipleVisualMedia(10)
    ) { uris ->

        if (uris.isNotEmpty()) {
            displaySelectedImages(uris)
        }
    }

실행 코드입니다.

multiplePhotoPickerLauncher.launch(
    PickVisualMediaRequest(
        ActivityResultContracts
            .PickVisualMedia
            .ImageOnly
    )
)

위 예제는 최대 10장 선택을 요청합니다.

다만 실제 선택 가능한 최대 개수와 동작은 기기 및 제공되는 선택기 구현에 영향을 받을 수 있습니다.


14. Android 14 사진 부분 접근 권한

Android 14(API 34)에서는 사용자가 앱에 전체 사진 라이브러리가 아닌 일부 사진과 동영상에만 접근을 허용할 수 있는 기능이 도입되었습니다.

이와 관련된 권한은 다음과 같습니다.

<uses-permission
    android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />

앱이 미디어 라이브러리에 직접 접근하는 구조라면 Android 14의 부분 접근 상태를 고려해야 합니다.

하지만 Photo Picker만 사용하는 앱이라면 일반적으로 이 권한을 별도로 요청할 필요가 없습니다.

따라서 사진 선택 기능을 구현하기 전에 다음을 먼저 결정하는 것이 좋습니다.

사용 목적권장 접근
프로필 사진 한 장 선택Photo Picker
게시글 이미지 첨부Photo Picker
사진 콜라주 이미지 선택Photo Picker
사용자가 지정한 사진 편집Photo Picker
전체 사진 라이브러리 관리필요한 미디어 권한 검토
사진 백업 및 동기화광범위한 접근 필요성 및 정책 검토

15. Android 14 부분 접근 상태 확인

전체 사진 접근과 부분 접근을 모두 지원해야 하는 앱에서는 권한 상태를 구분할 수 있습니다.

fun getImageAccessState(
    context: Context
): ImageAccessState {

    return when {

        Build.VERSION.SDK_INT >= 33 &&
        ContextCompat.checkSelfPermission(
            context,
            Manifest.permission.READ_MEDIA_IMAGES
        ) == PackageManager.PERMISSION_GRANTED -> {

            ImageAccessState.FULL
        }

        Build.VERSION.SDK_INT >= 34 &&
        ContextCompat.checkSelfPermission(
            context,
            Manifest.permission
                .READ_MEDIA_VISUAL_USER_SELECTED
        ) == PackageManager.PERMISSION_GRANTED -> {

            ImageAccessState.PARTIAL
        }

        else -> {
            ImageAccessState.NONE
        }
    }
}

상태 모델은 다음과 같습니다.

enum class ImageAccessState {
    FULL,
    PARTIAL,
    NONE
}

이 예제는 Android 13 이상 이미지 접근 상태의 일부를 단순화한 것입니다.

Android 12 이하의 READ_EXTERNAL_STORAGE 권한과 Android 14의 호환 모드까지 포함한 완전한 미디어 접근 상태 판별은 별도로 구현해야 합니다.

또한 부분 접근 권한은 앱이 백그라운드로 이동하거나 사용자가 선택 항목을 변경하면 접근 가능 범위가 달라질 수 있으므로, 이전에 확인한 상태를 영구적으로 신뢰해서는 안 됩니다.


16. Google Play 사진·동영상 권한 정책

Google Play에서는 사진과 동영상에 대한 광범위한 접근 권한을 사용하는 앱에 대해 사용 목적을 검토할 수 있습니다.

특히 READ_MEDIA_IMAGES, READ_MEDIA_VIDEO 같은 권한은 앱의 핵심 기능과 관련이 있는지 확인해야 합니다.

사진 한두 장을 선택하는 용도라면 Photo Picker를 사용하는 것이 더 적합할 수 있습니다.

다만 Photo Picker를 사용한다고 해서 앱의 모든 개인정보 보호 의무가 사라지는 것은 아닙니다.

선택한 사진을 서버로 업로드하거나 제3자에게 전송한다면 해당 데이터 처리 방식도 개인정보처리방침과 Data Safety 신고에 반영해야 합니다.


17. Android 위치 권한의 종류

위치 권한은 대표적으로 다음과 같이 구분됩니다.

<uses-permission
    android:name="android.permission.ACCESS_COARSE_LOCATION" />

<uses-permission
    android:name="android.permission.ACCESS_FINE_LOCATION" />

ACCESS_COARSE_LOCATION은 대략적인 위치 접근에 사용됩니다.

ACCESS_FINE_LOCATION은 더 정밀한 위치 접근에 사용됩니다.

하지만 FINE 권한을 요청했다고 해서 항상 GPS 수준의 정확한 위치가 제공되는 것은 아닙니다.

사용자는 Android 12 이상에서 대략적인 위치만 허용할 수 있으며, 실제 정확도는 기기와 환경에 따라 달라집니다.

18. 위치 권한을 함께 요청하기

Android 12 이상을 고려한다면 정밀 위치 권한과 대략적인 위치 권한을 함께 요청하는 것이 좋습니다.

private val locationPermissionLauncher =
    registerForActivityResult(
        ActivityResultContracts
            .RequestMultiplePermissions()
    ) { permissions ->

        val fineGranted =
            permissions[
                Manifest.permission.ACCESS_FINE_LOCATION
            ] == true

        val coarseGranted =
            permissions[
                Manifest.permission.ACCESS_COARSE_LOCATION
            ] == true

        when {

            fineGranted -> {
                startPreciseLocationFeature()
            }

            coarseGranted -> {
                startApproximateLocationFeature()
            }

            else -> {
                showLocationPermissionGuide()
            }
        }
    }

요청 코드는 다음과 같습니다.

locationPermissionLauncher.launch(
    arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )
)

사용자가 대략적인 위치만 허용했을 때도 앱이 정상적으로 동작할 수 있도록 대체 기능을 제공하는 것이 좋습니다.


19. 정밀 위치가 필요한 앱의 처리

예를 들어 GPS 이동 경로를 기록하는 앱이라면 정밀 위치가 필요할 수 있습니다.

하지만 권한이 허용되었다고 해서 항상 높은 정확도의 위치가 보장되지는 않습니다.

다음과 같은 요소가 영향을 줍니다.

  • GPS 수신 상태

  • 건물 내부 여부

  • 주변 건물과 지형

  • 기기 위치 서비스 설정

  • 배터리 절약 모드

  • 사용자가 선택한 위치 정확도

  • 위치 업데이트 주기

따라서 GPS 트래킹 앱에서는 권한 처리뿐 아니라 위치 정확도와 업데이트 품질을 별도로 검증해야 합니다.

20. 위치 권한 확인 함수

fun hasFineLocationPermission(
    context: Context
): Boolean {

    return ContextCompat.checkSelfPermission(
        context,
        Manifest.permission.ACCESS_FINE_LOCATION
    ) == PackageManager.PERMISSION_GRANTED
}

대략적인 위치 권한도 별도로 확인할 수 있습니다.

fun hasCoarseLocationPermission(
    context: Context
): Boolean {

    return ContextCompat.checkSelfPermission(
        context,
        Manifest.permission.ACCESS_COARSE_LOCATION
    ) == PackageManager.PERMISSION_GRANTED
}

위치 기능을 실행하기 직전에 현재 권한을 다시 확인하는 것이 안전합니다.

사용자는 앱 사용 중에도 시스템 설정에서 권한을 변경할 수 있기 때문입니다.


21. 백그라운드 위치 권한

앱이 화면에 표시되지 않는 상태에서도 위치를 수집해야 한다면 백그라운드 위치 접근이 필요할 수 있습니다.

관련 권한은 다음과 같습니다.

<uses-permission
    android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

하지만 이 권한은 매우 신중하게 사용해야 합니다.

Android에서는 백그라운드 위치 접근을 제한하고 있으며, Google Play에서도 사용 목적을 엄격하게 검토합니다.

특히 Android 11 이상에서는 일반적인 위치 권한 요청 대화상자에서 '항상 허용'을 직접 선택하는 방식이 제공되지 않습니다.

따라서 백그라운드 위치 권한은 전경 위치 권한과 별도의 사용자 안내 및 설정 흐름이 필요합니다.

22. 백그라운드 위치가 필요한 경우

예를 들어 다음과 같은 기능에서는 백그라운드 위치가 필요할 수 있습니다.

  • 화면이 꺼진 상태에서 이동 경로 기록

  • 사용자가 시작한 장시간 위치 추적

  • 특정 위치 진입·이탈 감지

  • 지속적인 위치 기반 서비스

하지만 단순히 현재 위치를 한 번 조회하는 앱이라면 백그라운드 위치 권한을 요청할 필요가 없습니다.

또한 Foreground Service를 사용한다고 해서 백그라운드 위치 권한 관련 제한이 모두 사라지는 것은 아닙니다.

Android 버전과 서비스 시작 시점, 서비스 유형에 따라 추가 제한이 적용될 수 있습니다.


23. Android 14 이상 위치 Foreground Service

Android 14 이상을 대상으로 하는 앱에서는 Foreground Service 유형과 관련 권한을 올바르게 선언해야 합니다.

위치 기반 Foreground Service를 사용하는 경우 다음과 같은 구성이 필요할 수 있습니다.

<uses-permission
    android:name="android.permission.FOREGROUND_SERVICE" />

<uses-permission
    android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />

Service 선언 예제입니다.

<service
    android:name=".service.LocationTrackingService"
    android:exported="false"
    android:foregroundServiceType="location" />

다만 이 선언만으로 위치 추적이 허용되는 것은 아닙니다.

위치 런타임 권한, 서비스 시작 제한, 사용자에게 표시할 알림, Google Play 정책 등을 함께 확인해야 합니다.

특히 앱이 백그라운드에 있는 상태에서 위치 Foreground Service를 새로 시작하는 동작에는 추가 제한이 있을 수 있습니다.


24. 카메라 권한 처리

카메라 기능을 구현할 때는 두 가지 방식을 구분해야 합니다.

앱이 카메라를 직접 제어하는 경우

CameraX나 Camera2를 이용해 카메라 프리뷰와 촬영 기능을 직접 구현한다면 일반적으로 CAMERA 권한이 필요합니다.

시스템 카메라 앱에 촬영을 위임하는 경우

시스템 카메라 앱을 실행해 사진 촬영을 위임하는 방식에서는 앱이 직접 CAMERA 권한을 요청하지 않아도 되는 구성이 가능합니다.

따라서 촬영 방식에 따라 필요한 권한을 결정해야 합니다.

25. CameraX 사용 시 권한 선언

Manifest에 다음과 같이 선언합니다.

<uses-permission
    android:name="android.permission.CAMERA" />

런타임 권한을 확인합니다.

if (
    ContextCompat.checkSelfPermission(
        requireContext(),
        Manifest.permission.CAMERA
    ) == PackageManager.PERMISSION_GRANTED
) {

    startCameraPreview()

} else {

    cameraPermissionLauncher.launch(
        Manifest.permission.CAMERA
    )
}

카메라 권한이 허용되면 CameraX 프리뷰를 시작하도록 구성할 수 있습니다.

다만 권한이 거부되었을 때 앱이 강제 종료되지 않도록 처리해야 합니다.


26. 시스템 카메라 앱으로 촬영하기

앱이 직접 카메라를 제어할 필요가 없다면 ActivityResultContracts.TakePicture()를 사용할 수 있습니다.

private val takePictureLauncher =
    registerForActivityResult(
        ActivityResultContracts.TakePicture()
    ) { success ->

        if (success) {
            displayCapturedImage()
        }
    }

촬영 전에 이미지를 저장할 Uri를 준비해야 합니다.

예를 들어 FileProvider를 이용해 앱 내부 파일의 Uri를 생성할 수 있습니다.

takePictureLauncher.launch(
    photoUri
)

여기서 photoUri는 사전에 생성된 유효한 Uri여야 합니다.

FileProvider의 Manifest 선언, 파일 경로 설정, Uri 권한 부여 등은 별도로 구성해야 합니다.

또한 CAMERA 권한을 Manifest에 선언한 앱에서는 시스템 카메라 위임 방식이라도 권한 상태에 따른 예외 상황이 발생할 수 있으므로, 직접 카메라 접근이 필요하지 않다면 불필요한 CAMERA 권한 선언을 제거하는 편이 좋습니다.


27. 권한 거부 후 처리하기

사용자가 권한을 거부했다고 해서 앱을 종료하거나 기능 전체를 차단할 필요는 없습니다.

예를 들어 다음과 같은 대체 동작을 제공할 수 있습니다.

권한거부 시 대체 기능
알림앱 내부 일정 목록 표시
카메라갤러리에서 사진 선택
정밀 위치대략적인 위치 사용
위치 전체 거부지역 수동 검색
사진 접근Photo Picker 제공

가능한 범위에서 권한 없이 사용할 수 있는 기능을 제공하면 사용자 경험이 좋아집니다.

28. shouldShowRequestPermissionRationale 사용하기

Android에서는 권한 요청 전에 추가 설명이 필요한지 확인할 수 있습니다.

val shouldExplain =
    shouldShowRequestPermissionRationale(
        Manifest.permission.CAMERA
    )

Fragment에서 사용하는 경우의 예제입니다.

if (
    shouldShowRequestPermissionRationale(
        Manifest.permission.CAMERA
    )
) {

    showCameraPermissionExplanation()

} else {

    cameraPermissionLauncher.launch(
        Manifest.permission.CAMERA
    )
}

다만 shouldShowRequestPermissionRationale()이 false라고 해서 사용자가 영구적으로 권한을 거부했다고 단정할 수는 없습니다.

처음 권한을 요청하는 경우에도 false가 반환될 수 있습니다.

따라서 권한 요청 이력과 현재 권한 상태를 함께 고려해야 합니다.


29. 권한이 영구적으로 거부된 경우

사용자가 반복적으로 권한을 거부하거나 시스템에서 권한 요청을 더 이상 표시하지 않는 상태가 될 수 있습니다.

이 경우 앱 설정 화면으로 이동하도록 안내할 수 있습니다.

fun openAppSettings(
    context: Context
) {

    val intent = Intent(
        Settings.ACTION_APPLICATION_DETAILS_SETTINGS
    ).apply {

        data = Uri.parse(
            "package:${context.packageName}"
        )
    }

    context.startActivity(intent)
}

이 코드는 앱의 시스템 설정 화면을 엽니다.

사용자가 직접 권한을 변경할 수 있도록 안내할 때 사용할 수 있습니다.

단, 권한이 거부될 때마다 무조건 설정 화면으로 이동시키는 것은 피해야 합니다.


30. Android 13~16 권한 변경 요약

Android 버전주요 변경사항
Android 12(API 31)대략적인 위치 선택, Bluetooth 런타임 권한 등
Android 13(API 33)알림 런타임 권한, 미디어 유형별 접근 권한
Android 14(API 34)사진·동영상 부분 접근, Foreground Service 유형 관련 제한 강화
Android 15(API 35)백그라운드 실행 및 Foreground Service 관련 제한 추가
Android 16(API 36)권한 및 백그라운드 실행 동작의 최신 변경사항 확인 필요

Android 15와 Android 16에서는 단순히 새로운 권한 이름만 확인할 것이 아니라, Foreground Service와 백그라운드 실행 제한, targetSdk에 따른 동작 변경을 함께 점검해야 합니다.

실제 앱 배포 전에는 Android Developers의 해당 버전 동작 변경 문서를 확인하는 것이 좋습니다.


31. 권한 요청과 Google Play Data Safety

권한을 요청한다고 해서 모든 데이터가 자동으로 수집되는 것은 아닙니다.

하지만 권한을 통해 접근한 데이터를 앱이 어떻게 사용하는지는 별도로 검토해야 합니다.

예를 들어 다음과 같은 차이가 있습니다.

위치 권한을 요청하지만 서버로 전송하지 않는 앱

위치 정보를 기기 내부에서만 사용하는 경우입니다.

위치 정보를 서버에 전송하는 앱

위치 데이터를 외부 서버로 전송하거나 저장하는 경우입니다.

두 앱은 데이터 처리 방식이 다르므로 Data Safety 신고 내용도 달라질 수 있습니다.

사진, 위치, 연락처, 마이크 등의 데이터는 실제 수집·공유 방식에 따라 신고해야 합니다.

또한 권한 사용 목적은 개인정보처리방침과 앱 내 안내 문구에서도 일관되게 설명하는 것이 좋습니다.


32. 권한을 최소화하는 설계

앱에서 필요한 기능을 구현할 때는 먼저 권한이 없는 대체 API가 있는지 확인하는 것이 좋습니다.

예를 들어 다음과 같습니다.

기능우선 검토할 방법
사진 한 장 선택Photo Picker
여러 사진 선택PickMultipleVisualMedia
사진 촬영 위임TakePicture
현재 위치 조회전경 위치 권한
지역 검색사용자 직접 입력
기념일 알림POST_NOTIFICATIONS
앱 내부 메시지별도 알림 권한 불필요

권한을 적게 요청하면 구현과 테스트가 단순해지고, 사용자에게 설명해야 할 개인정보 접근 범위도 줄어듭니다.


33. MVVM에서 권한 처리는 어디에 구현할까?

MVVM 구조에서는 권한 요청과 비즈니스 로직을 분리하는 것이 좋습니다.

예를 들어 다음과 같은 구조입니다.

Fragment
   │
   ├── 권한 상태 확인
   ├── 권한 요청 Launcher
   └── 사용자 안내 UI
           │
           ▼
       ViewModel
           │
           ▼
       Repository
           │
           ▼
       실제 기능 실행

권한 요청 Launcher는 Activity 또는 Fragment에서 관리하는 것이 자연스럽습니다.

ViewModel은 권한 요청 결과에 따라 필요한 기능을 실행하도록 상태를 관리할 수 있습니다.

다만 ViewModel이 Activity나 Fragment를 직접 참조하는 구조는 피하는 것이 좋습니다.

34. 권한 상태를 ViewModel에 전달하기

예를 들어 위치 권한이 허용되면 다음과 같이 ViewModel에 전달할 수 있습니다.

private val locationPermissionLauncher =
    registerForActivityResult(
        ActivityResultContracts
            .RequestMultiplePermissions()
    ) { result ->

        val fineGranted =
            result[
                Manifest.permission.ACCESS_FINE_LOCATION
            ] == true

        val coarseGranted =
            result[
                Manifest.permission.ACCESS_COARSE_LOCATION
            ] == true

        viewModel.onLocationPermissionResult(
            fineGranted = fineGranted,
            coarseGranted = coarseGranted
        )
    }

ViewModel에서는 결과를 바탕으로 기능 상태를 변경합니다.

fun onLocationPermissionResult(
    fineGranted: Boolean,
    coarseGranted: Boolean
) {

    when {

        fineGranted -> {
            // 정밀 위치 기능 사용
        }

        coarseGranted -> {
            // 대략적인 위치 기능 사용
        }

        else -> {
            // 위치 기능 비활성화
        }
    }
}

이렇게 하면 권한 요청 자체와 기능 상태 관리를 구분할 수 있습니다.


35. 권한 상태를 DataStore에 저장해도 될까?

권한 허용 여부를 DataStore에 저장해 사용하는 것은 주의해야 합니다.

사용자는 Android 시스템 설정에서 언제든 권한을 변경할 수 있습니다.

따라서 DataStore에 저장한 과거 권한 상태를 현재 권한 상태로 간주해서는 안 됩니다.

권한 허용 여부는 ContextCompat.checkSelfPermission() 등을 이용해 실제 시스템 상태를 확인해야 합니다.

반면 다음과 같은 정보는 DataStore에 저장할 수 있습니다.

  • 사용자가 기능 설명을 확인했는지

  • 권한 요청 안내를 이미 표시했는지

  • 앱 내부에서 알림 기능을 사용하도록 설정했는지

  • 권한 없이 사용할 대체 기능을 선택했는지

즉, 시스템 권한 상태와 앱의 사용자 설정은 구분해야 합니다.


36. Android 권한 테스트 방법

권한 기능은 Android 버전별로 테스트해야 합니다.

최소한 다음 환경을 고려하는 것이 좋습니다.

테스트 환경확인할 내용
Android 7(API 24)기본 런타임 권한
Android 12(API 31)정밀·대략 위치 선택
Android 13(API 33)알림 및 미디어 권한
Android 14(API 34)사진 부분 접근
Android 15(API 35)백그라운드 실행 제한
Android 16(API 36)최신 targetSdk 동작
권한 허용정상 기능 실행
권한 거부오류 없이 대체 기능 제공
설정에서 권한 변경앱 복귀 후 상태 반영
앱 업데이트기존 권한 상태 변화 확인

특히 새로 설치한 앱과 기존 버전에서 업데이트한 앱의 권한 동작은 다를 수 있습니다.

두 상황을 구분해 테스트해야 합니다.

37. ADB로 권한 상태 테스트하기

개발 중에는 ADB로 권한을 허용하거나 취소할 수 있습니다.

예를 들어 알림 권한을 취소합니다.

adb shell pm revoke \
  com.example.app \
  android.permission.POST_NOTIFICATIONS

권한을 허용합니다.

adb shell pm grant \
  com.example.app \
  android.permission.POST_NOTIFICATIONS

패키지 이름은 실제 앱의 Application ID로 변경해야 합니다.

또한 OS 버전, 권한 선언 상태, 권한 플래그에 따라 명령이 실패하거나 추가 상태 초기화가 필요할 수 있습니다.

권한 요청 흐름을 처음부터 테스트하려면 앱 데이터를 초기화하거나 권한 플래그를 적절히 재설정해야 할 수 있습니다.


38. 자주 발생하는 문제와 해결 방법

문제 1. Android 13에서 알림이 표시되지 않음

원인:

POST_NOTIFICATIONS 권한이 허용되지 않았거나 앱 또는 알림 채널이 차단된 경우입니다.

해결:

런타임 권한과 areNotificationsEnabled(), NotificationChannel 상태를 확인합니다.

문제 2. Android 14에서 일부 사진만 보임

원인:

사용자가 선택한 사진에 대해서만 접근을 허용했을 수 있습니다.

해결:

부분 접근 권한을 고려하거나 Photo Picker를 사용합니다.

문제 3. 정밀 위치 권한을 요청했는데 위치가 부정확함

원인:

사용자가 대략적인 위치만 허용했거나 위치 환경이 좋지 않을 수 있습니다.

해결:

FINE 권한 상태와 실제 위치 정확도를 함께 확인합니다.

문제 4. 권한을 거부한 뒤 팝업이 다시 표시되지 않음

원인:

권한 요청 이력이나 시스템 정책에 따라 요청 대화상자가 표시되지 않을 수 있습니다.

해결:

권한 상태와 요청 이력을 확인하고 필요한 경우 앱 설정 화면으로 이동하도록 안내합니다.

문제 5. 사진 Uri를 파일 경로로 변환할 수 없음

원인:

Content Uri를 일반 파일 경로처럼 처리했기 때문입니다.

해결:

ContentResolver를 사용하거나 앱 내부 저장소로 복사합니다.

문제 6. Google Play에서 미디어 권한 사용 목적을 지적받음

원인:

앱의 핵심 기능에 비해 지나치게 넓은 사진 접근 권한을 요청했을 수 있습니다.

해결:

Photo Picker 등 권한이 적게 필요한 API로 대체할 수 있는지 검토합니다.


39. 개인 개발자에게 추천하는 권한 처리 순서

권한 처리 코드를 한 번에 복잡하게 만들기보다 다음 순서로 구현하는 것이 좋습니다.

1단계: 기능별 필요 권한 정리

카메라, 사진, 위치, 알림 기능에 어떤 권한이 필요한지 확인합니다.

2단계: 권한 없는 대체 API 검토

Photo Picker와 시스템 카메라 위임 방식 등을 우선 검토합니다.

3단계: Manifest 최소 선언

실제로 필요한 권한만 선언합니다.

4단계: ActivityResultContracts 구현

권한 요청과 결과 처리를 구성합니다.

5단계: 권한 거부 대응

대체 기능과 사용자 안내를 제공합니다.

6단계: MVVM 구조 분리

Fragment에서 권한 요청을 처리하고 ViewModel에서 기능 상태를 관리합니다.

7단계: Android 버전별 테스트

Android 12~16의 주요 동작 차이를 확인합니다.

8단계: Google Play 정책 점검

권한 사용 목적과 데이터 처리 방식을 확인합니다.


40. Android 권한 처리 체크리스트

  • 필요한 권한만 Manifest에 선언

  • 권한 없이 사용할 수 있는 API 검토

  • ActivityResultContracts 사용

  • 권한 요청 전 현재 상태 확인

  • 기능 사용 시점에 권한 요청

  • 권한 사용 목적 설명

  • Android 13 알림 권한 처리

  • 앱 알림 차단 상태 확인

  • NotificationChannel 차단 상태 확인

  • Photo Picker 우선 검토

  • Android 14 사진 부분 접근 처리

  • Content Uri 안전하게 처리

  • 정밀·대략 위치 권한 구분

  • 백그라운드 위치 권한 최소화

  • Foreground Service 유형 확인

  • 카메라 직접 사용과 촬영 위임 구분

  • 권한 거부 시 대체 기능 제공

  • 권한 요청 반복 방지

  • 시스템 설정 이동 기능 제공

  • ViewModel에서 Activity 참조 방지

  • 권한 상태를 DataStore 값으로 대체하지 않기

  • Android 버전별 테스트

  • 신규 설치와 업데이트 테스트

  • Google Play 권한 정책 확인

  • 개인정보처리방침 및 Data Safety 점검

41. 마무리

Android 권한 처리는 단순히 권한 요청 팝업을 표시하는 작업이 아닙니다.

앱이 어떤 데이터에 접근하는지, 언제 접근하는지, 사용자가 거부했을 때 어떻게 동작하는지까지 함께 설계해야 합니다.

특히 Android 13~16을 지원하는 앱에서는 다음 원칙이 중요합니다.

  1. 필요한 권한만 요청합니다.

  2. 권한 없이 사용할 수 있는 시스템 API를 우선 검토합니다.

  3. ActivityResultContracts로 권한 요청을 구현합니다.

  4. Android 13 알림 권한을 별도로 처리합니다.

  5. 사진 선택에는 Photo Picker를 우선 사용합니다.

  6. Android 14 사진 부분 접근 상태를 고려합니다.

  7. 정밀 위치와 대략적인 위치를 구분합니다.

  8. 백그라운드 위치 권한은 꼭 필요한 경우에만 사용합니다.

  9. 권한 거부 후에도 가능한 기능을 제공합니다.

  10. Google Play 정책과 실제 데이터 처리 방식을 일치시킵니다.

개인 개발자가 여러 Android 앱을 운영한다면 권한 처리 로직을 공통 유틸리티나 재사용 가능한 컴포넌트로 정리하는 것도 좋은 방법입니다.

다만 모든 권한을 하나의 범용 함수로 처리하기보다 카메라, 사진, 위치, 알림처럼 사용 목적과 결과 처리가 다른 기능은 구분하는 것이 좋습니다.

Android 권한 처리의 핵심은 권한을 최대한 많이 확보하는 것이 아니라, 사용자가 이해하고 선택할 수 있는 방식으로 필요한 접근만 요청하는 것입니다.


참고 자료

태그: Android, Kotlin, Android 13, Android 14, Android 15, Android 16, ActivityResultContracts, 런타임 권한, POST_NOTIFICATIONS, Photo Picker, READ_MEDIA_IMAGES, 위치 권한, CAMERA, Google Play 정책, 개인정보 보호