Dangerous Permission은 앱 실행 중 런타임 대화상자를 통해 사용자의 명시적 동의가 필요한 Android 권한 범주입니다. Android 개발자 가이드(2024)에 따르면, 위험한 권한은 ProtectionLevel dangerous를 가지며 카메라, 마이크, 위치, 연락처와 같은 민감한 데이터에 대한 액세스를 제공합니다. 사용자의 명시적 동의 없이 앱은 이러한 기능을 사용할 수 없습니다.
핵심 요점
Dangerous Permission은 사용자의 민감한 데이터에 대한 액세스를 제공하는 Android 시스템 권한 범주입니다. 일반 권한과 달리 위험한 권한은 설치 시 자동으로 부여되지 않습니다. 앱은 Android 6.0 Marshmallow(API 23)에서 도입된 런타임 메커니즘을 통해 실행 중에 명시적으로 요청해야 합니다.
명시적 요청이 필요한 이유는 이러한 권한이 보호하는 데이터의 특성 때문입니다: 사용자 위치, 개인 연락처, 카메라 및 마이크 콘텐츠, 통화 기록, SMS 등입니다. Android는 이 데이터를 민감한 것으로 간주하며 사용자가 의식적으로 액세스를 허용하도록 요구합니다. Android Privacy Sandbox(2024)에 따르면, 사용자는 평균적으로 약 30%의 런타임 요청을 거부합니다.
Dangerous Permission의 주요 특징은 언제든지 철회할 수 있다는 것입니다. 사용자는 설정 → 앱 → 권한으로 이동하여 모든 위험한 권한을 해제할 수 있습니다. 앱은 이전에 부여된 권한이 재시작 없이 언제든지 철회될 수 있음을 대비해야 합니다.
dangerous 보호 수준은 OS 수준의 시스템 권한 정의에 설정됩니다. 앱이 이 protectionLevel로 uses-permission을 선언하면 시스템은 해당 권한을 런타임 요청이 필요한 것으로 표시합니다. normal과 달리 위험한 권한은 항상 시스템 권한 관리 UI에 표시되며 철회할 수 있습니다.
모든 위험한 권한은 기능 범주에 따라 Permission Groups으로 그룹화됩니다. 예를 들어, CAMERA와 CAMERA2는 CAMERA 그룹에, ACCESS_FINE_LOCATION과 ACCESS_COARSE_LOCATION은 LOCATION 그룹에 있습니다. 사용자가 그룹 내 하나의 권한을 부여하면 동일한 그룹의 나머지 권한은 추가 대화상자 없이 자동으로 부여됩니다.
런타임 요청은 앱이 시스템 API를 호출하여 권한 요청 대화상자를 표시하는 메커니즘입니다. 사용자는 권한 이름과 허용/거부 버튼이 있는 모달 창을 봅니다. 응답 후 시스템은 결과와 함께 onRequestPermissionsResult 콜백을 호출합니다.
전체 사이클은 세 단계로 구성됩니다: checkSelfPermission을 통한 상태 확인, 권한이 없는 경우 requestPermissions 호출, onRequestPermissionsResult에서 결과 처리. 상태 확인은 필수입니다. 사용자가 설정을 통해 언제든지 권한을 철회했을 수 있으며, 확인 없이 함수를 호출하면 SecurityException이 발생합니다.
fun checkAndRequestCameraPermission() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
openCamera()
}
else -> {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA),
REQUEST_CAMERA_CODE
)
}
}
}
결과 처리는 ActivityResultLauncher 또는 onRequestPermissionsResult에서 이루어집니다. 권장되는 최신 접근 방식은 ActivityResultContracts.RequestPermission을 사용하는 것입니다. 이는 명시적 요청 코드 없이 더 깔끔한 API를 제공합니다. 이 계약은 Boolean(권한 부여 여부)을 반환합니다.
위험한 권한은 앱 시작 시가 아니라 기능 사용 컨텍스트에서 엄격하게 요청하세요. 사용자가 카메라 버튼을 눌렀다면 CAMERA를 요청하세요. 지도를 열었다면 LOCATION을 요청하세요. 컨텍스트 요청은 첫 실행 시 모든 권한을 요청하는 것보다 두 배 더 많은 부여를 얻습니다. 또한 사용자가 어떤 기능에 액세스가 필요한지 이해할 수 있도록 한 번에 하나 이상의 권한을 요청하지 않는 것이 좋습니다.
Android는 여러 위험한 권한 그룹을 정의하며, 각 그룹에는 하나 이상의 상수가 포함됩니다. 가장 완전한 목록은 Manifest.permission 클래스에서 확인할 수 있습니다. 아래는 개발에 사용되는 주요 그룹과 권한입니다.
| 권한 그룹 | 권한 | API 액세스 |
|---|---|---|
| CAMERA | CAMERA | Camera API, CameraX |
| LOCATION | ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION | FusedLocationProvider, Geofence |
| MICROPHONE | RECORD_AUDIO | MediaRecorder, AudioRecord |
| PHONE | READ_PHONE_STATE, CALL_PHONE, READ_CALL_LOG | TelephonyManager |
| CONTACTS | READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS | ContactsContract |
| SMS | READ_SMS, SEND_SMS, RECEIVE_SMS | SmsManager |
| STORAGE | READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE | MediaStore, File API |
| CALENDAR | READ_CALENDAR, WRITE_CALENDAR | CalendarContract |
Android 12부터 Google은 일부 권한에 대한 요구 사항을 강화했습니다. 예를 들어, BLUETOOTH_CONNECT와 BLUETOOTH_SCAN이 위험해졌으며 런타임 요청이 필요합니다. 센서에 대한 백그라운드 액세스를 위해 BODY_SENSORS_BACKGROUND 권한도 추가되었습니다. 개발자는 targetSdkVersion을 업데이트하고 현재 OS 버전에서 요청을 테스트해야 합니다.
Android 13(API 33)은 알림(POST_NOTIFICATIONS) 및 미디어 파일(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO)에 대한 새로운 권한을 도입하여 일반 READ_EXTERNAL_STORAGE를 대체했습니다. 이제 사진, 비디오 및 오디오에 대한 액세스는 단일 대화상자 없이 특수 권한을 통해 별도로 요청됩니다.
Dangerous와 Normal Permission은 부여 방식, 철회 가능성 및 UX에서 근본적으로 다릅니다. Normal은 설치 시 자동으로 부여되며, Dangerous는 명시적 런타임 대화상자가 필요합니다. Normal은 설정을 통해 철회할 수 없지만 Dangerous는 언제든지 비활성화할 수 있습니다. 이러한 비대칭성은 다른 개발 패턴을 만듭니다.
코드 관점에서 위험한 권한은 더 많은 작업이 필요합니다: checkSelfPermission, requestPermissions, 거부 처리. 일반 권한의 경우 AndroidManifest.xml에 한 줄이면 충분합니다. 그러나 Dangerous Permission은 사용자에게 제어권을 제공하여, 특히 카메라나 위치와 같은 민감한 기능에 대한 신뢰를 높입니다.
범주 선택은 개발자가 아니라 시스템에 의해 결정됩니다. 개발자는 uses-permission만 선언하고 시스템이 protectionLevel에 따라 범주를 결정합니다. 그러나 위험한 권한 요청 전략은 사용자 경험에 영향을 미칩니다: 빈번하거나 부적절한 대화상자는 앱 평점을 낮춥니다.
최신 방법으로 Kotlin에서 권한을 요청하려면 ActivityResultContracts.RequestMultiplePermissions 또는 RequestPermission을 사용합니다. 이러한 계약은 androidx.activity 라이브러리의 일부이며 onRequestPermissionsResult를 재정의할 필요 없이 람다 기반의 깔끔한 API를 제공합니다.
class CameraActivity : AppCompatActivity() {
private val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
openCamera()
} else {
showPermissionDeniedMessage()
}
}
fun requestCamera() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED ->
openCamera()
ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.CAMERA
) ->
showRationaleDialog()
else ->
requestPermissionLauncher.launch(
Manifest.permission.CAMERA
)
}
}
}
앱이 동시에 여러 위험한 권한이 필요한 경우 RequestMultiplePermissions을 사용하세요. 계약은 Map<String, Boolean>을 반환하며 키는 권한 이름, 값은 결과입니다. 이는 비디오 녹화를 위해 CAMERA와 RECORD_AUDIO를 요청해야 하는 첫 실행 시 유용합니다.
사용자가 요청을 거부하면 shouldShowRequestPermissionRationale 메서드가 true를 반환합니다. 이는 권한이 필요한 이유에 대한 설명을 표시해야 함을 알립니다. 모범 사례는 설명과 재시도 버튼이 있는 사용자 정의 대화상자를 표시하는 것입니다. 사용자가 Never Ask Again 확인란을 선택하여 요청을 다시 거부하면 shouldShowRequestPermissionRationale가 false를 반환하며 설정으로 리디렉션해야 합니다.
Never Ask Again은 사용자가 런타임 대화상자를 두 번째로 거부할 때 설정할 수 있는 플래그입니다. 이후 해당 권한에 대한 표준 대화상자가 더 이상 표시되지 않습니다. 액세스를 부여하는 유일한 방법은 사용자를 시스템 앱 설정으로 리디렉션하는 것입니다.
개발자는 두 가지 거부 시나리오를 구분해야 합니다: 첫째, shouldShowRequestPermissionRationale가 true를 반환하는 경우(사용자가 거부했지만 대화상자를 계속 표시할 수 있음), 둘째, 메서드가 false를 반환하는 경우(Never Ask Again이 활성화되었거나 정책에 의해 권한이 차단됨). 두 번째 경우 설정 열기 버튼을 표시해야 합니다.
fun handlePermissionDenied(permission: String) {
if (ActivityCompat.shouldShowRequestPermissionRationale(
this, permission
)) {
showRationaleDialog(permission)
} else {
showSettingsRedirectDialog(permission)
}
}
private fun showSettingsRedirectDialog(permission: String) {
AlertDialog.Builder(this)
.setTitle("액세스 거부됨")
.setMessage(
"권한이 차단되었습니다. 설정을 여세요."
)
.setPositiveButton("설정") { _, _ ->
val intent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts(
"package", packageName, null
)
)
startActivity(intent)
}
.show()
}
shouldShowRequestPermissionRationale가 false를 반환한 경우 권한을 다시 요청하지 않는 것이 중요합니다. 이 경우 requestPermissions을 반복 호출해도 대화상자가 표시되지 않으며 결과가 설명 없이 즉시 DENIED로 반환됩니다. 사용자는 불명확한 동작을 경험하게 되어 앱 사용 경험에 부정적인 영향을 미칩니다.
자주 묻는 질문
위험한 권한에는 ProtectionLevel dangerous가 있는 권한이 포함됩니다: CAMERA, RECORD_AUDIO, ACCESS_FINE_LOCATION, READ_CONTACTS, READ_SMS, READ_CALENDAR 등. 전체 목록은 Manifest.permission 클래스에서 확인할 수 있습니다.
ContextCompat.checkSelfPermission을 사용하여 컨텍스트와 권한 이름을 전달합니다. 메서드는 PERMISSION_GRANTED 또는 PERMISSION_DENIED를 반환합니다. 위험한 권한이 필요한 모든 API 호출 전에 확인을 수행해야 합니다.
Permission Group은 관련된 위험한 권한을 그룹화합니다. 사용자가 그룹 내 하나의 권한을 부여하면 나머지는 자동으로 부여됩니다. 예를 들어, LOCATION에는 ACCESS_FINE_LOCATION과 ACCESS_COARSE_LOCATION이 포함됩니다.
거부 후 shouldShowRequestPermissionRationale을 확인하세요. 메서드가 false를 반환하고 권한이 여전히 부여되지 않은 경우 Never Ask Again이 활성화된 것입니다. ACTION_APPLICATION_DETAILS_SETTINGS가 포함된 Intent를 통해 사용자를 설정으로 리디렉션하세요.
네, 여전히 필수입니다. Android 13+에서는 일부 권한이 변경되었습니다: POST_NOTIFICATIONS가 별도의 런타임 권한이 되었고, READ_EXTERNAL_STORAGE는 미디어 파일에 대한 세분화된 액세스를 위해 READ_MEDIA_IMAGES로 대체되었습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.