안드로이드 권한 요청 이거 하나로 끝낸다

2021. 11. 4. 21:12개발을 파헤치다/Android

반응형

안드로이드 6.0(마시멜로) 이후로 앱 실행 중에 권한을 요청하도록 바뀌었습니다. 개인정보 보호 관점에서 사용자에게는 더 나은 선택이겠죠. 하지만 앱 개발자에게는 처리해줘야 할 일이 더 늘게 되었습니다. 여기에 방송통신 위원회에서 얘기하는 스마트폰 앱 접근 권한 명시에 관한 문제까지... 권한이 개인정보와 밀접한 관련이 있다 보니 처리하기 좀 까다로운 게 사실입니다. 이번 포스팅에서는 이 모든 걸 한방에 정리해보도록 하겠습니다. 앱 출시 앞두고 이런 부분을 놓쳐서 출시 거부당하거나 나중에 과태료 물게 되면 아찔하겠죠? 출시를 앞두고 있다면 꼭 끝까지 읽어보시기 바랍니다. 

 

앱 접근권한 명시

이제 스마트폰 어플리케이션에서 스마트폰 앱 접근권한에 대한 명시를 해주어야 합니다.
방송통신 위원회의 지침인데요. 여기에서는 권한을 필수적 접근 권한과 선택적 접근 권한으로 나누어 설명하고 있습니다. 이것이 안드로이드에서 얘기하는 권한 체계와 달라 조금 모호한 부분이 있습니다.

안드로이드 권한체계

 



안드로이드에서는 일반 권한과 위험 권한으로 나누어져 있습니다.
위의 내용들이 위험 권한에 해당되는데요. 개인정보와 연관된 권한들이 대부분 위험 권한으로 분류가 되어 있습니다. 이런 위험 권한들에 대해서는 Android 6.0 이후부터 관련된 기능을 사용 시(Runtime) 권한 요청을 하도록 앱을 만들어야 합니다.

방통위 앱 접근권한

스마트폰 앱 접근권한이란 앱 서비스 제공자가 앱을 통해 이용자의 스마트폰 내에 저장되어있는 정보 및 설치된 기능에 접근하여 해당 정보를 읽고 수정하거나 해당 기능을 실행할 수 있는 권한을 의미합니다.

앱 서비스를 제공하기 위해 반드시 앱에서 필요로 하는 권한이 필수인지 선택인지 나누어 사용자에게 명시를 해주어야 합니다.



멜론 서비스의 경우 위와 같이 필수 권한과 선택 권한을 나누어 명시하고 있습니다.
일반적으로 앱 실행과 동시에 팝업이나 Activity로 따로 구현하여 앱 접근권한에 대한 안내를 고지하도록 구현합니다. 이를 위반하면 정보통신망법을 위반하게 되어 여러 가지 불이익을 당할 수 있기 때문에 잊지 말고 꼭 해줘야 합니다.

권한 체크 로직

사용자에게 권한을 요청하면 다음과 같은 경우의 수를 생각해볼 수 있습니다.

  • 권한을 모두 수락하는 경우
  • 권한을 거부하는 경우

위의 경우에 대해서 개발자가 로직을 구현해서 처리를 해주어야 하는데요.
권한 요청 시 거부했는지 여부를 판단할 수 있는 메서드를 사용해서 사용자에게 권한을 꼭 수락해야 하는 이유를 말해주도록 구현하면 더 좋겠죠.
문제는 다시 묻기에 체크를 하고 거부를 하는 경우인데요. 이 경우 권한 요청 메서드를 사용해도 Android에서 권한 알림 창을 띄워주지 않기 때문에 개발자가 직접 로직을 구현해주어야 합니다. 이를테면 알림 창을 보여주고 적절하게 설명한 뒤 직접 앱의 설정 화면으로 이동해서 권한을 수락하도록 할 수 있겠죠.

권한 체크 구현

 val recordBtn : ImageView = mBinding!!.recordButton
        recordBtn.setOnClickListener(View.OnClickListener {
            // 권한을 체크한다
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){

                if(this.checkSelfPermission(android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED ||
                        this.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
                        this.checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){

                    val permissions = arrayOf(android.Manifest.permission.RECORD_AUDIO,
                            android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE)
                    requestPermissions(permissions, 0)

                }
                // 권한이 충족된 경우 다이얼로그를 보여준다
                else{
                    showRecordDialog()
                }
            }
            // Android 6.0 이하
            else{
                showRecordDialog()
            }
        })

먼저 권한을 요청하는 로직입니다.
버튼에 클릭 이벤트를 붙여서 권한을 체크하도록 구현이 되어 있습니다.

Android 6.0(마시멜로)부터 권한을 Runtime에 체크하도록 되어있기 때문에 이전 버전의 경우는 그냥 권한 처리를 따로 하지 않고 로직을 구현하면 됩니다.

