런타임 권한 요청 정리

Jonghwan Choi·2023년 2월 6일
0

Android

목록 보기
4/9

개요

기기의 현재위치나 내부저장소 파일처럼 민감한 정보에 접근할 권한은 구글에서 따로 '위험한 권한' 혹은 '런타임 권한'으로 분류하고 있다. 안드로이드 6 (API level 23) 부터 개발자는 위험한 권한을 AndroidManifest.xml에 명시하는 것에 추가로 런타임에서 직접 유저에게 권한 부여를 요청해야 한다. 앱을 다운받고 처음 켰을 때 뜨는 아래와 같은 모양의 다이얼로그가 바로 이것이다.

API레벨마다 위험한 권한의 종류도 다르고 요청 코드도 복잡하여 상당히 짜증을 유발하는데 이번 기회에 한번 정리해 보려 한다.

위험한 권한의 종류

공식 문서

장치 기능 접근 권한

권한명용도추가 API레벨비고
ACCEPT_HANDOVER다른 앱에서 건 통화를 계속 수신 (ex.영상통화)28
ACCESS_BACKGROUND_LOCATION백그라운드에서 위치정보 접근29ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION 필요
ACCESS_COARSE_LOCATION기지국 위치를 이용해 대략적인 위치정보 접근1
ACCESS_FINE_LOCATION기기의 정확한 위치정보 접근1
ACCESS_MEDIA_LOCATION유저 파일에 기록된 위치정보 접근 (ex. 사진의 EXIF데이터)29
ACTIVITY_RECOGNITION유저의 움직임 상태 정보 접근29
BODY_SENSORS인바디 센서 정보 접근20
BODY_SENSORS_BACKGROUND백그라운드에서 인바디 센서 정보 접근33BODY_SENSORS 필요
ADD_VOICEMAILAllows an application to add voicemails into the system.14
ANSWER_PHONE_CALLS통화 자동응답26
BLUETOOTH_ADVERTISE다른 블루투스 기기에서 내 기기 검색31
BLUETOOTH_CONNECT페어링된 다른 블루투스 기기에 연결31
BLUETOOTH_SCAN다른 블루투스 기기 검색31
CALL_PHONE전화 앱 거치지 않고 통화 기능 사용1
CAMERA기기 카메라 사용1
GET_ACCOUNTSAccounts Service 사용1API 23부터는 거의 불필요 (공식문서 참조)
NEARBY_WIFI_DEVICES다른 Wi-Fi기기 검색 및 내 Wi-Fi기기 검색 허용33
POST_NOTIFICATIONS알림 표시33
PROCESS_OUTGOING_CALLS발신 통화 중 상대방 번호에 접근1API 29부터 deprecated

읽기 권한

권한명용도추가 API레벨비고
READ_CALENDAR유저 캘린더 읽기1
READ_CALL_LOG통화 이력 읽기16
READ_CONTACTS유저 주소록 읽기1
READ_EXTERNAL_STORAGE외부저장소 읽기16API 19부터 강제화, WRITE_EXTERNAL_STORAGE 취득시 자동 취득, API 33부터 불필요
(READ_MEDIA_AUDIO, READ_MEDIA_IMAGES, READ_MEDIA_VIDEO 로 대체)
READ_MEDIA_AUDIO외부저장소 오디오파일 읽기33READ_EXTERNAL_STORAGE 대체
READ_MEDIA_IMAGES외부저장소 이미지파일 읽기33READ_EXTERNAL_STORAGE 대체
READ_MEDIA_VIDEO외부저장소 비디오파일 읽기33READ_EXTERNAL_STORAGE 대체
READ_PHONE_NUMBERS기기 전화번호 읽기26
READ_PHONE_STATE기기 상태 (네트워크 정보 등) 읽기1

쓰기 권한

권한명용도추가 API레벨비고
WRITE_CALENDAR유저 캘린더 쓰기1
WRITE_CALL_LOG통화 이력 쓰기16
WRITE_CONTACTS유저 주소록 쓰기1
WRITE_EXTERNAL_STORAGE외부저장소 쓰기4API 30부터 효력 상실. 아래 참고

