GDSC HUFS 3기/Android with Kotlin Team 4

[4팀] 15 1-2. 권한처리 Permission, BaseActivity 설계하기

김준욱 2021. 11. 17. 15:55

이 글은 이것이 안드로이드다 with 코틀린(개정판)를 참고하여 작성하였습니다.

작성자 : 김준욱

개발환경은 Windows, Android Studio입니다.

 

 

1. Permission 이란?

Permission이란 앱의 특정 기능에 부여하는 접근 권한을 말합니다.

나의 앱이 다른 앱이나 시스템에서 보호하는 특정 기능을 이용할 때 permission을 설정해야 하고 마찬가지로

내가 만든 기능을 다른 앱에서 사용할 수 없게 보호하거나 권한을 얻은 앱에서만 허용하고 싶을 때 permission을 설정합니다.

 

권한은 일반권한위험권한으로 나눌 수 있는데 

위험권한은 개인정보와 관련된 정보를 앱이 필요로 할때 사용자로부터 한번 더 허용여부를 물어봅니다.

 

일반권한의 처리는 AndroidManifest.xml 에서 명시해주는 것만으로 사용이 가능합니다.

하지만 위험권한은 명시해주는 것은 물론, 코드에서도 한번 더 확인하는 절차를 거쳐야 합니다.

    <!-- 일반권한 - manifest에 명시 해주는 것만으로 사용 가능-->
    <uses-permission android:name="android.permission.INTERNET"/>
    <!-- 위험권환 - manifest에 명시 + 코드에서도 한번 더 확인 -->
    <uses-permission android:name="android.permission.CAMERA"/>

 

 

2. 위험권한의 처리방법

카메라를 실행하는 버튼을 만들어 카메라 권한에 접근하는 앱을 만들어 보겠습니다.

 

1) 뷰바인딩 설정

build.gradle에 아래 코드를 추가한다.

//build.gradle
buildFeatures{
        viewBinding = true
    }

 

2) AndroidManifest.xml에 명시하기

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

 

3) 카메라 실행 버튼 배치

 

4) MainActivity 작성하기

//MainActivity.kt
package com.example.permission_blog

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.MediaStore
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.permission_blog.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.btnCamera.setOnClickListener {
            checkPermission()
        }
    }

    fun checkPermission() {     //권한 여부를 확인하는 함수
        val cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)  
        //현재 카메라 권한상태 저장

        if(cameraPermission == PackageManager.PERMISSION_GRANTED){
            openCamera()
        } else {
            requestPermission()
        }
    }

    fun openCamera() {
        Toast.makeText(this, "카메라를 실행합니다", Toast.LENGTH_SHORT).show()
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        startActivity(intent)
    }

    fun requestPermission(){    //권한 요청하기
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 99)
    }

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

        when(requestCode) {
            99 -> {
                if(grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    openCamera()
                } else {
                    Toast.makeText(this, "권한이 없으면 앱이 종료됩니다.", Toast.LENGTH_SHORT).show()
                    finish()
                }
            }
        }
    }
}

카메라 버튼을 클릭하면 권한 여부를 확인하는 checkPermission() 호출 ->

현재 카메라 권한 상태를 가지는 cameraPermission이 GRANTED라면 카메라 실행(openCamera), 아니라면 권한을 요청하는 requestPermission()실행 ->

사용자의 권한 설정 결과(While using the app / Only this time / Deny)를 가지고 onRequestPermissionsResult 실행

 

사용자에게 권한 허용 여부를 물어보는 모습
카메라 권한을 허용하니 카메라가 작동함

3. BaseActivity

BaseActivity는 코드를 한결 깔끔하게 관리하도록 도와줍니다.

여기서는 권한 요청과 처리에 대한 코드들을 BaseActivity로 만들어 보도록 하겠습니다. 

 

권한 요청과 처리에 대한 코드를 BaseActivity에 작성하고, MainActivity가 BaseAcitivy를 상속받도록 작성합니다.

