Android 앱 권한 관리 완벽 가이드 — 사진·카메라·위치·알림 권한과 Android 13~16 대응

Android 앱을 개발할 때 권한 처리는 반드시 고려해야 하는 기능입니다.

카메라로 사진을 촬영하거나, 갤러리에서 이미지를 선택하거나, 현재 위치를 지도에 표시하거나, 사용자에게 알림을 보내려면 Android의 권한 모델을 이해해야 합니다.

그런데 Android 권한 정책은 운영체제 버전에 따라 계속 변경되고 있습니다.

대표적으로 다음과 같은 변화가 있었습니다.

  • Android 6.0: 런타임 권한 도입

  • Android 10: 백그라운드 위치 권한 분리

  • Android 11: 일회성 위치 권한 및 권한 자동 초기화 확대

  • Android 12: 대략적인 위치와 정확한 위치 선택

  • Android 13: 알림 권한과 사진·동영상 미디어 권한 분리

  • Android 14: 선택한 사진과 동영상에 대한 부분 접근

  • Android 15·16: 기존 권한 모델을 유지하면서 개인정보 보호 및 백그라운드 실행 관련 제약 지속

특히 Android 13 이후에는 기존의 저장소 권한 요청 코드가 의도대로 동작하지 않는 상황이 발생할 수 있습니다.

이번 글에서는 Kotlin, XML, Fragment, MVVM 기반 앱에서 권한을 안전하게 처리하는 방법을 실제 코드와 함께 살펴보겠습니다.

작성 기준: 2026년 10월. 예제는 minSdk 24 이상을 가정합니다. 권한 동작은 Android 버전뿐 아니라 targetSdk, 기기 제조사, 사용자 설정에 따라 달라질 수 있으므로 실제 지원 기기에서 테스트해야 합니다.


1. Android 권한의 종류

Android 권한은 크게 다음과 같이 구분할 수 있습니다.

종류특징예시
일반 권한설치 시 자동 부여되는 경우가 많음INTERNET
위험 권한사용자에게 런타임 허용 요청CAMERA, 위치
특별 접근 권한별도 설정 화면이나 절차 필요모든 파일 접근, 정확한 알람
역할·시스템 제한 권한특정 역할 또는 시스템 조건 필요일부 통화·SMS 관련 권한

모든 권한이 같은 방식으로 처리되는 것은 아닙니다.

예를 들어 인터넷 통신을 위한 INTERNET 권한은 일반적으로 런타임 요청이 필요하지 않습니다.

반면 카메라를 직접 사용하는 앱에서는 CAMERA 런타임 권한을 요청해야 합니다.

2. Manifest 권한과 런타임 권한의 차이

AndroidManifest.xml에 권한을 선언하는 것과 사용자에게 권한을 허용받는 것은 다릅니다.

예를 들어 다음 코드를 작성했다고 가정하겠습니다.

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

이 코드는 앱이 카메라 권한을 사용한다고 선언합니다.

하지만 Android 6.0 이상에서 위험 권한을 사용하는 앱이라면 런타임 허용 여부도 확인해야 합니다.

AndroidManifest.xml
        │
        ▼
권한 선언
        │
        ▼
런타임 권한 확인
        │
        ├── 허용됨 → 기능 실행
        │
        └── 허용 안 됨 → 사용자에게 요청

따라서 Manifest 선언만으로 권한 처리가 완료되었다고 생각해서는 안 됩니다.


3. 기존 requestPermissions 방식의 한계

과거에는 다음과 같은 코드를 많이 사용했습니다.

ActivityCompat.requestPermissions(
    requireActivity(),
    arrayOf(Manifest.permission.CAMERA),
    100
)

그리고 onRequestPermissionsResult()에서 결과를 처리했습니다.

이 방식도 존재하지만, 새 프로젝트에서는 Activity Result API를 사용하는 것이 권장됩니다.

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

ActivityResultContracts.RequestPermission()

여러 권한을 요청할 때는 다음 계약을 사용합니다.

ActivityResultContracts.RequestMultiplePermissions()

Activity Result API를 사용하면 권한 요청과 결과 처리를 한곳에서 관리하기 쉬워집니다.


4. ActivityResultContracts.RequestPermission

Fragment에서 카메라 권한을 요청하는 기본 예제입니다.

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

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

권한이 허용되면 openCamera()를 실행합니다.

거부되면 사용자에게 권한이 필요한 이유를 설명할 수 있습니다.

여기서 중요한 점은 권한 요청 런처를 Fragment의 안정적인 초기화 시점에 등록해야 한다는 것입니다.