메시지 및 기타 권한

권한명용도추가 API레벨비고
READ_SMSSMS메시지 읽기1
RECEIVE_MMSMMS메시지 받기1
RECEIVE_SMSSMS메시지 받기1
RECEIVE_WAP_PUSHWAP푸쉬 허용1
RECORD_AUDIO녹음 기능 사용1
SEND_SMSSMS메시지 보내기1
USE_SIPSIP서비스 사용9
UWB_RANGINGRequired to be able to range to devices using ultra-wideband.31

런타임 권한 요청

권한 요청은 액티비티에서 진행하는데, 권한 요청 결과를 2가지 콜백 메소드로 받을 수 있다. 하나는 액티비티가 가진 onRequestPermissionResult() 메소드를 오버라이딩하는 것이고, 다른 하나는 registerForAcitivityResult() 메소드를 호출하는 것이다. 두 방식은 request code 처리법에서 차이가 있는데, 구글에서 전자를 deprecate시켰기 때문에 장기적으로 후자를 쓰는 것이 유리할 것으로 생각된다. 공식 문서에 두 방식 모두 설명되어 있다.

전체 프로세스

권한 요청 프로세스는 아래의 순서로 진행된다.

  1. Manifest에 필요한 권한 선언
  2. 런타임에서 권한 부여 상태 확인
  3. 유저에게 권한 필요 이유 설명 (필수 아님)
  4. 권한 요청 API 작동
  5. 유저가 API에서 권한 승인/거부 진행 (개발자 개입 없음)
  6. API로부터 권한 요청 결과를 콜백으로 수신
  7. 결과에 따라 후속 작업 진행

아래의 코딩 예시는 1, 3, 5를 생략한 것이다. API 레벨에 따라 요청하는 권한이 달라지는 케이스를 보여주기 위해 WRITE_EXTERNAL_STORAGE 권한으로 예시를 잡았다.

onRequestPermissionResult()

  • request code를 개발자가 직접 지정하고 검증
  • 구글에서 deprecate시킴
class MainActivity : AppCompatActivity() {

    val PERMISSION_REQUEST_CODE = 1234 //임의의 정수 지정

    // 6. API로부터 권한 요청 결과를 콜백으로 수신
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>, 
        grantResults: IntArray
    ) {
        // 7. 결과에 따라 후속 작업 진행
        when (requestCode) {
            PERMISSION_REQUEST_CODE -> {
                if (
                    grantResults.isNotEmpty() 
                    && (grantResults[0] == PackageManager.PERMISSION_GRANTED)
                ) {
                    Toast.makeText(this, "권한 승인", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(this, "권한 거부", Toast.LENGTH_SHORT).show()
                }
                return
            }
            else -> {
                // Ignore all other requests.
            }
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
    
        val wes = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
    
        // 2. 런타임에서 권한 부여 상태 확인
	    if (VERSION.SDK_INT < 29) {
            if (checkSelfPermission(wes) != GRANTED){
        
                // 4. 권한 요청 API 작동
                requestPermissions(
                    this,
                    arrayOf(wes),
                    PERMISSION_REQUEST_CODE
                )
            }
        }
    }
}

registerForAcitivityResult()

  • request code를 시스템이 지정하고 검증
  • 구글에서 밀어주는 방식
class MainActivity : AppCompatActivity() {

    // 6. API로부터 권한 요청 결과를 콜백으로 수신
    // 4보다 먼저 초기화되어야 함
    private val permissionLauncher = 
        registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
        
            // 7. 결과에 따라 후속 작업 진행
            if(permissions.entries.all { it.value }){
                Toast.makeText(this, "권한 승인", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this, "권한 거부", Toast.LENGTH_SHORT).show()
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
	
        val wes = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
        
        // 2. 런타임에서 권한 부여 상태 확인
	    if (VERSION.SDK_INT < 29) {
            if (checkSelfPermission(wes) != GRANTED){
        
                // 4. 권한 요청 API 작동
                permissionLauncher.launch(arrayOf(wes))
            }
        }
    }
}
profile
유니티 게임 클라이언트 개발자를 꿈꾸는 뉴비

0개의 댓글