Android 6.0 이상에서는 앱에 권한이 필요함을 선언하려면 해당 권한을 Manifest에 나열하고 런타임에 사용자가 권한을 승인하도록 해야합니다.  Android 지원 라이브러리를 사용하여 권한을 확인하고 요청하는 방법을 알아보겠습니다.

 

Android 프레임워크에서는 Android 6.0(API level 23)과 유사한 메서드를 제공하지만 지원라이브러리르 사용하여 더욱 쉽게 이전 Android 버전과 호환성을 지원할 수 있습니다.

 

manifest에 권한 추가

Android에서는 버전에 상관없이 앱에 권한이 필요함을 선언하려면 manifest에 <uses-permission> 요소를 최상위 <manifest>의 하위요소로 지정합니다.

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

    <uses-permission android:name="android.permission.CAMERA"/>

    <application
    	...
    </application>

</manifest>

권한 선언 후 해당 권한의 민감한 정도에 따라 시스템 동작은 달라집니다. 어떤 권한은 '정상'권한으로 간주되어 시스템이 설치 시에 곧바로 부여하지만, 어떤 권한은 '위험'권한으로 간주되어 사용자가 명시적으로 엑세스 권한을 부여해야 합니다. 권한에 관한 자세한 정보는 보호수준에서 확인할 수 있습니다.

 

권한 확인

앱에 위험 권한이 필요한 경우 해당 권한이 요구되는 작업을 할 때마다 권한이 있는지 확인해야합니다.

Android 6.0(API level 23)부터는 앱이 더 낮은 API 레벨을 타겟팅하더라도 사용자가 언제든 앱의 권한을 취소할 수 있습니다. 따라서 어제 카메라를 사용했다고 해서 오늘도 권한이 있다고 가정할 수는 없습니다.

 

권한이 있는지 확인하려면 ContextCompat.checkSelfPermission()메서드를 호출합니다.

// 권한 체크하기
if(ContextCompat.checkSelfPermission(this@WelcomeActivity, Manifest.permission.CAMERA) ==
   PackageManager.PERMISSION_GRANTED) {
	// 권한이 있을 때 카메라 실행하기
	Toast.makeText(this, "카메라 허가 가능, 프리뷰 시작", Toast.LENGTH_SHORT).show()
	startCamera()
} else {
	// 권한이 없을 때 권한 요청하기
	requestCameraPermission()
}

앱에 권한이 있는 경우 이 메서드는 PERMISSION_GRANTED를 반환하고, 앱은 작업을 계속 진행할 수 있습니다. 앱에 권한이 없는 경우 이 메서드는 PERMISSION_DENIED를 반환하고, 앱은 사용자에게 명시적으로 권한을 요청해야합니다.

 

권한 요청

앱이 checkSelfPermission()에서 PERMISSION_DENIED를 수신하면 사용자에게 해당 권한을 요청하는 메시지를 표시해야합니다. Android는 requestPermissions()과 같이 권한을 요청하는데 사용할 수 있는 여러 메서드를 제공합니다. 이러한 메서드를 호출하면 표준 Android 대화상자가 나타나며, 이 상자는 맞춤설정할 수 없습니다.

 

사용자에게 표시되는 방식은 기기 Android버전과 애플리케이션의 대상 버전에 따라 다릅니다. 자세한 내용은 권한 개요에 설명됩니다.

 

앱에 권한이 필요한 이유 설명

사용자가 사진앱을 실행하는 경우 사용자에게 카메라 사용 권한을 요청해도 놀라지 않을 것입니다. 그러나 사용자 위치나 연락처에 접근하려고 하면 사용자가 이유를 이해하지 못할 수도 있습니다. 앱에서 권한을 요청하기 전에 먼저 사용자에게 이유를 설명하는 것을 고려해야합니다. 명심할 점은, 설명이 사용자에게 부담이 되어서는 안됩니다. 너무 많은 설명을 제공할 경우 사용자가 짜증을 느끼고 앱을 제거할 수도 있습니다.

 

권한 설명에서 사용할 수 있는 한 가지 방법은 사용자가 해당 권한 요청을 이미 거절한 경우에만 설명을 제공하는 것입니다. Android에서는 이를 위해 유틸리티 메서드 shouldShowRequestPermissionRationable()를 제공합니다. 이 메서드는 사용자가 전에 해당 요청을 거부한 경우에는 true를 반환하고, 사용자가 해당 권한을 거부했으며 '다시 묻지 않음' 옵션을 선택했거나 기기 정책에서 해당 권한을 금지하는 경우에는 false를 반환합니다.

 

