본문 바로가기

Android/개인 기록

Permission Check (권한 요청)

들어가며..

안드로이드 개발을 하다보면 권한 요청이 사소하게 성가시다.

반드시 필요하지만, 손볼 일이 많지 않다보니 작성해야하는 순간이 오면 헷갈리기 쉽상이었다.

물론 구글링으로 다른 뛰어난 분들의 코드를 쉽게 접할 수 있고, 최근에는 gpt의 도움을 받을 수도 있겠지만, 일단 내 코드 스타일과 맞는 걸 찾기는 어려웠다.

그래서 나중에 내가 사용할 목적으로 기록해 둔다.

검증 없이 작성된 코드이므로 문제가 발생 할 수 있다.

발생한 문제는 공유해주면 같이 해결해보도록 최대한 노력하겠다. 언제든지 피드백은 환영이다.

 

최종 수정일: 2024.01.18


Permission Check

우선, Manifest에 필요한 권한들을 등록해 준다.

<uses-permission android:name="android.permission.INTERNET" /> 
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.CAMERA" /> 
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> 
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> 
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

본인은 인터넷 접근, 알림, 카메라, 위치 정보, 저장소의 이미지 읽기 권한, 포그라운드 서비스 권한을 등록하였다.

참고로, Sdk 33 부터는 "READ_EXTERNAL_STORAGE"가 아닌, "READ_MEDIA_AUDIO", "READ_MEDIA_IMAGES", "READ_MEDIA_VIDEO" 와 같이 좀더 세부적으로 작성해 주어야 한다.

이제 권한 요청을 위한 코드를 살펴보자.

 

 

1. 권한 요청에 사용될 리스트 생성

var permissions = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { // 28 까지
        listOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.CAMERA
        )
    } else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { // 32 까지
        listOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.CAMERA
        )
    } else { // 33 이상
        listOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.READ_MEDIA_IMAGES,
            Manifest.permission.CAMERA,
            Manifest.permission.POST_NOTIFICATIONS
        )
    }

OS 버전에 맞게 권한을 요청해 주어야한다.

타겟으로 하는 Sdk 버전이 하나라면 모르겠지만, 본인이 담당하는 프로젝트는 sdk 24부터 33(수정일 24.01.17 기준)까지 지원하기 때문에, 이렇게 버전을 구분해 작성해 주었다.

 

 

2. 권한 허용 여부 체크 함수

fun checkPermission(permissionList: List<String>) {
    val requestList = ArrayList<String>()

    for (permission in permissionList) {
        if (ActivityCompat.checkSelfPermission(this, permission)
            != PackageManager.PERMISSION_GRANTED
        ) {
            requestList.add(permission)
        }
    }

    if (requestList.isNotEmpty()) {
        ActivityCompat.requestPermissions(this, requestList.toTypedArray(), REQUEST_PERMISSION)
        false // 권한 요청 필요, 즉시 false 반환
    }
}
  • permissionList에 있는 각 권한에 대해 ActivityCompat.checkSelfPermission을 호출하여 권한이 이미 허용되었는지 확인한다.
  • 만약 권한이 허용되지 않았다면 requestList에 추가한다.
  • requestList가 빈값이 아니라면 ActivityCompat.requestPermissions를 호출해 권한을 요청한다.
  • REQUEST_PERMISSION는 Int값의 권한 요청 코드로, 결과 처리에 사용한다.

 

3. 권한 요청 이후의 사용자 응답 처리

override fun onRequestPermissionsResult(
    requestCode: Int, permissions: Array<String>, grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)

    if (requestCode == REQUEST_PERMISSION) {

        val deniedPermission = ArrayList<String>()

        for ((index, result) in grantResults.withIndex()) {
            if (result == PackageManager.PERMISSION_DENIED) {
                deniedPermission.add(permissions[index])
            }
        }

        if (deniedPermission.isNotEmpty()) {
            if (shouldShowRationale) {
               // ✅ 사용자에게 한번 거절한 권한에 대한 허용을 다시 요청
               requestPermissions(deniedPermission.toTypedArray(), REQUEST_PERMISSION)
            }else{
                Snackbar.make(
                    binding.root, // ✅ 'binding.root'는 현재 뷰의 루트 레이아웃을 나타낸다.
                    "권한이 거부되었습니다. 설정(앱 정보)에서 권한을 허용해주세요.",
                    Snackbar.LENGTH_INDEFINITE
                ).setAction("To Setting") {
                    // ✅ 설정 화면으로 이동
                    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                    val uri = Uri.fromParts("package", packageName, null)
                    intent.data = uri
                    startActivity(intent)
                }.show()
            }
        }
    }
}
  • requestCode를 확인하여 이 콜백이 앱에서 보낸 권한 요청에 대한 응답인지 확인한다. 이 예제에서는 앞에서 작성한 REQUEST_PERMISSION이게 요청 코드이다.
  • grantResult 배열을 순회하면서 사용자가 거부한 권한을 찾는다. 권한이 거부되었으면 PackageManager.PERMISSION_DENIED가 반환된다.
  • 하나 이상의 권한이 또 거부되었다면, Snackbar가 표시된다. 여기서 "To Setting"버튼을 클릭하면 앱의 설정으로 이동시킨다.
  • binding.root는 임의로 넣어뒀다. 현재 뷰(Activity or Fragment)의 구조에 맞게 조정해서 넣으면 된다.

Android는 OS차원에서 사용자가 권한을 2번 거절한 경우, 더 이상의 동의여부가 없는 것으로 간주하여 추가적인 요청 메시지를 띄우지 않는다.

2번 다 거절 했지만 앱의 원활한 동작을 위해 반드시 특정 권한이 필요하다면, 사용자가 직접 권한 설정을 해주어야 한다.

본인은 필요한 권한의 경우 두번 다 거절했을 때, 앱 설정으로 이동시켜 직접 설정하도록 하는 방식을 택하고 있다.

 

 

4. 실사용 예시

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(binding.root)

    val permissionList =...

    checkRunTimePermission(permissionList)
}

 


Object 싱글톤으로 만들어서 사용하는 방법도 있고, 필요한 액티비티의 onCreate에서 필요한 권한에 대해서만 개별로 요청하는 방법도 있겠지만 본인은 일단 앱을 시작했을때 모든 권한을 전부 요청하는 방식으로 진행 중이다.

개인의 프로젝트에 맞게 적절한 방법을 적용하길 바란다.


내용 추가: 2024.05.09

 

Jetpack Compose로 Permission을 관리하는 샘플 프로젝트를 만들었습니다.

제가 글을 잘 못써서 글의 가독성이 상당히 낮다는 것을 알고있기에..
상기 내용의 이해가 많이 어렵다면, 아래 프로젝트 코드를 확인 부탁드립니다

https://github.com/parade621/ComposePermissionSample

 

GitHub - parade621/ComposePermissionSample: Jetpack Compose permission dialog sample project

Jetpack Compose permission dialog sample project. Contribute to parade621/ComposePermissionSample development by creating an account on GitHub.

github.com