Android 권한 완벽 가이드

발행: (2025년 12월 31일 오후 01:22 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

(번역할 텍스트가 제공되지 않았습니다. 번역이 필요한 본문을 알려주시면 한국어로 번역해 드리겠습니다.)

권한 종류

Protection Level

  • Normal: 설치 시 자동 부여, 사용자 승인 불필요
    예: INTERNET, VIBRATE, ACCESS_NETWORK_STATE

  • Dangerous: 런타임에 사용자 승인 필요
    예: CAMERA, READ_CONTACTS, ACCESS_FINE_LOCATION

  • Signature: 같은 서명의 앱만 사용 가능
    예: BIND_ACCESSIBILITY_SERVICE

런타임 권한

Android 6.0 (API 23)+ 및 targetSdkVersion 23 이상에서 필요합니다.

권한 확인

if (ContextCompat.checkSelfPermission(
        this,
        Manifest.permission.CAMERA
    ) == PackageManager.PERMISSION_GRANTED
) {
    // 권한 있음
    openCamera()
} else {
    // 권한 요청 필요
    requestCameraPermission()
}

권한 요청

private val requestPermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted ->
    if (isGranted) {
        openCamera()
    } else {
        showPermissionDeniedMessage()
    }
}

private fun requestCameraPermission() {
    when {
        shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
            // 권한 필요 이유 설명 후 요청
            showPermissionRationale()
        }
        else -> {
            requestPermissionLauncher.launch(Manifest.permission.CAMERA)
        }
    }
}

여러 권한 동시 요청

private val requestMultiplePermissions = registerForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
    permissions.entries.forEach { (permission, isGranted) ->
        when {
            isGranted -> Log.d(TAG, "$permission granted")
            shouldShowRequestPermissionRationale(permission) -> {
                // 거부됨, 다시 설명 가능
            }
            else -> {
                // 영구 거부됨
            }
        }
    }
}

// 요청
requestMultiplePermissions.launch(
    arrayOf(
        Manifest.permission.CAMERA,
        Manifest.permission.RECORD_AUDIO
    )
)

권한 플로우

권한 확인 -> 없음 -> shouldShowRationale?
                      -> Yes: 설명 보여주기 -> 권한 요청
                      -> No: 권한 요청
                            -> 허용: 기능 사용
                            -> 거부:
                                -> shouldShowRationale = true: 재요청 가능
                                -> shouldShowRationale = false: 영구 거부됨

영구 거부 처리

private fun handlePermanentDenial() {
    AlertDialog.Builder(this)
        .setTitle("권한 필요")
        .setMessage("이 기능을 사용하려면 설정에서 권한을 허용해주세요.")
        .setPositiveButton("설정으로 이동") { _, _ ->
            openAppSettings()
        }
        .setNegativeButton("취소", null)
        .show()
}

private fun openAppSettings() {
    Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
        data = Uri.fromParts("package", packageName, null)
        startActivity(this)
    }
}

권한 그룹

같은 그룹의 권한 중 하나를 허용하면 그룹 내 다른 권한도 자동 허용됩니다.
주의: 권한 그룹은 변경될 수 있으므로 각 권한을 개별적으로 체크하세요.

하드웨어 기능

if (packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
    // 카메라 기능 사용 가능
}

BroadcastReceiver 권한

특정 권한이 있는 앱의 브로드캐스트만 수신:

<receiver android:name=".MyReceiver">
    <intent-filter>
        <action android:name="android.intent.action.SOME_ACTION"/>
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.ANOTHER_ACTION"/>
    </intent-filter>
    <meta-data
        android:name="android.permission.SOME_PERMISSION"
        android:value="true"/>
</receiver>

ADB로 권한 테스트

# 권한 부여
adb shell pm grant com.example.app android.permission.CAMERA

# 권한 해제
adb shell pm revoke com.example.app android.permission.CAMERA

Permission Helper 클래스

class PermissionHelper(private val activity: ComponentActivity) {

    private var callback: ((Boolean) -> Unit)? = null

    private val launcher = activity.registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        val allGranted = permissions.values.all { it }
        callback?.invoke(allGranted)
    }

    fun requestPermissions(
        permissions: Array<String>,
        onResult: (Boolean) -> Unit
    ) {
        callback = onResult

        val notGranted = permissions.filter {
            ContextCompat.checkSelfPermission(activity, it) !=
                PackageManager.PERMISSION_GRANTED
        }

        if (notGranted.isEmpty()) {
            onResult(true)
            return
        }

        launcher.launch(notGranted.toTypedArray())
    }

    fun hasPermission(permission: String): Boolean {
        return ContextCompat.checkSelfPermission(activity, permission) ==
            PackageManager.PERMISSION_GRANTED
    }

    fun shouldShowRationale(permission: String): Boolean {
        return activity.shouldShowRequestPermissionRationale(permission)
    }
}

사용 예시

class MainActivity : AppCompatActivity() {
    private lateinit var permissionHelper: PermissionHelper

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        permissionHelper = PermissionHelper(this)

        // 예시: 카메라와 오디오 권한 요청
        permissionHelper.requestPermissions(
            arrayOf(
                Manifest.permission.CAMERA,
                Manifest.permission.RECORD_AUDIO
            )
        ) { allGranted ->
            if (allGranted) {
                // 모든 권한이 허용됨
                startRecording()
            } else {
                // 권한이 거부됨
                showPermissionDeniedMessage()
            }
        }
    }

    private fun startRecording() {
        // 녹음/촬영 로직
    }

    private fun showPermissionDeniedMessage() {
        Toast.makeText(this, "권한이 필요합니다.", Toast.LENGTH_SHORT).show()
    }
}

코드 예시

missionHelper = PermissionHelper(this)
}

private fun checkCameraPermission() {
    permissionHelper.requestPermissions(
        arrayOf(Manifest.permission.CAMERA)
    ) { granted ->
        if (granted) {
            openCamera()
        } else {
            handlePermissionDenied()
        }
    }
}

SMS 권한 없이 OTP 받기 – SMS Retriever API 사용

val client = SmsRetriever.getClient(this)
client.startSmsRetriever()
    .addOnSuccessListener { /* 리스너 시작됨 */ }
    .addOnFailureListener { /* 실패 */ }

주의사항

  • 권한 그룹 변경 가능: 각 권한을 개별 체크
  • 설정에서 해제 가능: 이전에 허용했어도 다시 체크 필요
  • 권한 거부 시 앱 재시작: onTerminate() 호출되지 않음
  • 권한 허용 후 거부: 영구 거부 기록 초기화됨

결론

런타임 권한은 사용자 프라이버시 보호를 위해 중요합니다. 권한 요청 이유를 명확히 설명하고, 필요할 때만 요청하며, 거부되었을 때의 대체 플로우를 제공하세요.

Back to Blog

관련 글

더 보기 »