일반적으로 Fragment의 프로퍼티로 선언하거나 onCreate()에서 등록할 수 있습니다.


5. 카메라 권한 확인하기

권한이 이미 허용되어 있는지 확인할 때는 ContextCompat.checkSelfPermission()을 사용할 수 있습니다.

private fun hasCameraPermission(): Boolean {

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

카메라 버튼을 클릭하면 다음과 같이 처리합니다.

binding.buttonCamera.setOnClickListener {

    if (hasCameraPermission()) {

        openCamera()

    } else {

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

이 구조에서는 사용자가 실제로 카메라 기능을 선택했을 때 권한을 요청합니다.

앱 실행 직후 사용하지도 않을 권한을 한꺼번에 요청하는 방식보다 사용자에게 권한의 목적을 설명하기 쉽습니다.


6. 권한은 필요한 시점에 요청하기

권한 요청 시점은 사용자 경험에 큰 영향을 줍니다.

예를 들어 조류 도감 앱에서 카메라 기능을 제공한다고 가정하겠습니다.

앱을 실행하자마자 카메라 권한을 요청하면 사용자는 왜 권한이 필요한지 이해하기 어려울 수 있습니다.

반면 사용자가 '사진 촬영' 버튼을 누른 직후 요청하면 목적이 명확해집니다.

권장 흐름은 다음과 같습니다.

사용자 기능 선택
       │
       ▼
권한 필요성 확인
       │
       ▼
이미 허용되었는가?
       │
       ├── YES → 기능 실행
       │
       └── NO
             │
             ▼
        권한 요청
             │
             ├── 허용 → 기능 실행
             └── 거부 → 대체 기능 안내

권한 요청은 기능과 연결되어 있어야 합니다.


7. 권한 요청 이유 설명하기

Android에서는 다음 API를 제공합니다.

shouldShowRequestPermissionRationale(
    Manifest.permission.CAMERA
)

이 함수는 권한을 다시 요청하기 전에 설명 UI를 제공하는 것이 적절한지 판단하는 데 도움이 됩니다.

예를 들어 다음과 같이 처리할 수 있습니다.

private fun requestCameraPermission() {

    val permission = Manifest.permission.CAMERA

    if (
        shouldShowRequestPermissionRationale(permission)
    ) {

        MaterialAlertDialogBuilder(requireContext())
            .setTitle("카메라 권한")
            .setMessage(
                "사진 촬영 기능을 사용하려면 " +
                "카메라 접근 권한이 필요합니다."
            )
            .setPositiveButton("계속") { _, _ ->

                cameraPermissionLauncher.launch(
                    permission
                )
            }
            .setNegativeButton("취소", null)
            .show()

    } else {

        cameraPermissionLauncher.launch(
            permission
        )
    }
}

단, shouldShowRequestPermissionRationale()이 false라고 해서 사용자가 반드시 '다시 묻지 않음'을 선택했다고 판단할 수는 없습니다.

최초 요청 전에도 false일 수 있기 때문입니다.


8. 권한을 거부했을 때 처리하기

사용자가 권한을 거부했다고 해서 앱이 종료되거나 모든 기능이 막혀서는 안 됩니다.

예를 들어 카메라 권한을 거부했다면 다음과 같은 대안을 제공할 수 있습니다.

  • 카메라 촬영 대신 기존 사진 선택

  • 사진 없이 메모 작성

  • 나중에 다시 권한 요청

  • 필요한 경우 앱 설정에서 권한 변경 안내

권한이 없는 상태에서도 가능한 기능은 계속 사용할 수 있도록 구성하는 것이 좋습니다.

특히 권한을 반복적으로 요청하는 무한 루프는 피해야 합니다.


9. 앱 설정 화면으로 이동하기

사용자가 권한을 반복적으로 거부했거나 시스템에서 권한 요청이 더 이상 표시되지 않는 상황에서는 앱 설정 화면으로 이동하도록 안내할 수 있습니다.

private fun openAppSettings() {

    val intent = Intent(
        Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
        Uri.parse(
            "package:${requireContext().packageName}"
        )
    )

    startActivity(intent)
}

이때는 바로 설정 화면을 여는 것보다 먼저 사용자에게 이유를 설명하는 것이 좋습니다.

예를 들어 다음과 같은 안내입니다.

카메라 권한이 허용되지 않았습니다.

사진 촬영 기능을 사용하려면
Android 앱 설정에서 카메라 권한을
허용해 주세요.

[취소] [설정 열기]

또한 설정 화면에서 돌아온 뒤에는 권한 상태를 다시 확인해야 합니다.


Android 13 이상 알림 권한

10. POST_NOTIFICATIONS 권한

Android 13(API 33)부터 일반 알림을 표시하려면 런타임 알림 권한이 필요한 경우가 많습니다.

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

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

Android 12 이하에서는 이 권한에 대한 런타임 요청을 수행하지 않습니다.

따라서 버전 분기가 필요합니다.

if (
    Build.VERSION.SDK_INT >=
    Build.VERSION_CODES.TIRAMISU
) {
    // Android 13 이상
}

참고로 일부 알림 유형은 권한 관련 예외가 있으며, Foreground Service의 동작도 일반 알림과 완전히 동일하지 않습니다.


11. 알림 권한 요청 구현

먼저 런처를 등록합니다.

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

        if (granted) {
            enableNotifications()
        } else {
            showNotificationDisabledMessage()
        }
    }

그리고 다음과 같이 요청합니다.

private fun requestNotificationPermission() {

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

        notificationPermissionLauncher.launch(
            Manifest.permission.POST_NOTIFICATIONS
        )

    } else {

        enableNotifications()
    }
}

실제 프로젝트에서는 권한 요청 전에 현재 허용 상태를 확인하는 것이 좋습니다.

또한 사용자가 알림 기능을 켜거나 알림의 필요성을 이해할 수 있는 시점에 요청하는 것이 적절합니다.


12. 알림 권한과 알림 채널

Android 8.0(API 26)부터는 알림 채널이 도입되었습니다.

따라서 Android 13 이상에서는 알림 권한뿐 아니라 알림 채널도 고려해야 합니다.

private fun createNotificationChannel(
    context: Context
) {

    if (
        Build.VERSION.SDK_INT >=
        Build.VERSION_CODES.O
    ) {

        val channel = NotificationChannel(
            "bird_updates",
            "조류 정보 알림",
            NotificationManager.IMPORTANCE_DEFAULT
        )

        val manager =
            context.getSystemService(
                NotificationManager::class.java
            )

        manager.createNotificationChannel(channel)
    }
}

알림 권한이 허용되어 있어도 사용자가 특정 알림 채널을 비활성화했다면 해당 채널의 알림이 표시되지 않을 수 있습니다.

따라서 앱 내부 알림 스위치와 시스템 알림 설정을 구분해야 합니다.


사진 및 동영상 접근 권한

13. Android 사진 접근 권한의 변화

Android의 미디어 접근 방식은 버전에 따라 달라졌습니다.

Android 버전주요 접근 방식
Android 12 이하필요한 경우 READ_EXTERNAL_STORAGE
Android 13READ_MEDIA_IMAGES, READ_MEDIA_VIDEO
Android 14 이상선택한 사진·동영상 부분 접근 지원
Android 15·16Photo Picker 및 부분 접근 모델 활용

하지만 사진을 선택하기 위해 반드시 저장소 권한을 요청해야 하는 것은 아닙니다.

많은 앱에서는 Android Photo Picker를 사용하는 것이 더 적합합니다.


14. Photo Picker를 우선 고려하기

사용자가 갤러리에서 사진 한 장을 선택하는 기능이라면 Photo Picker를 사용할 수 있습니다.

이 방식은 앱이 사용자의 전체 사진 라이브러리에 접근할 필요가 없습니다.

대표적인 장점은 다음과 같습니다.

  • 전체 미디어 접근 권한 없이 사진 선택 가능

  • 사용자가 직접 선택한 사진만 전달

  • 개인정보 접근 범위 최소화

  • 최신 Android 저장소 정책 대응에 유리

예를 들어 프로필 이미지 선택, 메모 사진 첨부, 조류 관찰 사진 등록 등에 적합합니다.


15. Photo Picker 구현하기

AndroidX Activity Result API를 사용할 수 있습니다.

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

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

사진 선택 버튼에서는 다음과 같이 호출합니다.

binding.buttonSelectPhoto.setOnClickListener {

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

선택한 Uri는 Glide로 표시할 수 있습니다.

private fun displaySelectedImage(uri: Uri) {

    Glide.with(this)
        .load(uri)
        .into(binding.imagePreview)
}

Photo Picker는 지원되는 환경에서 시스템 선택 UI를 사용하며, AndroidX가 제공하는 호환 경로가 사용될 수도 있습니다.

따라서 모든 기기에서 완전히 동일한 화면이 표시된다고 가정해서는 안 됩니다.


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

여러 사진을 선택하려면 PickMultipleVisualMedia를 사용할 수 있습니다.

private val multiplePhotoPicker =
    registerForActivityResult(
        ActivityResultContracts.PickMultipleVisualMedia(5)
    ) { uris ->

        uris.forEach { uri ->
            // 선택한 사진 처리
        }
    }

이 예제는 최대 5장의 사진 선택을 요청합니다.

하지만 실제 선택 가능 개수는 사용되는 Photo Picker 구현과 폴백 동작에 따라 달라질 수 있습니다.

따라서 반환된 Uri 목록의 개수도 확인하는 것이 좋습니다.


17. READ_MEDIA_IMAGES 권한

앱이 사용자가 선택한 사진만 처리하는 것이 아니라 미디어 라이브러리를 직접 조회해야 한다면 Android 13 이상에서 미디어 권한이 필요할 수 있습니다.

Manifest에 다음과 같이 선언할 수 있습니다.

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

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

다만 사진만 필요한 앱에서 동영상 권한까지 요청해서는 안 됩니다.

필요한 미디어 유형만 요청하는 것이 좋습니다.

또한 단순 사진 첨부 기능이라면 이러한 권한을 요청하기 전에 Photo Picker로 충분한지 먼저 검토해야 합니다.


18. Android 14 선택한 사진 접근 권한

Android 14(API 34)부터는 사용자가 앱에 전체 사진 접근을 허용하는 대신 선택한 사진과 동영상에만 접근하도록 제한할 수 있습니다.

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

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

이 권한은 앱이 직접 미디어 라이브러리를 조회하는 방식에서 부분 접근을 처리할 때 중요합니다.

Android 14 이상에서는 다음과 같은 상태를 구분해야 할 수 있습니다.

미디어 접근 상태

전체 접근 허용
선택한 사진만 허용
접근 거부

따라서 기존처럼 권한이 허용되었는지만 Boolean 값으로 확인하는 구조는 충분하지 않을 수 있습니다.


19. 미디어 권한 Manifest 예제

Android 13 이상과 이전 버전을 함께 지원한다면 다음과 같이 선언할 수 있습니다.

<uses-permission
    android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />

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

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

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

다만 이 예제는 여러 종류의 미디어를 직접 조회해야 하는 앱을 가정한 것입니다.

모든 앱에 위 권한을 전부 선언하는 것은 권장하지 않습니다.

Photo Picker만 사용하는 앱이라면 미디어 읽기 권한이 필요하지 않은 경우가 많습니다.


20. WRITE_EXTERNAL_STORAGE는 필요한가?

과거 Android 앱에서는 외부 저장소에 파일을 저장하기 위해 다음 권한을 사용했습니다.

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

하지만 최신 Android에서는 Scoped Storage와 MediaStore, Storage Access Framework 등을 사용합니다.

따라서 최신 Android를 대상으로 하는 앱에서 WRITE_EXTERNAL_STORAGE를 일반적인 파일 저장 방법으로 사용해서는 안 됩니다.

예를 들어 사용자가 파일 저장 위치를 직접 선택하는 기능이라면 CreateDocument를 사용할 수 있습니다.


21. Storage Access Framework 활용

사용자가 직접 파일을 선택하거나 저장 위치를 지정하도록 하려면 Storage Access Framework를 사용할 수 있습니다.

예를 들어 JSON 파일을 선택할 때는 다음과 같이 구현할 수 있습니다.

private val documentLauncher =
    registerForActivityResult(
        ActivityResultContracts.OpenDocument()
    ) { uri ->

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

실행 코드는 다음과 같습니다.

documentLauncher.launch(
    arrayOf("application/json")
)

이 방식에서는 일반적인 전체 저장소 접근 권한이 필요하지 않습니다.

다만 Uri 접근 권한의 지속 여부와 파일 제공자의 동작을 고려해야 합니다.


카메라 권한

22. 카메라를 사용하는 두 가지 방식

Android에서 카메라를 사용하는 방법은 크게 두 가지입니다.

  1. 다른 카메라 앱에 촬영을 위임

  2. 앱에서 CameraX 또는 Camera2로 카메라를 직접 제어

두 방식은 권한 처리도 다를 수 있습니다.

예를 들어 시스템 카메라 앱에 촬영을 위임하는 경우에는 앱이 직접 카메라 하드웨어에 접근하지 않을 수 있습니다.

반면 CameraX로 촬영 화면을 직접 구현한다면 CAMERA 권한이 필요합니다.


23. ActivityResultContracts.TakePicture

사진 촬영을 다른 카메라 앱에 위임할 때는 TakePicture 계약을 사용할 수 있습니다.

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

        if (success) {
            // 사진 저장 완료 후 처리
        }
    }

촬영을 시작할 때는 저장할 이미지의 Uri를 전달해야 합니다.

takePictureLauncher.launch(photoUri)

이 Uri는 FileProvider나 MediaStore 등을 이용해 생성할 수 있습니다.

임의의 내부 파일 경로를 그대로 다른 앱에 전달해서는 안 됩니다.

또한 촬영을 외부 카메라 앱에 위임하는 경우에는 앱이 직접 CAMERA 권한을 요청하지 않아도 되는 구조를 사용할 수 있습니다.


24. CameraX를 사용할 때

CameraX를 이용해 앱 내부에 카메라 미리보기 화면을 구현한다면 카메라 권한을 확인해야 합니다.

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

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

그리고 런타임 권한을 확인한 뒤 CameraX를 시작합니다.

카메라 화면 진입
      │
      ▼
CAMERA 권한 확인
      │
      ├── 허용 → CameraX 시작
      │
      └── 거부 → 권한 요청

권한이 없는 상태에서 카메라 사용을 시도하지 않도록 구성해야 합니다.


위치 권한

25. Android 위치 권한 종류

Android에서는 위치 정확도에 따라 권한을 구분합니다.

권한설명
ACCESS_COARSE_LOCATION대략적인 위치
ACCESS_FINE_LOCATION정확한 위치
ACCESS_BACKGROUND_LOCATION백그라운드 위치 접근

앱에서 필요한 정확도에 따라 권한을 요청해야 합니다.

예를 들어 주변 지역 정보를 대략적으로 보여주는 앱이라면 대략적인 위치만으로 충분할 수 있습니다.

반면 GPS 기반 이동 경로를 기록하는 앱에서는 정확한 위치가 필요할 수 있습니다.


26. 위치 권한 Manifest 선언

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

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

이 예제는 대략적인 위치와 정확한 위치를 모두 사용할 수 있는 앱을 가정합니다.

실제 앱에서는 필요한 권한만 선언해야 합니다.

또한 위치 권한을 요청할 때는 사용 목적을 명확하게 설명하는 것이 좋습니다.


27. Android 12 이후 정확한 위치 선택

Android 12(API 31) 이상에서는 사용자가 위치 권한을 허용하면서 정확도를 선택할 수 있습니다.

예를 들어 다음과 같은 선택입니다.

위치 접근 허용

정확한 위치
대략적인 위치

따라서 ACCESS_FINE_LOCATION을 요청했다고 해서 항상 정확한 위치를 받을 수 있는 것은 아닙니다.

Android 12 이상에서 정확한 위치가 필요한 경우에는 ACCESS_FINE_LOCATION과 ACCESS_COARSE_LOCATION을 함께 요청해야 합니다.


28. 여러 위치 권한 요청하기

다음과 같이 RequestMultiplePermissions를 사용할 수 있습니다.

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 -> {
                startPreciseLocation()
            }

            coarseGranted -> {
                startApproximateLocation()
            }

            else -> {
                showLocationPermissionDenied()
            }
        }
    }

