안드로이드에는 두 종류의 권한이 있다.
첫번째는 사용자에게 따로 권한 승인 여부를 묻지 않고, 설치 시간에 권한을 부여받을 수 있는 설치 시간 권한(Install-time permissions)이다.
이 종류에 해당하는 권한들은 Manifest에 선언하기만 하면 앱 설치와 동시에 사용할 수 있는 권한들이다.
이 종류에 해당하는 권한은 다이얼로그를 통해 사용자에게 직접 권한 승인을 요청하여 권한을 부여받는다.
주로 개인정보에 접근할 수 있는 권한을 런타임 권한으로 분류하고 있다.
이러한 권한들은 Android System에서 보호하고 있기 때문에 해당 기능을 사용하는 App은 AndroidManifest.xml에 해당 기능에 대한 권한을 <uses-permission>으로 선언한 뒤 사용자가 직접 해당 권한을 허용하도록 요청해야 한다.
세부적인 권한의 종류는 아래의 링크에서 확인할 수 있다.
https://developer.android.com/reference/android/Manifest.permission
Android 11 (API 30, R os)를 기준으로 권한을 요청하는 방법은 다르게 구현할 수 있다.
우선 안드로이드 버전에 관계없이 Manifest.xml 파일에는 권한을 선언해주어야한다.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18"/>
Android API 30 이전까는 ActivityCompat에서 제공하는 requestPermissions과 Activity 및 Fragment에서 지원하는 onRequestPermissionsResult를 통해 권한을 아래와 같이 획득할 수 있다.
const val CAMERA_PERMISSION_CODE = 999
class CameraActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
checkCameraPermission()
}
// 1. Camera 권한 확인
private fun checkCameraPermission() {
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
startCamera()
} else {
requestCameraPermission()
}
}
// 2. Camera 권한 요청
private fun requestCameraPermission() {
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), CAMERA_PERMISSION_CODE)
}
// 3. Camera 권한 처리
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray) = when (requestCode) {
CAMERA_PERMISSION_CODE -> {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startCamera()
} else {
// 권한 요청 거부했을 경우의 logic 처리
// ex) permission 재요청 or 무시 등등
}
}
}
// 4. Camera 권한 획득 후 처리되는 logic
private fun startCamera() {
// ...
}
}
위 코드를 보면 요청했던 모든 권한은 onRequestPermissionsResult 한 곳에서 처리되고 있다.
이는, 권한 요청 flow가 Activity 및 Fragment Lifecycle에 밀접하게 연관되어있음에도 불구하고 앱에서 권한 요청을 처리하는 방법이 명확하지 않아 구현/관리가 복잡해질 수 있으며 여러 권한을 함께 처리하는 데 제한적이게 된다.
ex) Camera 권한의 경우 보통 Storage 권한과 함께 처리되야 한다.
이러한 이유로, Android에서는 명확하고 독립적인 권한 요청 Flow를 지원하기 위해 API 30 이상부터 해당 방식을 통한 권한 요청은 deprecated 되었다.
안드로이드에서는 개발자가 보다 나은 권한 요청 logic을 작성하고 제공할 수 있도록 다음과 같은 Permission workflow를 제공하고 있다.

참고로 안드로이드에서는 Permission에 대한 권한 요청은 사용자 사용성을 고려하여 두 번만 실행할 수 있다.
따라서, 한번 거절된 Permission을 재요청할 때 rationale을 통해 해당 Permission이 필요한 이유를 사용자에게 노출시키고 또 거절된 경우엔 App을 종료시키지 않고 해당 feature에 대해 degrade된 사용성을 가지도록 권장하고 있다.
즉, 앞선 Permission workflow는 다음과 같이 요약할 수 있다.
(1) 최초로 권한을 요청하는 경우
(2) 거절당한 권한을 다시 요청하는 경우
(3) 거절과 동시에 해당 권한요청을 다시 표시하지 않음 옵션을 선택한 경우
안드로이드 11 이전과 이후가 다른 점은 ActivityResultLauncher를 통해서 App의 Permission을 제어한다는 점이다.
Permission launcher를 개발자가 직접 구현함으로써 각각의 Permission 성격에 따라 Runtime에 유연하고 독립적으로 처리할 수 있다.
class MyActivity : AppCompatActivity() {
private val cameraPermissionLauncher : ActivityResultLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
// 권한이 허용된 경우에 실행할 코드
} else {
// 권한이 거부된 경우에 실행할 코드
// ActivityCompat.shouldShowRequestPermissionRationale
// → 사용자가 권한 요청을 명시적으로 거부한 경우 true를 반환한다.
// → 사용자가 다시 묻지 않음 선택한 경우 false를 반환한다.
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
// 권한 요청에 대한 이유를 사용자에게 설명하는 Dialog를 표시
AlertDialog.Builder(this)
.setTitle("권한 요청")
.setMessage("카메라 권한이 필요합니다.")
.setPositiveButton("확인") { _, _ ->
requestCameraPermission.launch(Manifest.permission.CAMERA)
}
.setNegativeButton("취소") { _, _ ->
// Dialog에서 취소 버튼을 누른 경우에 실행할 코드
}
.show()
} else {
// 사용자가 권한 요청 다이얼로그에서 "다시 묻지 않음" 옵션을 선택한 경우에 실행할 코드
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 카메라 권한이 허용되어 있는지 확인
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
// 권한이 이미 허용된 경우에 실행할 코드
} else {
// 권한이 허용되어 있지 않은 경우 권한 요청 다이얼로그를 표시
cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
}
복수개의 Permission을 한번에 요청하는 sample code는 다음과 같이 ActivityResultContracts.RequestMultiplePermissions()를 사용하여 처리할 수 있다.
val multiplePermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.entries.forEach { (permission, isGranted) ->
when {
isGranted -> {
// 권한이 승인된 경우 처리할 작업
}
!isGranted -> {
// 권한이 거부된 경우 처리할 작업
}
else -> {
// 사용자가 "다시 묻지 않음"을 선택한 경우 처리할 작업
}
}
}
// multiple permission 처리에 대한 선택적 작업
// - 모두 허용되었을 경우에 대한 code
// - 허용되지 않은 Permission에 대한 재요청 code
}
val permissions = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_CONTACTS,
...
)
multiplePermissionsLauncher.launch(permissions)