안드로이드 코틀린 : 런타임 권한(위험 권한) 요청 코드 작성 방법 - 카메라 및 외부 저장소 권한 사용 예시
Lucy Archive
Lucy / Facilitate4U
2021. 3. 16. 13:59

안드로이드 권한2 : 위험 권한 허가 확인 및 허가 요청 코드 작성 방법

이전 포스트에서 안드로이드 권한에 대해 간략히 설명하고 일반 권한 사용법을 정리하였습니다. 이번 포스트에서는

카메라, 공용 저장소와 같이 사용자의 개인 정보와 관련된 위험 권한 사용하는 방법

에 대해 정리합니다. 일반 권한 사용법은 이전 포스트인 아래의 링크를 참조해주세요.

안드로이드 권한 - 일반 권한

 

안드로이드 코틀린 : 권한 개요 및 일반 권한 사용하기 - WebView

안드로이드 권한1 : 인터넷 권한을 사용하여 WebView로 웹 페이지 보기 안드로이드 권한에 대해 알아보고, 일반 권한을 사용하는 법으로 앱 내부 WebView를 사용하여 웹페이지를 보는 예제 를 소개합

juahnpop.tistory.com

 

런타임 권한(위험 권한)

런타임 권한 종류

위험 권한은 사용자의 개인정보와 관련된 데이터나 기능으로, 앱 실행중에 해당 데이터나 기능을 실행시 사용자에게 허가를 받아야 합니다. 아래는 위험 권한에 해당되는 예시입니다.

  • 캘린더 읽기/쓰기
  • 카메라
  • 주소록 읽기/쓰기/계정정보 가져오기
  • 위치 정보 사용
  • 마이크 녹음
  • 전화,문자관련
  • 바디 센서
  • 안드로이드 공용 저장소 읽기/쓰기
<!--위험 권한 - 코드 작성 필요-->

<!--권한 그룹 : CARENDAR-->
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<!--권한 그룹 : CAMERA    -->
<uses-permission android:name="android.permission.CAMERA"/>
<!--권한 그룹 : CONTACTS-->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!--권한 그룹 : LOCATION-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION_LOCATION"/>
<!-- 권한 그룹 : MICROPHONE-->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!-- 권한 그룹 : PHONE-->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.MODIFY_PHONE_NUMBER"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<uses-permission android:name="android.permission.ADD_VOICEMAIL"/>
<uses-permission android:name="android.permission.USE_SIP"/>
<!-- 권한 그룹 : SENSOR-->
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<!-- 권한 그룹 : SMS-->
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<!-- 권한 그룹 : STORAGE-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

안드로이드 기기 기능의 권한 명세 및 권한 요청 필요성 검토는 아래의 링크를 참조해주세요.

앱 권한 선언 필요한지 평가하기

 

앱에서 권한을 선언해야 하는지 평가하기  |  Android 개발자  |  Android Developers

앱에서 권한을 선언하기 전에 필요성을 고려하세요. 사용자가 런타임 권한이 필요한 앱 기능을 사용하려고 할 때마다 앱에서 권한 요청을 위해 사용자의 작업을 중단해야 합니다. 그러면 사용

developer.android.com

 

권한 처리하기

위험 권한을 사용하기 위해 아래의 절차로 진행합니다.

  • Manifest에 권한 명세
  • 권한 확인 및 요청 코드 작성

권한 명세

사용하고자 하는 권한을 AndroidManifest.xml 파일에서 <uses-permission> 태그를 사용하여 권한 추가합니다. 카메라와 외부메모리를 사용하고 싶은 경우 아래와 같이 코드를 작성합니다. 자동완성 기능을 사용하면 쉽게 코드 작성이 가능합니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blacklog.permission">

    <!--권한 그룹 : CAMERA -->
    <uses-permission android:name="android.permission.CAMERA"/>
    <!-- 권한 그룹 : STORAGE-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Permission">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

API버전별 권한 명세 옵션

권한의 종류에 따라 API별 권한 명세 조건이 다른 경우가 있습니다. 예를들어, 앱에서 공용 저장소의 사진, 동영상 등의 콘텐츠를 표시 하는 경우 아래와 같이 API 수준에 따라 처리 방식이 구분됩니다.

  • API 28이하 : 외부 저장소 읽기 권한 명세 필요
  • API 29이상 : 외부 저장소 읽기 권한 명세 불필요, 미디어 저장소를 사용하여 파일을 열기

이런 경우 <user-permission> 태그의 maxSdkVersion 옵션을 사용하여 API 28이하 기기에서만 권한을 명세하도록 설정 할 수 있습니다.