사용자가 권한이 요구되는 기능을 계속 사용하려고 시도하면서도 권한 요청을 계속 거절한다면 아마도 이 사용자는 해당 기능을 제공하기 위해 앱에 권한이 필요한 이유를 모를 수도 있습니다. 이런 상황에서는 설명을 하는 것이 좋을 수 있습니다. 권한을 요청할 때 좋은 사용자 환경을 만드는 방법에 관한 권장사항을 확인할 수 있습니다.

 

여러분에게 필요한 권한 요청

앱에 필요한 권한이 아직 없는 경우 앱은 requestPermissions() 메서드 중 하나를 호출하여 적절한 권한을 요청해야 합니다. 앱은 원하는 권한 및 이 권한 요청을 식별하기 위해 지정된 정수 요청 코드를 전달합니다. 이 메서드는 비동기식으로 작동합니다. 즉각적으로 반환되며, 사용자가 메시지에 응답하면 시스템은 그 결과를 가지고 앱의 콜백 메서드를 호출하여 앱이 requestPermissions()에 전달한 것과 동일한 요청 코드를 전달합니다.

 

아래의 예는 카메라 권한을 요청하는 코드입니다.

시나리오

  • 카메라 접근 권한이 있는지 확인

  • 권한이 없으면 권한이 필요한 이유를 표시해야하는지 확인

  • 권한 요청

private fun requestCameraPermission() {

	if (ActivityCompat.shouldShowRequestPermissionRationale(this@WelcomeActivity, Manifest.permission.CAMERA)) {
        Toast.makeText(this@WelcomeActivity, "카메라 권한이 요구됩니다", Toast.LENGTH_SHORT).show()
        ActivityCompat.requestPermissions(this@WelcomeActivity, arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CAMERA)
    } else {
        // 다시 묻지 않음을 누르고 거부하고 요청했을 때
        Toast.makeText(this@WelcomeActivity, "카메라 허가를 받을 수 없습니다.", Toast.LENGTH_SHORT).show()
        ActivityCompat.requestPermissions(this@WelcomeActivity, arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CAMERA)
    }
}

참고 : requestPermissions()을 호출하면 시스템은 표준 대화상자를 사용자에게 표시합니다. 이 대화상자를 구성하거나 변경할 수 없습니다. 앱에 권한이 필요한 이유에 나오는 것처럼, 사용자에게 정보나 설명을 제공해야하는 겨우 requestPermissions()를 호출하기 전에 제공하는 것이 좋습니다.

권한 요청 응답 처리

사용자가 앱 권한 요청에 응답하면 시스템은 앱의 onRequestPermissionResult() 메서드를 호출하여 사용자 응답을 전달합니다. 권한이 부여되었는지 확인하려면 앱은 해당 메서드를 재정의해야 합니다. 이 콜백에는 requestPermissions()에 전달한 것과 동일한 요청 코드가 전달됩니다.

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

    if(requestCode == PERMISSION_REQUEST_CAMERA) {
        if(grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(this, "카메라의 허가를 받았다", Toast.LENGTH_SHORT).show()
            startCamera()
        }
    } else {
        Toast.makeText(this, "카메라 요청이 거부되었다", Toast.LENGTH_SHORT).show()
    }
}

시스템이 표시하는 대화상자에서는 앱이 액세스해야 하는 권한 그룹을 설명합니다. 특정 권한은 나열하지 않습니다. 

 

예를 들어 READ_CONTACTS 권한을 요청하는 경우 기기의 연락처에 액세스해야 한다는 메시지만 시스템 대화상자에 나타납니다. 사용자는 각 권한 그룹에 관해 한번만 권한을 부여해야 합니다.

 

앱이 해당 그룹에 있는 다른 권한(앱 manifest에 나열된 다른 권한)을 요청하는 경우 시스템이 자동으로 권한을 부여합니다. 여러분이 권한을 요청하면 시스템은 사용자가 시스템 대화상자를 통해 명시적으로 요청을 승인했을 때와 동일한 방식으로 onRequestPermissionsResult() 콜백 메서드를 호출하고 PERMISSION_GRANTED를 전달합니다.

 

참고 : 사용자가 이미 동일한 그룹에 있는 다른 권한을 부여한 경우라도 앱이 필요한 모든 권한을 명시적으로 요청해야 합니다. 향후 Android 리리스에서는 권한 그룹화도 변경될 수 있습니다. 코드에서는 특정 권한이 동일한 그룹에 있다고 가정하거나 없다고 가정해서는 안됩니다.

 