권한 요청은 다음과 같이 수행합니다.

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

이렇게 하면 사용자가 정확한 위치만 허용하지 않았더라도 대략적인 위치 기능을 제공할 수 있습니다.


29. 정확한 위치가 필요한 앱

예를 들어 GPS 이동 경로 기록 앱을 개발한다고 가정하겠습니다.

사용자는 정원이나 산책로를 이동하면서 경로를 기록합니다.

이러한 앱에서는 위치 정확도가 중요합니다.

하지만 Android의 ACCESS_FINE_LOCATION 권한이 허용되어 있다고 해서 항상 수 미터 수준의 정확도가 보장되는 것은 아닙니다.

실제 정확도는 다음 요소에 영향을 받습니다.

  • GPS 수신 상태

  • 건물 및 나무에 의한 신호 차단

  • 기기 하드웨어

  • 위치 제공자의 측정 방식

  • 사용자 위치 정확도 설정

  • 이동 속도

  • 주변 환경

따라서 경로 기록 앱에서는 위치 권한뿐 아니라 위치 데이터의 정확도 값을 함께 확인해야 합니다.


30. FusedLocationProviderClient 활용

Google Play services Location 라이브러리를 사용하는 앱에서는 FusedLocationProviderClient를 이용해 현재 위치를 가져올 수 있습니다.