....
<uses-permission 
  android:name="android.permission.READ_EXTERNAL_STORAGE" 
  android:maxSdkVersion="28"/>
....

(참고)기능 명세 필터링 옵션 변경

<uses-permission> 으로 AndroidManifest.xml 에 카메라 권한이 명세되어 있으면, 카메라가 없는 기기의 구글 앱스토어에서는 해당 앱이 검색 되지 않습니다. 자동적으로 기기의 하드웨어 스펙에 맞게 필터링됩니다. 만약 카메라가 없는 기기에서도 해당 앱이 검색되어야 하면 아래의 같이 <uses-feature> 태그 및 requeired 옵션을 이용해서 앱 필터링을 조정할 수 있습니다.

  • requeired 옵션
    • true : 해당 기능이 없는 기기의 앱 스토어에는 앱이 검색 되지 않음
    • false : 해당 기능의 유/무와 관계없이 앱스토에서 검색 가능
	....
    
    <!--권한 그룹 : CAMERA -->
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-feature android:name="android.permission.CAMERA" android:required="false"/>
    <!-- 권한 그룹 : STORAGE-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    
    ....

위와 같이 권한과 기능이 명세되어 있는 앱은, 카메라가 없는 기기의 앱스토어에서 검색이 가능합니다.

 

권한 요청 관련 코드

일반적으로 권한 요청은 아래의 3가지 단계가 있습니다.

  • 권한 확인하기
    • ContextCompat.checkSelfPermission() 메서드 사용
    • 권한 확인 결과 승인시 PackageManager.PERMISSION_GRANTED 값을 반환
  • (권한 미 승인시)권한 요청하기 
    • 권한 요청은 ActivityCompat.requestPermission() 함수를 사용합니다.
    • 권한 요청은 그룹으로 요청 합니다.
  • 권한 요청 결과 처리 
    • onRequestPermissionResult() 함수를 override 하여 결과를 처리합니다.

 

권한 확인하기

권한 확인은 ContextCompat.CheckSelfPermission() 메서드를 사용합니다. 메서드의 인자 첫번째는 Context, 두번째 인자는 확인할 권한을 입력하면 됩니다. 메서드가 반환하는 값은 아래와 같습니다. 

  • ContextCompat.CheckSelfPermission() 반환 값
    • 권한 승인  : PackageManager.PERMISSION_GRANTED
    • 권한 미승인 : PackageManager.PERMISSION_DENIED
fun checkPermission(){
    val permission = Manifest.permission.CAMERA
    val permissionResult = ContextCompat.checkSelfPermission(this, permission)

    when(permissionResult){
        PackageManager.PERMISSION_GRANTED -> {
            Toast.makeText(this, "Permission Granted", Toast.LENGTH_SHORT).show()
            // Go Main Function
        }
        PackageManager.PERMISSION_DENIED -> {
            Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show()
            // Go Request Permission
        }
    }
}

권한 요청하기

권한 요청은 ActivityCompat.requestPermissions() 함수를 사용하며, 권한 요청은 그룹으로 요청합니다. 함수의 인자로 Context, 권한 어레이(권한 그룹별 처리) 및 권한 요청 코드를 사용합니다. 권한 요청 코드는 사용자가 지정하고, 권한 그룹별 권한 요청 결과를 식별하기 위해 사용됩니다.

fun checkPermission(){
    val permission = Manifest.permission.CAMERA
    val permissionResult = ContextCompat.checkSelfPermission(this, permission)

    when(permissionResult){
        PackageManager.PERMISSION_GRANTED -> {
            Toast.makeText(this, "Permission Granted", Toast.LENGTH_SHORT).show()
            // Go Main Function
        }
        PackageManager.PERMISSION_DENIED -> {
            Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show()
            ActivityCompat.requestPermissions(this, arrayOf(permission), 100)
        }
    }
}

권한 요청 결과 확인

권한 요청 결과 처리 코드는 onRequestPermissionResult() 콜백 함수에서 작성합니다. 클래스 내에서 Ctrl + O 를 눌러 콜백 함수를 검색 후 Override 할 수 있습니다. 

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

    when(requestCode){
        100 -> {
            if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
                Toast.makeText(this, "Permission Granted", Toast.LENGTH_SHORT).show()
                // Go Main Function
            }else{
                Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show()
                // Finish() or Show Guidance on the need for permission
            }
        }
    }
}

 

권한 처리하기 예제

프로젝트 생성

  • 템플릿 : Empy Activity
  • 프로젝트명 : Permission
  • 언어 : Kotlin

앱 권한 매니패스트에 등록