예를 들어 manifest에 READ_CONTACTS 및 WRITE_CONTACTS를 둘 다 나열한다고 가정합니다. READ_CONTACTS를 요청하여 사용자가 권한을 부여한 후 WRITE_CONTACTS를 요청하면 시스템이 사용자와 상호작용 없이 곧바로 해당 권한을 부여합니다.

 

사용자가 권한 요청을 거부하는 경우 앱은 적절한 작업을 수행해야 합니다. 예를 들어 해당 권한이 필요한 작업을 사용자가 요청하면, 수행할 수 없는 이유를 설명하는 대화상자를 표시할 수 있습니다.

 

시스템에서 사용자에게 권한을 부여하도록 요청하면 사용자는 해당 권한을 다시 요청하지 말도록 시스템에 지시할 수 있습니다. 이 경우 앱이 해당 권한을 다시 요청하기 위해 requestPermissions()를 사용할 때 마다 시스템은 즉시 해당 요청을 거부합니다. 시스템은 사용자가 명시적으로 요청을 다시 거부했을 때와 동일한 방식으로 onRequestPermissionResult() 콜백 메서드를 호출하고 PERMISSION_DENIED를 전달합니다.

 

앱이 해당 권한을 가지지 못하도록 기기 정책에서 금지하는 경우에도 이 메서드는 false를 반환합니다. 즉, requestPermissions()를 호출하는 경우 사용자와의 직접적 상호작용이 발생했다고 가정할 수 없습니다.

 

Reference

 

Android Runtime PermissionBasicSample을 참고하여 만들었습니다. 

package com.hyun.sqlite

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.activity_welcome.*

const val PERMISSION_REQUEST_CAMERA = 0

class WelcomeActivity : AppCompatActivity() {

    val TAG: String = ".WelcomeActivityAKDJKA"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_welcome)

        btn_camera.setOnClickListener {
            showCameraPreview()
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array<out String>, grantResults: IntArray
    ) {
        Log.d(TAG, "onRequestPermissionsResult")

        if (requestCode == PERMISSION_REQUEST_CAMERA) {
            if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "카메라의 허가를 받았다. 프리뷰 시작", Toast.LENGTH_SHORT).show()
                startCamera()
            }
        } else {
            Toast.makeText(this, "카메라 요청이 거부되었다", Toast.LENGTH_SHORT).show()
        }
    }

    // 1
    fun showCameraPreview() {
        Log.d(TAG, "showCameraPreview")

        // 권한 체크하기
        if (ContextCompat.checkSelfPermission(this@WelcomeActivity, Manifest.permission.CAMERA) ==
            PackageManager.PERMISSION_GRANTED
        ) {
            // 권한이 있을 때 카메라 실행하기
            Toast.makeText(this, "카메라 허가 가능, 프리뷰 시작", Toast.LENGTH_SHORT).show()
            startCamera()
        } else {
            // 권한이 없을 때 권한 요청하기
            requestCameraPermission()
        }
    }


    private fun requestCameraPermission() {
        Log.d(TAG, "requestCameraPermission()")

        if (ActivityCompat.shouldShowRequestPermissionRationale(
                this@WelcomeActivity,
                Manifest.permission.CAMERA
            )
        ) {
            Toast.makeText(this@WelcomeActivity, "카메라 권한이 요구됩니다", Toast.LENGTH_SHORT).show()
            ActivityCompat.requestPermissions(
                this@WelcomeActivity,
                arrayOf(Manifest.permission.CAMERA),
                PERMISSION_REQUEST_CAMERA
            )
        } else {
            // 다시 묻지 않음을 누르고 거부하고 요청했을 때
            Toast.makeText(this@WelcomeActivity, "카메라 허가를 받을 수 없습니다.", Toast.LENGTH_SHORT).show()
            ActivityCompat.requestPermissions(
                this@WelcomeActivity,
                arrayOf(Manifest.permission.CAMERA),
                PERMISSION_REQUEST_CAMERA
            )
        }
    }

    private fun startCamera() {
        Log.d(TAG, "startCamera()")
        Intent(this@WelcomeActivity, CameraPreviewActivity::class.java).let {
            startActivity(it)
        }
    }
}

'Android' 카테고리의 다른 글

[Kotlin] 안드로이드 권한 - 1  (0) 2019.09.30
[Design] Material Design  (0) 2019.08.18
[Kotlin] Room Library  (0) 2019.08.17
Jetpack, AndroidX  (0) 2019.07.30
Singleton, MVC, MVP, MVVM 한 눈에 보기  (0) 2019.07.18

+ Recent posts