예를 들어 다음과 같은 방식입니다.

val fusedLocationClient =
    LocationServices.getFusedLocationProviderClient(
        requireActivity()
    )

현재 위치를 요청할 때는 다음과 같이 사용할 수 있습니다.

val cancellationTokenSource =
    CancellationTokenSource()

fusedLocationClient.getCurrentLocation(
    Priority.PRIORITY_HIGH_ACCURACY,
    cancellationTokenSource.token
).addOnSuccessListener { location ->

    if (location != null) {

        val latitude = location.latitude
        val longitude = location.longitude

        // 위치 정보 처리
    }
}

이 코드는 필요한 위치 권한이 이미 허용된 상태를 가정합니다.

실제 앱에서는 권한 확인, 위치 서비스 활성화 여부, 실패 처리, 취소 처리 등을 추가해야 합니다.


31. 위치 권한이 있어도 위치를 가져오지 못하는 이유

위치 권한이 허용되어 있어도 위치 정보가 항상 반환되는 것은 아닙니다.

대표적인 원인은 다음과 같습니다.

  • 기기의 위치 서비스가 꺼져 있음

  • GPS 신호 수신 불량

  • 네트워크 연결 문제

  • 위치 요청이 취소됨

  • 위치를 계산하지 못함

  • 백그라운드 실행 제한

