Home 안드로이드 권한 요청하기
Post
Cancel

안드로이드 권한 요청하기

  • 최종 수정일자 : 2022-03-14 23:17:18 +0900

안드로이드 권한 요청하기

  • 안드로이드의 Dangerous 권한은 API 23(마시멜로우) 이상부터는 런타임 시에 권한을 요청하도록 변경되었으며 API 22 이하는 권한에 상관없이 설치시에 자동으로 부여된다.

  • API 23 이상부터는 사용자가 Dangerous 권한이 필요한 기능을 시작하려고 할 때에만 권한을 요청해야하며, 사용자가 필요한 권한을 거부하더라도 다른 기능들까지 사용을 못하게 해서는 안된다.

    • 스노우 어플에서 권한 거부 시 아래와 같이 보여진다.

    snow

권한 요청 Workflow

  • 런타임 권한을 요청할 때에는 아래와 같은 방법으로 요청 및 구현해야 한다.

    workflow-runtime

    1. 매니페스트 파일에 권한을 정의한다.
    2. 사용자가 권한이 필요한 기능을 시작할 때 앱에서는 이미 권한이 수락되었는지 확인한다.
    3. 권한이 수락되지 않은 경우 왜 이 권한이 필요한지에 대해 사용자에게 설명한 후 권한 수락 창을 띄운다.
      • 설명은 필수가 아니지만, 연구에 따르면 필요한 이유가 설명되어 있을 경우 사용자가 안심하고 수락할 가능성이 높다고 한다.
    4. 권한이 이미 수락된 경우 해당 기능이 동작할 수 있도록 한다.
    5. 권한을 거부한 경우 위 스노우 어플처럼 해당 기능만 사용을 중지하고 나머지 기능들은 그대로 둔다.

구현하기

  • READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE 퍼미션을 요청하는 코드이다.

1. 매니페스트 파일에 권한을 정의한다.

AndroidManifest.xml
1
2
3
4
5
6
7
<manifest ...>
...
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28"/>
...
</manifest>
  • WRITE_EXTERNAL_STORAGE의 경우 Andoid 9.0 (API LEVEL 28, 파이)까지만 해당 퍼미션을 이용해 권한 요청이 가능하다.
  • Android 10.0부터는 Scoped Storage가 적용되어 WRITE_EXTERNAL_STORAGE의 사용이 불가능하다.
    • 단, Android 10에서는 requestLegacyExternalStorage를 이용해 저장소에 접근할 수 있다고 한다.
    • 그러나 11부터는 적용되지 않으므로 주의해야 한다.


2. 권한 관련 확장 함수를 작성한다.

AppCompatActivityExt.kt
1
2
3
4
5
6
7
8
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat

fun AppCompatActivity.checkSelfPermissionCompat(permission: String) =
    ActivityCompat.checkSelfPermission(this, permission)

fun AppCompatActivity.requestPermissionsCompat(permissionsArray: Array<String>, requestCode: Int) =
    ActivityCompat.requestPermissions(this, permissionsArray, requestCode)
  • 끝이 Compat으로 끝나는 경우 호환성 이슈가 있는 경우 내부적으로 알아서 처리해준다.
  • ActivityCompatrequestPermissions함수를 살펴보면 아래와 같다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      public static void requestPermissions(final @NonNull Activity activity,
          final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
      [...]
    
      if (Build.VERSION.SDK_INT >= 23) {
          [...]
          activity.requestPermissions(permissions, requestCode);
      } else if (activity instanceof OnRequestPermissionsResultCallback) {
          [...]
      }
    
  • API Level 23 이상에서 Dangerous 타입의 퍼미션인 경우 런타임 퍼미션으로 실행해야하고 그 이전 버전에서는 런타임 퍼미션이 적용되지 않는다.
  • 따라서 내부적으로 위 코드처럼 23버전과 23이전 버전을 나누어 구현되어 있다.
  • 사용하려는 함수가 XXXCompat에도 있다면 Compat에 있는 함수를 이용하는게 나을 것 같다.


3. 메인 액티비티를 구현한다.

MainActivity.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import android.Manifest
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.core.app.ActivityCompat

const val READWRITE_EXTERNAL_PERMISSION = 0

class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsResultCallback {

    private lateinit var tvReadWritePermission: TextView
    private lateinit var btnPermissionCheck: Button
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        tvReadWritePermission = findViewById(R.id.textview_readwrite_permission)
        btnPermissionCheck = findViewById(R.id.btn_get_permission)
        btnPermissionCheck.setOnClickListener {
            checkPermissions()
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        var grantCount = 0
        if (requestCode == READWRITE_EXTERNAL_PERMISSION) {
            for (result in grantResults) {
                if (result == PackageManager.PERMISSION_GRANTED) {
                    grantCount++
                }
            }

            if (grantCount == 2) {
                Toast.makeText(this, "읽기/쓰기 가능", Toast.LENGTH_SHORT).show()
                tvReadWritePermission.text = "읽기/쓰기 가능"
            } else {
                Toast.makeText(this, "읽기/쓰기 불가", Toast.LENGTH_SHORT).show()
                tvReadWritePermission.text = "읽기/쓰기 불가"
            }
        }
    }

    private fun checkPermissions() {
        if (checkSelfPermissionCompat(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
            checkSelfPermissionCompat(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
            tvReadWritePermission.text = "읽기/쓰기 가능"
        } else {
            requestPermissions()
        }
    }

    private fun requestPermissions() {
        requestPermissionsCompat(
            arrayOf(
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            ),
            READWRITE_EXTERNAL_PERMISSION
        )
    }
}
  • 버튼을 클릭하면 checkPermissions 함수로 넘어가 위에서 작성한 확장함수인 checkSelfPermissionCompat을 이용해 읽기/쓰기에 권한이 이미 있는지 확인하고 없다면 requestPermissions 함수로 넘어가 확장함수 requestPermissionsCompat를 이용해 퍼미션을 요청한다.

  • 퍼미션을 수락/거절하게 되면 그 결과가 onRequestPermissionsResult 함수로 콜백되어 표현된다.

테스트

  • API 28에서 테스트

    api28

  • API 31에서 테스트

    api31


  • [참고]

    • https://developer.android.com/training/permissions/requesting?hl=ko
This post is licensed under CC BY 4.0 by the author.

안드로이드 앱 권한

안드로이드 Theme 변경하기

Comments powered by Disqus.