Permissions trong Android
1. Giới thiệu
Permissions bảo vệ quyền riêng tư của user bằng cách yêu cầu app xin phép trước khi truy cập sensitive data hoặc hardware.
2. Loại Permissions
Normal Permissions
Tự động được cấp, chỉ cần khai báo trong Manifest:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />Dangerous Permissions
Cần runtime request:
- CAMERA
- LOCATION
- MICROPHONE
- CONTACTS
- CALENDAR
- STORAGE
- PHONE
3. Khai báo trong Manifest
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Storage permissions -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />4. Request Permission - Activity
class MainActivity : ComponentActivity() {
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
openCamera()
} else {
showPermissionDeniedMessage()
}
}
private fun checkCameraPermission() {
when {
checkSelfPermission(Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED -> {
openCamera()
}
shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
showRationaleDialog()
}
else -> {
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
}
}5. Multiple Permissions
private val requestMultiplePermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val allGranted = permissions.entries.all { it.value }
if (allGranted) {
startLocationTracking()
} else {
// Check which permissions were denied
permissions.forEach { (permission, granted) ->
if (!granted) {
handleDenied(permission)
}
}
}
}
private fun requestLocationPermissions() {
requestMultiplePermissions.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
)
}6. Compose với Accompanist
// build.gradle.kts
implementation("com.google.accompanist:accompanist-permissions:0.34.0")Single Permission
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun CameraScreen() {
val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA)
when {
cameraPermissionState.status.isGranted -> {
CameraPreview()
}
cameraPermissionState.status.shouldShowRationale -> {
Column {
Text("Camera permission is required for this feature")
Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
Text("Grant Permission")
}
}
}
else -> {
Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
Text("Request Camera Permission")
}
}
}
}Multiple Permissions
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun LocationScreen() {
val locationPermissions = rememberMultiplePermissionsState(
listOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
)
when {
locationPermissions.allPermissionsGranted -> {
LocationContent()
}
locationPermissions.shouldShowRationale -> {
RationaleDialog(onConfirm = { locationPermissions.launchMultiplePermissionRequest() })
}
else -> {
Button(onClick = { locationPermissions.launchMultiplePermissionRequest() }) {
Text("Request Location Permissions")
}
}
}
}7. Xử lý “Don’t ask again”
if (!shouldShowRequestPermissionRationale(permission)
&& checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
// User selected "Don't ask again"
showSettingsDialog()
}
private fun showSettingsDialog() {
AlertDialog.Builder(this)
.setTitle("Permission Required")
.setMessage("Please enable permission in Settings")
.setPositiveButton("Settings") { _, _ ->
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.fromParts("package", packageName, null)
startActivity(intent)
}
.setNegativeButton("Cancel", null)
.show()
}8. Permission Groups (Android 14+)
// Photo picker - không cần permission
val pickMedia = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
uri?.let { handleSelectedImage(it) }
}
pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))9. Best Practices
// ✅ Good: Request khi cần
fun onCameraButtonClick() {
requestCameraPermission()
}
// ❌ Bad: Request khi app start
override fun onCreate() {
requestAllPermissions() // Don't do this
}
// ✅ Good: Explain why
@Composable
fun PermissionRationale() {
Column {
Text("We need camera access to take photos for your profile")
Button(onClick = { requestPermission() }) {
Text("Continue")
}
}
}
// ✅ Good: Graceful degradation
if (hasLocationPermission) {
showNearbyPlaces()
} else {
showAllPlaces() // Fallback without location
}10. Special Permissions
// Overlay permission
if (!Settings.canDrawOverlays(this)) {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
startActivity(intent)
}
// Notification permission (Android 13+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}📝 Tóm tắt
| Type | Request |
|---|---|
| Normal | Manifest only |
| Dangerous | Runtime request |
| Special | Intent to Settings |
| Best Practice | Description |
|---|---|
| Request in context | Khi user cần feature |
| Explain why | Show rationale |
| Handle denial | Graceful degradation |
| Don’t ask again | Link to Settings |
Last updated on