따라서 위치 권한 허용 여부와 위치 서비스 활성화 여부를 구분해야 합니다.

필요한 경우 Google Play services의 SettingsClient를 이용해 위치 설정을 확인할 수 있습니다.


32. 백그라운드 위치 권한

앱이 화면에 표시되지 않는 상태에서도 위치를 수집해야 한다면 백그라운드 위치 접근을 검토해야 합니다.

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

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

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

Android 버전에 따라 백그라운드 위치 권한을 요청하는 절차가 다르며, Android 11 이상에서는 일반적인 위치 권한 요청 대화상자만으로 '항상 허용'을 선택할 수 없는 경우가 있습니다.

또한 Google Play에서는 백그라운드 위치 사용 목적과 필요성을 엄격하게 검토할 수 있습니다.

따라서 화면이 켜진 상태에서만 사용하는 지도나 주변 검색 기능이라면 백그라운드 위치 권한을 요청하지 않는 것이 좋습니다.


33. Foreground Service와 위치 권한

GPS 경로 기록 앱처럼 사용자가 명시적으로 시작한 작업을 지속해야 하는 경우에는 Foreground Service를 검토할 수 있습니다.

하지만 Foreground Service를 사용한다고 해서 위치 권한이나 백그라운드 실행 제한이 사라지는 것은 아닙니다.