물론 기존에 MainActivity가 상속받던 AppCompatActivity는 BaseActivity가 상속받습니다.

 

이 때 상속은 매우 조심히 다루어야 하는데 이를 위해 직접 인스턴스화 되는것을 방지하기 위해 BaseActivity는 abstract class로 생성합니다.

BaseActivity를 상속받은 MainActivity

 

BaseActivity는 기존 Main이 상속받던 AppCompatActivity를 상속받고, 추상클래스(abstract)로 선언함

그 후 위에서 배웠던 권한 요청에 대한 코드를 작성합니다.

이번에는 앱 실행시 Storage 권한에 대한 처리를 하고 승인되어 앱이 실행됐을때 카메라 버튼을 통해 Camera 권한에 대해 처리를 하는 코드를 작성하도록 하겠습니다.

 

//BaseActivity.kt
package com.example.baseactivity

import android.content.pm.PackageManager
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat

abstract class BaseActivity : AppCompatActivity() {

    abstract fun permissionGranted(requestCode: Int)
    abstract fun permissionDenied(requestCode: Int)

    // 권한 검사
    fun requirePermission(permissions: Array<String>, requestCode: Int) {
        // api 버전이 마시멜로 미만이면 권한처리가 필요없다
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            permissionGranted(requestCode)
        } else {
            // 권한이 없으면 권한 요청 -> 팝업
            ActivityCompat.requestPermissions(this, permissions, requestCode)
        }
    }

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

        if (grantResults.all {it == PackageManager.PERMISSION_GRANTED}) {
            //grantResult가 모두 승인되었다면 1, 하나라도 안되면 0
            permissionGranted((requestCode))
        } else {
            permissionDenied(requestCode)
        }

        for (result in grantResults) {
            if (result == PackageManager.PERMISSION_GRANTED) {
            }
        }
    }
}
//MainActivity.kt
package com.example.baseactivity

import android.Manifest
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.MediaStore
import android.provider.MediaStore.ACTION_VIDEO_CAPTURE
import android.widget.Toast
import com.example.baseactivity.databinding.ActivityMainBinding

class MainActivity : BaseActivity() {

    val binding by lazy{ActivityMainBinding.inflate(layoutInflater)}

    val PERM_CAMERA = arrayOf(Manifest.permission.CAMERA)
    val PERM_STORAGE = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)

    val REQ_STORAGE = 99
    val REQ_CAMERA = 100

    val TAKE_CAMERA = 101

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        //앱이 시작되면 스토리지 권한을 처리한다
        requirePermission(PERM_STORAGE, REQ_STORAGE)
        //카메라 버튼이 클릭되면 권한처리 후 카메라 오픈
        binding.btnCamera.setOnClickListener{
            requirePermission(PERM_CAMERA, REQ_CAMERA)
        }

    }

    override fun permissionGranted(requestCode: Int) {
        when(requestCode){
            REQ_STORAGE -> {
                Toast.makeText(this, "Strorage 권한 승인 완료", Toast.LENGTH_SHORT).show()
            }
            REQ_CAMERA -> {
                openCamera()
            }
        }

    }

    override fun permissionDenied(requestCode: Int) {
        when(requestCode){
            REQ_STORAGE -> {
                Toast.makeText(this, "Strorage 권한 승인 거부 앱 종료", Toast.LENGTH_SHORT).show()
                finish()
            }
            REQ_CAMERA -> {
                Toast.makeText(this, "Camera 권한 승인 거부", Toast.LENGTH_SHORT).show()
            }
        }
    }

    fun openCamera() {
        val intent = Intent(ACTION_VIDEO_CAPTURE)
        startActivityForResult(intent, TAKE_CAMERA)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if(resultCode == RESULT_OK){
            when(requestCode) {
                TAKE_CAMERA -> {
                    //카메라 촬영 결과 처리
                }
            }
        }
    }


}