app > manifests > AndroidManifest.xml 에 인터넷 사용 권한을 명시합니다. 카메라와 외장 메모리 읽기/쓰기를 등록하였습니다. 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blacklog.permission">

    <!--권한 그룹 : CAMERA -->
    <uses-permission android:name="android.permission.CAMERA"/>
    <!-- 권한 그룹 : STORAGE-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Permission">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

View Binding 환경 설정

build.gradle(Module) 파일에 viewBinding 요소를 추가합니다.

android { 
  	... 
    viewBinding { 
   		enabled = true 
    } 
}

실제 build.gradle(Module) 파일에서 아래와 같이 코드를 추가하면 됩니다.

biuld.gradle에서 viewBinding 설정 코드 추가

※ View Binding은 레이아웃 View의 Id를 사용해서 코드와 연결해주는 도구 입니다. View Binding과 관련된 설명은 아래의 링크를 참조해주세요.

안드로이드 View Binding

 

안드로이드 View Binding 사용하기 - kotlin-android-extensions 지원 중단

안드로이드 View Binding 방법 정리 안드로이드 코드에서 레이아웃 View에 접근하기 위해 사용된 kotlin-android-extensions 의 지원이 중단예정으로, 이를 대체하여 사용 할 수 있는 ViewBinding 사용법에 대해

juahnpop.tistory.com

Manifest에 권한 명세하기

카메라와 외부저장소 권한을 명세하기 위해 AndroidManifest.xml 에 아래와 같이 코드를 작성합니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blacklog.permission">

    <!--권한 그룹 : CAMERA -->
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-feature android:name="android.permission.CAMERA" android:required="true"/>
    <!-- 권한 그룹 : STORAGE-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>


    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Permission">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

UI 만들기

레이아웃 파일 activity_main.xml 파일에서 아래와 같이 코드를 작성 합니다. 기존 코드에서 textView를 삭제하고 2개의 버튼을 추가하였습니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnCameraPermission"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:text="Camera Permission"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnStoragePermission"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:text="External Storage Permission"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnCameraPermission" />

</androidx.constraintlayout.widget.ConstraintLayout>

activity_main.xml

코드 작성하기 MainActivity.kt

아래 코드는 카메라와 공용 저장소 권한을 확인하고 승인 받기 위한 코드입니다. 

package com.blacklog.permission

import android.Manifest
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.blacklog.permission.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity(){

    lateinit var binding : ActivityMainBinding
    // 권한 정의
    val CAMERA_PERMISSION = arrayOf(Manifest.permission.CAMERA)
    val CAMERA_PERMISSION_REQUEST = 100

    val STORAGE_PERMISSION = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
    val STORAGE_PERMISSION_REQUEST = 200

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        binding.btnCameraPermission.setOnClickListener {
            checkPermission(CAMERA_PERMISSION, CAMERA_PERMISSION_REQUEST)
        }
        binding.btnStoragePermission.setOnClickListener {
            checkPermission(STORAGE_PERMISSION, STORAGE_PERMISSION_REQUEST)
        }
    }

    fun checkPermission(permissions: Array<String>, permissionRequestNumber:Int){
        val permissionResult = ContextCompat.checkSelfPermission(this, permissions[0])

        when(permissionResult){
            PackageManager.PERMISSION_GRANTED -> {
                Toast.makeText(this, "Permission Granted", Toast.LENGTH_SHORT).show()
                // Go Main Function
            }
            PackageManager.PERMISSION_DENIED -> {
                ActivityCompat.requestPermissions(this, permissions, permissionRequestNumber)
            }
        }
    }

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

    when(requestCode){
        CAMERA_PERMISSION_REQUEST -> {
            if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
                Toast.makeText(this, "Camera Permission Granted", Toast.LENGTH_SHORT).show()
                // Go Main Function
            }else{
                Toast.makeText(this, "Camera Permission Denied", Toast.LENGTH_SHORT).show()
                // Finish() or Show Guidance on the need for permission
            }
        }
        STORAGE_PERMISSION_REQUEST -> {
            if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
                Toast.makeText(this, "Storage Permission Granted", Toast.LENGTH_SHORT).show()
                // Go Main Function
            }else{
                Toast.makeText(this, "Storage Permission Denied", Toast.LENGTH_SHORT).show()
                // Finish() or Show Guidance on the need for permission
            }
        }
    }
}

    fun toast(message: String){
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
    }
}

실행 결과

위 코드를 실행하면 아래와 같이 Camera 및 Storage 권한이 승인 되는 것을 확인 할 수 있습니다. 

앱 실행 결과

끝까지 읽어 주셔서 감사합니다.^^

관련포스트

🤞 안드로이드 앱 제작 관련글 목록 보기

🤞 안드로이드 권한 관련글 목록 보기