Android 14(API 34) 이상을 대상으로 위치 Foreground Service를 사용하는 경우에는 서비스 유형과 관련 권한 선언이 필요합니다.

예를 들어 다음과 같은 선언을 검토할 수 있습니다.

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

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

서비스에는 적절한 foregroundServiceType을 지정해야 합니다.

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

실제 구현에서는 위치 런타임 권한과 Foreground Service 시작 조건을 함께 확인해야 합니다.

특히 앱이 백그라운드에 있을 때 서비스를 시작하는 경우에는 추가 제한이 적용될 수 있습니다.


Android 12~16 추가 권한

34. Bluetooth 권한 변경

Android 12(API 31)부터 Bluetooth 관련 권한이 세분화되었습니다.

대표적으로 다음 권한을 사용합니다.

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

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

기존 Android 버전에서 사용하는 Bluetooth 권한과 동작이 다를 수 있습니다.

따라서 Bluetooth 기기를 검색하거나 연결하는 앱에서는 Android 버전별 Manifest 선언과 런타임 권한 처리를 확인해야 합니다.


35. NEARBY_WIFI_DEVICES 권한

Android 13(API 33)부터 일부 Wi-Fi 관련 기능에서는 다음 권한이 필요할 수 있습니다.

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

하지만 모든 Wi-Fi 기능에서 이 권한이 필요한 것은 아닙니다.

사용하는 Wi-Fi API에 따라 필요한 권한이 다르므로 공식 문서를 확인해야 합니다.

또한 위치 정보와 연결되는 기능이라면 위치 권한이 추가로 필요할 수 있습니다.


36. 특별 접근 권한

일부 기능은 일반적인 런타임 권한 요청과 다르게 처리합니다.

예를 들어 다음과 같은 기능입니다.

  • 모든 파일 접근

  • 다른 앱 위에 표시

  • 정확한 알람 예약

  • 일부 시스템 설정 변경

이러한 기능은 별도 설정 화면이나 특별 접근 절차가 필요할 수 있습니다.

특히 MANAGE_EXTERNAL_STORAGE는 일반적인 파일 선택 기능을 위해 사용해서는 안 됩니다.

Google Play에서도 모든 파일 접근 권한은 사용 목적에 제한이 있습니다.


