모든 Android 앱은 액세스가 제한된 샌드박스에서 실행됩니다. 앱이 자체 샌드박스 밖에 있는 리소스나 정보를 사용해야 하는 경우 권한을 선언하고 이 액세스를 제공하는 권한 요청을 설정할 수 있습니다. 이러한 단계는 권한 사용 워크플로의 일부입니다.
위험한 권한을 선언하고 앱이 Android 6.0(API 수준 23) 이상을 실행하는 기기에 설치된 경우 이 가이드의 단계에 따라 위험한 런타임 권한을 요청해야 합니다.
위험한 권한을 선언하지 않거나 앱이 Android 5.1(API 수준 22) 이하를 실행하는 기기에 설치된 경우 권한이 자동으로 부여되므로 이 페이지의 다른 단계를 완료하지 않아도 됩니다.
기본 원칙
런타임 권한을 요청하기 위한 기본 원칙은 다음과 같습니다.
- 사용자가 권한이 필요한 기능과 상호작용하기 시작할 때 컨텍스트에 따라 권한을 요청합니다.
- 사용자를 차단하지 않습니다. 교육용 UI 흐름(예: 권한 요청의 근거를 설명하는 흐름)을 취소하는 옵션을 항상 제공합니다.
- 사용자가 기능에 필요한 권한을 거부하거나 취소하면 권한이 필요한 기능을 사용 중지하는 등의 방법으로 앱의 성능을 단계적으로 저하시켜 사용자가 앱을 계속 사용할 수 있도록 합니다.
- 시스템 동작을 가정하지 않습니다. 예를 들어 여러 권한이 동일한 권한 그룹에 표시된다고 가정하지 마세요. 권한 그룹은 앱이 밀접하게 관련된 여러 권한을 요청할 때 시스템에서 사용자에게 표시하는 시스템 대화상자의 수를 최소화하는 데만 도움이 됩니다.
권한 요청 워크플로
앱에서 런타임 권한을 선언하고 요청하기 전에 앱에서 이런 작업이 필요한지 평가하세요. 개발자는 어떤 권한도 선언할 필요 없이 앱에서 사진 찍기, 미디어 재생 일시중지, 관련성 있는 광고 표시 등 여러 사용 사례를 실행할 수 있습니다.
앱에서 런타임 권한을 선언하고 요청해야 한다고 판단되면 다음 단계를 완료하세요.
- 앱의 매니페스트 파일에서 앱이 요청할 필요가 있을 권한을 선언합니다.
- 앱의 특정 작업이 알맞은 런타임 권한과 연결되도록 앱의 UX를 설계합니다. 앱이 비공개 사용자 데이터에 액세스하도록 권한을 부여해야 할 수 있는 작업을 사용자에게 알립니다.
- 특정 비공개 사용자 데이터에 액세스해야 하는 앱의 작업을 사용자가 호출할 때까지 기다립니다. 이때 앱은 데이터에 액세스하는 데 필요한 런타임 권한을 요청할 수 있습니다.
사용자가 이미 앱에 필요한 런타임 권한을 부여했는지 확인합니다. 부여했다면 앱에서 비공개 사용자 데이터에 액세스할 수 있습니다. 부여하지 않았다면 다음 단계로 이동합니다.
런타임 권한이 필요한 작업을 실행할 때마다 권한이 있는지 확인해야 합니다.
사용자에게 앱에서 근거를 표시해야 하는지 확인합니다. 여기서 앱이 사용자에게 특정 런타임 권한을 요청하는 이유를 설명합니다. 시스템에서 앱이 근거를 표시하지 않아야 한다고 판단하면 UI 요소를 표시하지 않고 다음 단계로 바로 진행합니다.
그러나 시스템에서 앱이 근거를 표시해야 한다고 판단하면 사용자에게 근거를 UI 요소로 표시합니다. 이러한 근거를 통해 앱이 액세스하려는 데이터가 무엇인지, 런타임 권한을 부여하면 앱이 사용자에게 제공할 수 있는 이점이 무엇인지 명확하게 설명해야 합니다. 사용자가 근거를 확인한 후 다음 단계를 진행합니다.
앱에서 비공개 사용자 데이터에 액세스하는 데 필요한 런타임 권한을 요청합니다. 권한 개요 페이지에 표시된 것과 같은 런타임 권한 메시지가 시스템에 표시됩니다.
런타임 권한 부여를 선택했는지 또는 거부를 선택했는지 사용자의 응답을 확인합니다.
사용자가 앱에 권한을 부여하면 비공개 사용자 데이터에 액세스할 수 있습니다. 사용자가 권한을 거부하면 권한으로 보호되는 정보 없이도 사용자에게 기능을 제공하도록 앱 환경의 성능을 단계적으로 저하합니다.
그림 1은 이 프로세스와 관련된 워크플로 및 일련의 의사 결정 사항을 보여줍니다.
앱에 이미 권한이 부여되었는지 확인
사용자가 이미 앱에 특정 권한을 부여했는지 확인하려면 ContextCompat.checkSelfPermission()
메서드에 해당 권한을 전달합니다. 이 메서드는 앱에 권한이 있는지에 따라 PERMISSION_GRANTED
또는 PERMISSION_DENIED
를 반환합니다.
앱에 권한이 필요한 이유 설명
requestPermissions()
를 호출하면 시스템에서 표시하는 권한 대화상자에 앱에서 원하는 권한이 무엇인지는 표시되지만 필요한 이유는 제시되지 않습니다. 이 때문에 사용자가 당황하는 경우도 있습니다. requestPermissions()
를 호출하기 전에 앱에서 권한을 요청하는 이유를 사용자에게 설명하는 것이 좋습니다.
연구에 따르면 앱의 핵심 기능을 지원하기 위해 또는 광고를 위해 권한이 필요한지 등 사용자가 앱에 권한이 필요한 이유를 알고 있으면 권한 요청을 훨씬 더 편안하게 느낍니다. 따라서 권한 그룹에 속하는 API 호출 중 일부만 사용하고 있다면 앱에서 사용 중인 권한과 그 이유를 명시적으로 나열하는 것이 좋습니다. 예를 들어 대략적인 위치만 사용 중인 경우 앱 설명이나 앱 도움말에 이 정보를 표시하여 사용자가 알도록 합니다.
특정 조건에서는 사용자에게 민감한 정보 액세스를 실시간으로 알리는 것이 도움이 됩니다. 예를 들어 카메라나 마이크에 액세스한다면 앱의 특정 위치 또는 알림 목록(애플리케이션이 백그라운드에서 실행 중인 경우)에 알림 아이콘을 표시하여 앱에서 데이터를 은밀하게 수집하지 않음을 사용자에게 알리는 것이 좋습니다.
앱에서 기능을 사용하기 위해 권한을 요청해야 하지만 사용자의 입장에서는 그 이유가 명확하지 않은 경우 가장 민감한 권한이 필요한 이유를 사용자에게 알릴 방법을 찾으시기 바랍니다.
ContextCompat.checkSelfPermission()
메서드가 PERMISSION_DENIED
를 반환하면 shouldShowRequestPermissionRationale()
을 호출하세요.
이 메서드가 true
를 반환하면 교육용 UI를 사용자에게 표시합니다. 이 UI에서 사용자가 사용 설정하려는 기능에 특정 권한이 필요한 이유를 설명합니다.
또한 앱이 위치, 마이크 또는 카메라와 관련된 권한을 요청하는 경우 이 정보에 앱이 액세스해야 하는 이유를 설명하는 것이 좋습니다.
권한 요청
사용자에게 교육용 UI가 표시되거나 shouldShowRequestPermissionRationale()
의 반환 값에서 교육용 UI를 표시하지 않아도 된다고 나타내면 권한을 요청합니다. 사용자에게 시스템 권한 대화상자가 표시되고 사용자는 여기서 특정 권한을 앱에 부여할지 선택할 수 있습니다.
이렇게 하려면 AndroidX 라이브러리에 포함된 RequestPermission
계약을 사용하세요. 여기서 시스템이 권한 요청 코드를 관리하도록 허용합니다. RequestPermission
계약을 사용하면 로직이 간소화되므로 가능하면 이 방법을 사용하는 것이 좋습니다. 그러나 필요한 경우 권한 요청의 일부로 요청 코드를 직접 관리하고 이 요청 코드를 권한 콜백 로직에 포함할 수도 있습니다.
시스템이 권한 요청 코드를 관리하도록 허용
시스템이 권한 요청과 연결된 요청 코드를 관리하도록 허용하려면 모듈의 build.gradle
파일에 다음 라이브러리의 종속 항목을 추가합니다.
androidx.activity
, 버전 1.2.0 이상androidx.fragment
, 버전 1.3.0 이상
그 후에 다음 클래스 중 하나를 사용할 수 있습니다.
- 단일 권한을 요청하려면
RequestPermission
을 사용합니다. - 여러 권한을 동시에 요청하려면
RequestMultiplePermissions
를 사용합니다.
다음 단계는 RequestPermission
계약을 사용하는 방법을 보여줍니다. 이 프로세스는 RequestMultiplePermissions
계약과 거의 동일합니다.
활동 또는 프래그먼트의 초기화 로직에서
ActivityResultCallback
구현을registerForActivityResult()
호출에 전달합니다.ActivityResultCallback
은 앱이 권한 요청에 대한 사용자의 응답을 처리하는 방법을 정의합니다.ActivityResultLauncher
유형인registerForActivityResult()
의 반환 값을 계속 참조합니다.필요할 때 시스템 권한 대화상자를 표시하려면 이전 단계에서 저장한
ActivityResultLauncher
인스턴스에서launch()
메서드를 호출합니다.launch()
가 호출되면 시스템 권한 대화상자가 표시됩니다. 사용자가 선택하면 시스템은 개발자가 이전 단계에서 정의한ActivityResultCallback
구현을 비동기식으로 호출합니다.참고: 앱은
launch()
를 호출할 때 표시되는 대화상자를 맞춤설정할 수 없습니다. 사용자에게 더 많은 정보나 컨텍스트를 제공하려면 앱의 UI를 변경하여 사용자가 앱의 기능에 특정 권한이 필요한 이유를 더 쉽게 알 수 있도록 합니다. 예를 들어 기능을 사용 설정하는 버튼의 텍스트를 변경할 수 있습니다.또한 시스템 권한 대화상자의 텍스트는 개발자가 요청한 권한과 연결된 권한 그룹을 참조합니다. 이 권한 그룹은 시스템의 사용 편의성을 위해 설계되었으며 앱은 특정 권한 그룹 내부 또는 외부에 있는 권한에 의존해서는 안 됩니다.
다음 코드 스니펫은 권한 응답을 처리하는 방법을 보여줍니다.
// Register the permissions callback, which handles the user's response to the
// system permissions dialog. Save the return value, an instance of
// ActivityResultLauncher. You can use either a val, as shown in this snippet,
// or a lateinit var in your onAttach() or onCreate() method.
val requestPermissionLauncher =
registerForActivityResult(RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
// Permission is granted. Continue the action or workflow in your
// app.
} else {
// Explain to the user that the feature is unavailable because the
// feature requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
}
}
// Register the permissions callback, which handles the user's response to the
// system permissions dialog. Save the return value, an instance of
// ActivityResultLauncher, as an instance variable.
private ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new RequestPermission(), isGranted -> {
if (isGranted) {
// Permission is granted. Continue the action or workflow in your
// app.
} else {
// Explain to the user that the feature is unavailable because the
// feature requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
}
});
이 코드 스니펫은 권한을 확인하고 필요할 때 사용자에게 권한을 요청하는 권장 프로세스를 보여줍니다.
when {
ContextCompat.checkSelfPermission(
CONTEXT ,
Manifest.permission.REQUESTED_PERMISSION
) == PackageManager.PERMISSION_GRANTED -> {
// You can use the API that requires the permission.
}
ActivityCompat.shouldShowRequestPermissionRationale(
this, Manifest.permission.REQUESTED_PERMISSION ) -> {
// In an educational UI, explain to the user why your app requires this
// permission for a specific feature to behave as expected, and what
// features are disabled if it's declined. In this UI, include a
// "cancel" or "no thanks" button that lets the user continue
// using your app without granting the permission.
showInContextUI(...)
}
else -> {
// You can directly ask for the permission.
// The registered ActivityResultCallback gets the result of this request.
requestPermissionLauncher.launch(
Manifest.permission.REQUESTED_PERMISSION )
}
}
if (ContextCompat.checkSelfPermission(
CONTEXT , Manifest.permission.REQUESTED_PERMISSION ) ==
PackageManager.PERMISSION_GRANTED) {
// You can use the API that requires the permission.
performAction(...);
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
this, Manifest.permission.REQUESTED_PERMISSION )) {
// In an educational UI, explain to the user why your app requires this
// permission for a specific feature to behave as expected, and what
// features are disabled if it's declined. In this UI, include a
// "cancel" or "no thanks" button that lets the user continue
// using your app without granting the permission.
showInContextUI(...);
} else {
// You can directly ask for the permission.
// The registered ActivityResultCallback gets the result of this request.
requestPermissionLauncher.launch(
Manifest.permission.REQUESTED_PERMISSION );
}
권한 요청 코드 직접 관리
시스템이 권한 요청 코드를 관리하도록 허용하는 대신 권한 요청 코드를 직접 관리할 수 있습니다. 이렇게 하려면 requestPermissions()
호출에 요청 코드를 포함합니다.
다음 코드 스니펫은 요청 코드를 사용하여 권한을 요청하는 방법을 보여줍니다.
when {
ContextCompat.checkSelfPermission(
CONTEXT ,
Manifest.permission.REQUESTED_PERMISSION
) == PackageManager.PERMISSION_GRANTED -> {
// You can use the API that requires the permission.
performAction(...)
}
ActivityCompat.shouldShowRequestPermissionRationale(
this, Manifest.permission.REQUESTED_PERMISSION ) -> {
// In an educational UI, explain to the user why your app requires this
// permission for a specific feature to behave as expected, and what
// features are disabled if it's declined. In this UI, include a
// "cancel" or "no thanks" button that lets the user continue
// using your app without granting the permission.
showInContextUI(...)
}
else -> {
// You can directly ask for the permission.
requestPermissions(CONTEXT ,
arrayOf(Manifest.permission.REQUESTED_PERMISSION ),
REQUEST_CODE )
}
}
if (ContextCompat.checkSelfPermission(
CONTEXT , Manifest.permission.REQUESTED_PERMISSION ) ==
PackageManager.PERMISSION_GRANTED) {
// You can use the API that requires the permission.
performAction(...);
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
this, Manifest.permission.REQUESTED_PERMISSION )) {
// In an educational UI, explain to the user why your app requires this
// permission for a specific feature to behave as expected, and what
// features are disabled if it's declined. In this UI, include a
// "cancel" or "no thanks" button that lets the user continue
// using your app without granting the permission.
showInContextUI(...);
} else {
// You can directly ask for the permission.
requestPermissions(CONTEXT ,
new String[] { Manifest.permission.REQUESTED_PERMISSION },
REQUEST_CODE );
}
사용자가 시스템 권한 대화상자에 응답하면 시스템은 앱의 onRequestPermissionsResult()
구현을 호출합니다. 시스템은 다음 코드 스니펫과 같이 사용자 응답을 권한 대화상자에 전달하고 개발자가 정의한 요청 코드도 전달합니다.
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>, grantResults: IntArray) {
when (requestCode) {
PERMISSION_REQUEST_CODE -> {
// If request is cancelled, the result arrays are empty.
if ((grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
// Permission is granted. Continue the action or workflow
// in your app.
} else {
// Explain to the user that the feature is unavailable because
// the feature requires a permission that the user has denied.
// At the same time, respect the user's decision. Don't link to
// system settings in an effort to convince the user to change
// their decision.
}
return
}
// Add other 'when' lines to check for other
// permissions this app might request.
else -> {
// Ignore all other requests.
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
switch (requestCode) {
casePERMISSION_REQUEST_CODE :
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission is granted. Continue the action or workflow
// in your app.
} else {
// Explain to the user that the feature is unavailable because
// the feature requires a permission that the user has denied.
// At the same time, respect the user's decision. Don't link to
// system settings in an effort to convince the user to change
// their decision.
}
return;
}
// Other 'case' lines to check for other
// permissions this app might request.
}
}
위치 정보 액세스 권한 요청
위치 정보 액세스 권한을 요청할 때는 다른 런타임 권한과 동일한 권장사항을 따르세요. 위치 정보 액세스 권한에 있어 한 가지 중요한 차이점은 시스템에 위치와 관련된 여러 권한이 포함된다는 것입니다. 어떤 권한을 어떻게 요청하는지는 앱 사용 사례의 위치 요구사항에 따라 다릅니다.
포그라운드 위치
앱에 위치 정보를 한 번만 또는 정의된 시간 동안 공유하거나 수신하는 기능이 포함되어 있으면 이 기능에는 포그라운드 위치 정보 액세스 권한이 필요합니다. 몇 가지 예는 다음과 같습니다.
- 내비게이션 앱에서 사용자가 세부 경로 안내를 받을 수 있는 기능
- 메시지 앱에서 사용자가 현재 위치를 다른 사용자와 공유할 수 있는 기능
앱의 기능이 다음 상황 중 하나에서 기기의 현재 위치에 액세스한다면 시스템은 앱에서 포그라운드 위치를 사용하고 있다고 간주합니다.
- 앱에 속한 활동이 표시됩니다.
앱에서 포그라운드 서비스를 실행하고 있습니다. 포그라운드 서비스를 실행하고 있을 때 시스템은 지속적으로 알림을 표시하여 사용자 인지도를 높입니다. 앱은 사용자가 기기의 홈 버튼을 누르거나 기기 화면을 끌 때와 같이 백그라운드에 배치되면 액세스 권한을 유지합니다.
Android 10(API 수준 29) 이상에서는 다음 코드 스니펫과 같이
location
의 포그라운드 서비스 유형을 선언해야 합니다. 이전 버전의 Android에서는 이 포그라운드 서비스 유형을 선언하는 것이 권장됩니다.<!-- Recommended for Android 9 (API level 28) and lower. -->
<!-- Required for Android 10 (API level 29) and higher. -->
<service
android:name="MyNavigationService"
android:foregroundServiceType="location" ... >
<!-- Any inner elements go here. -->
</service>
다음 스니펫과 같이 앱에서 ACCESS_COARSE_LOCATION
권한이나 ACCESS_FINE_LOCATION
권한을 요청하면 포그라운드 위치의 필요성을 선언합니다.
<manifest ... >
<!-- Include this permission any time your app needs location information. -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Include only if your app benefits from precise location access. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>
백그라운드 위치
앱의 기능이 지속적으로 다른 사용자와 위치를 공유하거나 Geofencing API를 사용하는 경우 앱에는 백그라운드 위치 정보 액세스 권한이 필요합니다. 몇 가지 예는 다음과 같습니다.
- 가족 위치 공유 앱에서 사용자가 가족 구성원과 지속적으로 위치를 공유할 수 있는 기능
- IoT 앱에서 사용자가 집을 나갈 때 꺼지고 집에 돌아올 때 다시 켜지도록 홈 기기를 구성할 수 있는 기능
앱이 포그라운드 위치 섹션에서 설명된 상황 이외의 모든 상황에서 기기의 현재 위치에 액세스한다면 시스템은 앱에서 백그라운드 위치를 사용하고 있다고 간주합니다. 백그라운드 위치 정확성은 앱에서 선언한 위치 정보 액세스 권한을 기반으로 하는 포그라운드 위치 정확성과 동일합니다.
Android 10(API 수준 29) 이상에서 개발자는 런타임 시 백그라운드 위치 액세스 권한을 요청하기 위해 앱 매니페스트에서 ACCESS_BACKGROUND_LOCATION
권한을 선언해야 합니다. 이전 버전의 Android에서는 앱이 포그라운드 위치 정보 액세스 권한을 수신하면 자동으로 백그라운드 위치 정보 액세스 권한도 수신합니다.
<manifest ... >
<!-- Required only when requesting background location access on
Android 10 (API level 29) and higher. -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>
권한 거부 처리
사용자가 권한 요청을 거부하면 앱에서는 사용자가 권한 거부에 따른 영향을 이해하도록 지원해야 합니다. 특히 앱은 권한이 없어서 작동하지 않는 기능을 사용자가 인식하도록 해야 합니다. 이때 다음 권장사항에 유의하세요.
사용자의 주의를 유도합니다. 앱에 필요한 권한이 없어서 기능이 제한된 앱 UI의 특정 부분을 강조표시합니다. 다음과 같은 작업을 실행할 수 있습니다.
- 기능의 결과나 데이터가 나타난 메시지를 표시합니다.
- 오류 아이콘과 색상이 포함된 다른 버튼을 표시합니다.
자세히 설명합니다. 일반적인 메시지는 표시하지 마세요. 대신 앱에 필요한 권한이 없어서 어떤 기능을 사용할 수 없는지 명확히 설명합니다.
사용자 인터페이스를 차단하지 않습니다. 즉, 사용자가 앱을 계속 사용하는 것을 막는 전체 화면 경고 메시지를 표시하지 않습니다.
동시에 앱은 권한을 거부하겠다는 사용자의 결정을 존중해야 합니다. Android 11(API 수준 30)부터 사용자가 앱이 기기에 설치된 전체 기간 동안 특정 권한에 관해 거부를 두 번 이상 탭하면 앱에서 그 권한을 다시 요청하는 경우 사용자에게 시스템 권한 대화상자가 표시되지 않습니다. 이러한 사용자의 작업은 '다시 묻지 않음'을 의미합니다. 이전 버전에서는 사용자가 이전에 '다시 묻지 않음' 체크박스 또는 옵션을 선택한 경우를 제외하고 앱에서 권한을 요청할 때마다 사용자에게 시스템 권한 대화상자가 표시되었습니다.
사용자가 권한 요청을 두 번 이상 거부하면 영구 거부로 간주됩니다. 따라서 사용자가 특정 기능에 액세스해야 하는 경우에만 권한을 요청하는 것이 매우 중요합니다. 그러지 않으면 의도와 달리 권한을 다시 요청하지 못하게 될 수 있습니다.
어떤 상황에서는 사용자의 조치가 없어도 권한이 자동으로 거부될 수 있습니다. 마찬가지로 권한이 자동으로 부여될 수도 있습니다. 자동 동작에 관해 어떤 가정도 하지 않는 것이 중요합니다. 앱에서 권한이 필요한 기능에 액세스해야 할 때마다 앱에 여전히 권한이 부여되어 있는지 확인합니다.
앱 권한을 요청할 때 최고의 사용자 환경을 제공하려면 앱 권한 권장사항도 참고하세요.
테스트 및 디버깅 시 거부 상태 검사
앱이 권한을 영구적으로 거부했는지 확인하려면(디버깅 및 테스트 목적) 다음 명령어를 사용하세요.
adb shell dumpsys packagePACKAGE_NAME
여기서 PACKAGE_NAME은 검사할 패키지의 이름입니다.
명령어의 출력에는 다음과 같은 섹션이 포함됩니다.
... runtime permissions: android.permission.POST_NOTIFICATIONS: granted=false, flags=[ USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED] android.permission.ACCESS_FINE_LOCATION: granted=false, flags=[USER_SET |USER_FIXED |USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED] android.permission.BLUETOOTH_CONNECT: granted=false, flags=[ USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED] ...
사용자가 한 번 거부한 권한은 USER_SET
으로 플래그가 지정됩니다.
거부를 두 번 선택하여 영구적으로 거부된 권한은 USER_FIXED
로 플래그가 지정됩니다.
테스터가 테스트 중에 요청 대화상자를 볼 수 있도록 앱 디버깅을 마쳤을 때 이러한 플래그를 재설정합니다. 이렇게 하려면 다음 명령어를 사용하세요.
adb shell pm clear-permission-flagsPACKAGE_NAME PERMISSION_NAME user-set user-fixed
PERMISSION_NAME은 재설정하려는 권한의 이름입니다.
Android 앱 권한의 전체 목록을 보려면 권한 API 참조 페이지를 참고하세요.
일회성 권한
Android 11(API 수준 30)부터 앱이 위치, 마이크 또는 카메라와 관련된 권한을 요청할 때마다 사용자에게 표시되는 권한 대화상자에 그림 2와 같이 이번만 허용이라는 옵션이 포함됩니다. 사용자가 대화상자에서 이 옵션을 선택하면 임시 일회성 권한이 앱에 부여됩니다.
그러면 앱의 동작과 사용자의 작업에 따라 일정 시간 동안 관련 데이터에 액세스할 수 있습니다.
- 앱의 활동이 표시되는 동안 앱에서 데이터에 액세스할 수 있습니다.
- 사용자가 앱을 백그라운드로 보내면 앱에서 짧은 시간 동안 데이터에 계속 액세스할 수 있습니다.
- 활동이 표시되는 동안 포그라운드 서비스가 실행되고 사용자가 앱을 백그라운드로 이동하면 포그라운드 서비스가 중지될 때까지 앱에서 데이터에 계속 액세스할 수 있습니다.
권한이 취소되면 앱 프로세스가 종료됨
사용자가 일회성 권한을 취소하면(예: 시스템 설정에서) 포그라운드 서비스 실행 여부와 상관없이 앱에서 데이터에 액세스할 수 없습니다. 다른 권한과 마찬가지로 사용자가 앱의 일회성 권한을 취소하면 앱의 프로세스가 종료됩니다.
사용자가 다음에 앱을 열고 앱의 기능에서 위치, 마이크 또는 카메라의 액세스 권한을 요청하면 사용자에게 다시 권한을 요청하는 메시지가 표시됩니다.
사용하지 않는 권한 재설정
Android는 사용하지 않는 런타임 권한을 기본값인 거부 상태로 재설정할 몇 가지 방법을 제공합니다.
- 사용하지 않는 런타임 권한에 대한 앱의 액세스를 선제적으로 삭제할 수 있는 API
- 사용하지 않는 앱의 권한을 자동으로 재설정하는 시스템 메커니즘
앱 액세스 권한 삭제
Android 13(API 수준 33) 및 이후 버전에서는 앱에 더 이상 필요하지 않은 런타임 권한에 대한 앱의 액세스 권한을 삭제할 수 있습니다. 앱을 업데이트할 때 이 단계를 진행하면 사용자가 앱에서 계속해서 특정 권한을 요청하는 이유를 더 잘 이해할 수 있습니다. 이 정보는 앱에 대한 사용자의 신뢰를 쌓는 데 도움이 됩니다.
런타임 권한에 대한 액세스를 삭제하려면 권한의 이름을 revokeSelfPermissionOnKill()
에 전달합니다.
여러 런타임 권한을 동시에 삭제하려면 권한 이름 모음을 revokeSelfPermissionsOnKill()
에 전달합니다.
권한 삭제 프로세스는 비동기적으로 이루어지며, 앱의 UID에 연결된 모든 프로세스를 종료합니다.
시스템이 권한에 대한 앱의 액세스 권한을 삭제할 수 있으려면 앱에 연결된 모든 프로세스를 종료해야 합니다. 이 API를 호출하면 시스템은 이러한 프로세스를 종료할 안전한 시기를 결정합니다. 일반적으로 시스템은 앱이 포그라운드가 아닌 백그라운드에서 장시간 실행될 때까지 기다립니다.
앱이 더 이상 특정 런타임 권한에 액세스할 필요가 없음을 사용자에게 알리려면 사용자가 다음에 앱을 실행할 때 대화상자를 표시합니다. 이 대화상자에는 권한 목록이 포함될 수 있습니다.
사용하지 않는 앱의 권한 자동 재설정
앱이 Android 11(API 수준 30) 및 이후 버전을 타겟팅하고 몇 개월 동안 사용되지 않은 경우 시스템은 사용자가 앱에 부여한 민감한 런타임 권한을 자동으로 재설정하여 사용자 데이터를 보호합니다. 앱 최대 절전 모드 가이드에서 자세히 알아보세요.
필요 시 기본 핸들러로 요청
일부 앱은 통화 기록 및 SMS 메시지와 관련된 민감한 사용자 정보에 액세스해야 합니다. 통화 기록 및 SMS 메시지와 관련된 권한을 요청하고 Play 스토어에 앱을 게시하려면, 런타임 권한을 요청하기 전에 사용자에게 허용 여부 메시지를 표시하여 앱을 핵심 시스템 기능의 기본 핸들러로 설정하도록 해야 합니다.
기본 핸들러 및 기본 핸들러 허용 여부 메시지를 사용자에게 표시하는 방법에 관한 자세한 내용은 기본 핸들러에서만 사용되는 권한에 관한 가이드를 참고하세요.
테스트 목적으로 모든 런타임 권한 부여
에뮬레이터 또는 테스트 기기에 앱을 설치할 때 모든 런타임 권한을 자동으로 부여하려면 다음 코드 스니펫에서와 같이 adb shell install
명령어에 -g
옵션을 사용합니다.
adb shell install -gPATH_TO_APK_FILE
추가 리소스
권한에 관한 자세한 정보는 다음 문서를 참조하세요.
권한 요청에 관한 자세한 내용은 권한 샘플을 참고하세요.
개인 정보 보호 권장사항을 보여주는 Codelab도 진행해 보세요.