checkSelfPermission을 실행하면 해당 권한을 유저가 허용했는지 아닌지 결과값이 리턴됩니다.
이 값을 바탕으로 다시 권한 요청을 하거나 이후 로직을 실행하도록 구현하면 됩니다.

 

 @RequiresApi(Build.VERSION_CODES.M)
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        when(requestCode){
            0 -> {
                // 권한이 비어있는 경우 에러
                if(grantResults.isEmpty()){
                    throw RuntimeException("Empty Permission Result")
                }
                // 거부된 권한이 있는지 확인한다
                var isPermitted= true
                val deniedPermission = ArrayList<String>()
                for((id, result) in grantResults.withIndex()){
                    if(result == PackageManager.PERMISSION_DENIED){
                        isPermitted = false
                        deniedPermission.add(permissions[id])
                    }
                }
                // 권한이 모두 충족된 경우 다이얼로그를 보여준다
                if(isPermitted){
                    showRecordDialog()
                }
                else{
                    // 거부만 선택한 경우
                    if(ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO) ||
                        ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ||
                        ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)){

                        // 권한이 필요하다는 토스트 메시지를 띄운다
                        Toast.makeText(this, AppUtils.getString(R.string.permission_record_notice), Toast.LENGTH_LONG).show()
                        // 권한을 다시 요청한다
                        requestPermissions(deniedPermission.toArray(arrayOfNulls<String>(deniedPermission.size)), 0)
                    }
                    // 거부 및 다시보지 않기를 선택한 경우
                    else{
                        // 권한 설정으로 이동할 수 있도록 알림창을 띄운다
                        super.showDialogToGetPermission(this)
                    }
                }
            }
        }
    }

onRequestPermissionResult는 유저에게 권한을 요청하는 알림 창을 띄워준 뒤 그 결과값이 리턴되면서 실행되는 메서드입니다. 
위에서는 요청 권한이 여러 개이기 때문에 결과값 리스트를 돌면서 거부된 권한이 있는지 확인합니다.
모두 허용되었으면 이후 로직을 실행하면 됩니다. 하지만 거부가 있을 경우 조금씩 문제가 복잡해지는데요.
앞에서 설명한 다양한 케이스에 대해 개발자가 직접 구현을 해주어야 합니다.

이때 중요한 역할을 하는 메서드가 shouldShowRequestPermissionRationale입니다.
이 메서드가 리턴하는 값을 정리하면 아래와 같습니다.

  • 최초 권한 요청 실행 시: False 리턴
  • 유저가 거부한 권한이 있을 경우: True 리턴
  • 유저가 거부하고 다시 묻지 않기까지 선택한 경우: False 리턴

위 메서드가 onRequestPermissionResult에 있는 이유는 최초 실행 시 유저가 거부를 하게 되면 그때 true값으로 바뀌기 때문입니다. 따라서 onRequestPermissionResult에서 shuldSHowRequestPermissionRationale의 리턴 값이 가지는 의미가 두 가지가 됩니다.

  • True일 경우: 사용자가 거부한 권한이 존재
  • False일 경우: 사용자가 권한 허용을 거부하고 다시 묻지 않음까지 선택

특히 False일 경우 권한 요청을 해도 더 이상 안드로이드에서 권한 허용 알림 창을 띄워주지 않기 때문에 사용자가 직접 설정 화면으로 이동해서 권한을 허용해주어야 합니다. 이 부분에 대한 편의도 개발자가 직접 구현을 해줘야 하는 것이죠.

 

// 직접 권한 설정을 하기 위한 알림창
    fun showDialogToGetPermission(context: Context){
        val builder = AlertDialog.Builder(context)
        builder.setTitle("권한설정")
            .setMessage("허세영어의 모든 기능을 사용하기 위해 녹음 및 외부 스토리지 접근 권한이 필요합니다." +
                    "확인을 눌러 권한 설정창으로 이동한 뒤 설정을 완료해주세요")
            .setPositiveButton(AppUtils.getString(R.string.dialog_ok)){ dialog, i->
                val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                Uri.fromParts("package", context.packageName, null))
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                startActivity(intent)
            }
        builder.setNegativeButton(AppUtils.getString(R.string.dialog_no)){ dialog, i ->


        }
        val dialog = builder.create()
        dialog.show()
    }

 

위의 메서드는 사용자가 권한을 거부하고 다시 묻지 않음까지 선택했을 때 알림 창을 보여주고 직접 설정 화면으로 이동할 수 있도록 합니다. 이렇게 하면 좀 더 편하게 권한을 허용할 수 있겠죠.

 

여기까지 출시 이전에 꼭 확인해야 할 안드로이드 앱 접근 권한에 대해서 살펴보았습니다.

궁금한 점은 언제든 댓글로 물어봐주세요! 포스팅이 도움이 되셨다면 공감도 잊지 말고 부탁드립니다 :)

 

개발자 취업을 준비중이시거나 이직을 고민하시는 분들을 위해 지난 6년간 저의 노하우를 담은 무료 전자책을 드립니다.

아래 배너 눌러서 개발자 H 카카오 채널 친구추가하시면 바로 보내드리니 놓치지 말고 꼭 받아가세요 :)

 

반응형