MVVM 구조와 권한 관리

37. 권한 요청은 Fragment에서 처리하기

MVVM 구조에서는 권한 요청을 어디에서 처리할지 고민할 수 있습니다.

권한 요청은 Activity 또는 Fragment의 생명주기와 연결됩니다.

따라서 실제 권한 요청은 UI 계층에서 처리하는 것이 적합합니다.

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

사용자 버튼 클릭
       │
       ▼
Fragment
       │
       ▼
권한 확인 및 요청
       │
       ▼
권한 결과
       │
       ▼
ViewModel
       │
       ▼
Repository

ViewModel은 권한 요청 런처나 Activity 참조를 직접 보유하지 않는 것이 좋습니다.

권한이 허용된 후 필요한 데이터 작업을 ViewModel에 전달할 수 있습니다.


38. 권한 상태는 언제 다시 확인해야 할까?

권한은 앱 실행 중에도 변경될 수 있습니다.

예를 들어 사용자가 Android 설정 화면에서 권한을 변경할 수 있습니다.

또한 Android는 오랫동안 사용하지 않은 앱의 권한을 자동으로 초기화할 수 있습니다.

따라서 권한 상태를 앱 최초 실행 시 한 번만 확인해서는 안 됩니다.

다음과 같은 시점에 다시 확인하는 것이 좋습니다.

  • 권한이 필요한 기능을 실행하기 직전

  • 앱 설정 화면에서 돌아온 후

  • Fragment가 다시 활성화된 경우

  • 장시간 중단되었던 작업을 재개하는 경우

필요하다면 onResume()에서 현재 권한 상태를 확인해 UI를 갱신할 수 있습니다.


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

앱 내부 설정값과 실제 시스템 권한은 구분해야 합니다.

예를 들어 다음과 같은 값을 DataStore에 저장했다고 가정하겠습니다.

notification_enabled = true

하지만 Android 시스템에서 알림 권한을 거부했다면 실제 알림을 표시하지 못할 수 있습니다.

따라서 시스템 권한 허용 여부를 DataStore 값만으로 판단해서는 안 됩니다.

DataStore에는 사용자의 앱 내부 선호 설정을 저장하고, 시스템 권한은 Android API를 통해 현재 상태를 확인해야 합니다.


40. 권한 요청 UX에서 피해야 할 패턴

다음과 같은 방식은 피하는 것이 좋습니다.

앱 실행 직후 모든 권한 요청

사용자가 권한이 필요한 이유를 이해하기 어렵습니다.

거부할 때마다 반복 요청

사용자 경험을 해치고 권한 요청이 더 이상 표시되지 않을 수 있습니다.

권한이 없으면 앱 종료

권한이 필요 없는 기능까지 사용할 수 없게 됩니다.

실제 사용하지 않는 권한 선언

개인정보 보호와 Google Play 정책 측면에서 불필요한 위험이 됩니다.

권한을 허용했다고 정확한 위치를 보장

실제 GPS 정확도는 별도의 문제입니다.

사진 선택에 무조건 전체 저장소 권한 요청

Photo Picker로 충분한 기능이라면 과도한 권한 요청입니다.


41. Google Play 권한 정책과 개인정보처리방침

Google Play에 앱을 배포한다면 권한 사용 목적을 검토해야 합니다.

특히 다음 권한은 주의가 필요합니다.

  • 백그라운드 위치

  • 전체 파일 접근

  • 사진 및 동영상 접근

  • SMS 및 통화 관련 권한

  • 일부 특별 접근 권한

권한을 Manifest에 선언했다는 사실만으로 사용이 정당화되는 것은 아닙니다.

실제 앱 기능과 연결되어 있어야 하며, 필요한 경우 Google Play Console에서 사용 목적을 신고해야 합니다.

또한 개인정보처리방침과 Data Safety 작성 내용이 실제 데이터 수집·처리 방식과 일치해야 합니다.


42. Android 버전별 권한 테스트

권한 처리는 반드시 여러 Android 버전에서 테스트해야 합니다.

Android 버전주요 테스트 항목
Android 7(API 24)기본 런타임 권한
Android 10(API 29)Scoped Storage, 백그라운드 위치
Android 11(API 30)일회성 권한, 권한 자동 초기화
Android 12(API 31)정확한 위치·대략적인 위치
Android 13(API 33)알림 권한, 미디어 권한
Android 14(API 34)선택한 사진 접근
Android 15(API 35)최신 권한 및 백그라운드 제한
Android 16(API 36)targetSdk별 동작 및 호환성

특히 Android 버전뿐 아니라 앱의 targetSdk를 변경했을 때 권한 요청 동작이 달라지는지 확인해야 합니다.


43. 권한 테스트 시나리오

다음과 같은 상황을 테스트하는 것이 좋습니다.

테스트확인할 내용
최초 권한 요청정상 표시
권한 허용기능 실행
권한 거부대체 UI 제공
반복 거부무한 요청 없음
설정에서 권한 허용앱 복귀 후 반영
설정에서 권한 차단기능 실행 전 재확인
알림 권한 거부앱 정상 사용
사진 한 장 선택Uri 정상 처리
여러 사진 선택개수 제한 처리
선택한 사진만 허용부분 접근 처리
대략적인 위치 허용대체 기능 제공
위치 서비스 OFF오류 안내
앱 재실행현재 권한 상태 반영
앱 장기 미사용자동 초기화 대응

권한 테스트에서는 '허용'뿐 아니라 '거부' 상태가 매우 중요합니다.

사용자가 권한을 허용하지 않아도 앱이 안정적으로 동작해야 합니다.


44. Android 권한 관리 체크리스트

  • 실제 필요한 권한만 Manifest에 선언

  • 일반 권한과 위험 권한 구분

  • ActivityResultContracts 사용

  • 권한 요청 런처 생명주기 준수

  • 권한 필요 시점에 요청

  • 권한 요청 이유 설명

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

  • 반복 권한 요청 방지

  • 앱 설정 이동 안내

  • Android 13 알림 권한 대응

  • 알림 채널 생성 및 상태 확인

  • 사진 선택에 Photo Picker 우선 검토

  • Android 13 미디어 권한 구분

  • Android 14 선택한 사진 접근 처리

  • 전체 저장소 접근 권한 남용 방지

  • 카메라 직접 사용과 외부 앱 위임 구분

  • FileProvider 또는 MediaStore 활용

  • 위치 권한 정확도 구분

  • 대략적인 위치 허용 시 대체 기능 제공

  • 위치 서비스 비활성화 상태 처리

  • 백그라운드 위치 권한 필요성 검토

  • Foreground Service 위치 제한 확인

  • Bluetooth 및 Wi-Fi 권한 버전 분기

  • 시스템 권한과 DataStore 설정 구분

  • 앱 복귀 후 권한 상태 재확인

  • Google Play 권한 정책 검토

  • 개인정보처리방침과 Data Safety 일치 확인

  • Android 버전별 실제 기기 테스트

45. 마무리

Android 앱의 권한 관리는 단순히 사용자에게 허용 버튼을 누르게 만드는 기능이 아닙니다.

앱이 필요한 정보와 기능에만 접근하도록 설계하고, 사용자가 권한을 거부하더라도 가능한 기능을 계속 제공하는 것이 중요합니다.

특히 Android 13 이후에는 사진과 알림 권한이 크게 달라졌고, Android 14에서는 선택한 사진에만 접근하도록 제한하는 기능이 추가되었습니다.

따라서 오래된 권한 요청 코드를 그대로 유지하기보다 현재 앱의 사용 목적에 맞게 검토해야 합니다.

핵심 원칙은 다음과 같습니다.

  1. 실제로 필요한 권한만 요청합니다.

  2. 권한은 사용자가 해당 기능을 실행할 때 요청합니다.

  3. ActivityResultContracts로 권한 요청과 결과를 관리합니다.

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

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

  6. Android 13 알림 권한과 Android 14 부분 미디어 접근을 구분합니다.

  7. 정확한 위치 권한이 있어도 GPS 정확도가 보장되는 것은 아닙니다.

  8. 백그라운드 위치와 특별 접근 권한은 신중하게 사용합니다.

  9. 시스템 권한 상태와 앱 내부 설정값을 구분합니다.

  10. Android 버전과 targetSdk 조합별로 테스트합니다.

권한 처리를 올바르게 구현하면 개인정보 보호뿐 아니라 앱의 안정성과 사용자 경험도 개선할 수 있습니다.

Android 권한 관리의 핵심은 가능한 많은 권한을 얻는 것이 아니라, 필요한 순간에 최소한의 권한으로 원하는 기능을 제공하는 것입니다.


참고 자료

태그: Android, Kotlin, Android 권한, Runtime Permission, ActivityResultContracts, POST_NOTIFICATIONS, Photo Picker, READ_MEDIA_IMAGES, READ_MEDIA_VISUAL_USER_SELECTED, CAMERA, 위치 권한, ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION, Android 13, Android 14, Android 15, Android 16, Google Play 정책