Tình huống: Nó hoạt động trên điện thoại cũ của tôi!
Bạn vừa thêm <uses-permission android:name="android.permission.CAMERA" /> vào AndroidManifest.xml của mình. Bạn kiểm tra ứng dụng trên một máy tính bảng Android 5.1 cũ và nó hoạt động hoàn hảo. Nhưng ngay khi bạn chạy nó trên một thiết bị Pixel hiện đại hoặc một trình giả lập gần đây, ứng dụng sẽ bị treo trước khi ống kính kịp mở.
Nhìn nhanh vào Logcat sẽ thấy thủ phạm:
java.lang.SecurityException: Permission Denial: starting Intent { act=android.media.action.IMAGE_CAPTURE ... } requires android.permission.CAMERA
Nguyên nhân: Tại sao chỉ Manifest là chưa đủ
Trước Android 6.0 (Marshmallow), các quyền được xử lý theo kiểu "chấp nhận hoặc từ bỏ" tại thời điểm cài đặt. Người dùng đồng ý với mọi thứ ngay từ đầu. Tuy nhiên, Google đã thay đổi cuộc chơi với mô hình Quyền thực thi (Runtime Permissions). Giờ đây, đối với các quyền "Nguy hiểm"—như Máy ảnh (Camera), Vị trí (Location) hoặc Danh bạ (Contacts)—việc chỉ khai báo trong Manifest mới chỉ là một nửa chặng đường.
Ứng dụng của bạn hiện phải kiểm tra quyền mỗi khi thực hiện một hành động nhạy cảm. Nếu người dùng không nhấn "Cho phép" một cách rõ ràng, hệ thống sẽ bảo vệ quyền riêng tư của họ bằng cách ném ra một ngoại lệ SecurityException. Vì gần 99% thiết bị Android đang hoạt động hiện nay chạy phiên bản 6.0 trở lên, việc nắm vững quy trình này là bắt buộc đối với bất kỳ nhà phát triển nào.
Cách khắc phục từng bước
Bước 1: Thiết lập nền tảng trong Manifest
Bạn vẫn cần phải khai báo quyền. Điều này cho Cửa hàng Play biết ứng dụng của bạn cần phần cứng nào. Đối với máy ảnh, hãy bao gồm cả thẻ uses-feature để ứng dụng của bạn không hiển thị trên các thiết bị không có máy ảnh.
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<!-- Giúp lọc thiết bị trên Cửa hàng Play -->
<uses-feature android:name="android.hardware.camera" android:required="false" />
</manifest>
Bước 2: Đăng ký Permission Launcher
Hãy quên đi onRequestPermissionsResult lộn xộn và các mã yêu cầu (request codes) của trước đây. Activity Result API là cách hiện đại, an toàn về kiểu dữ liệu (type-safe) để xử lý việc này. Đăng ký launcher này ở đầu Activity hoặc Fragment của bạn:
// Sử dụng Kotlin và Activity Result API
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
// Thành công! Tiếp tục và khởi chạy máy ảnh
openCamera()
} else {
// Người dùng đã từ chối. Xử lý việc này một cách khéo léo.
Toast.makeText(this, "Cần quyền truy cập máy ảnh để chụp ảnh.", Toast.LENGTH_LONG).show()
}
}
Bước 3: Logic kiểm tra và yêu cầu
Khi người dùng nhấp vào nút "Chụp ảnh", hãy chạy kiểm tra này. Nó tuân theo logic chính thức của Android: kiểm tra xem bạn đã có quyền chưa, xem bạn có nên giải thích lý do tại sao bạn cần nó hay không, hoặc chỉ cần yêu cầu nó.
private fun checkCameraPermission() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
openCamera()
}
shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
// Hiển thị một hộp thoại cảnh báo giải thích tại sao cần máy ảnh
showPermissionRationaleDialog()
}
else -> {
// Điều này kích hoạt cửa sổ bật lên của hệ thống
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
}
Bước 4: Khởi chạy máy ảnh một cách an toàn
Khi các quyền đã được đảm bảo, giờ đây bạn có thể kích hoạt intent máy ảnh mà không sợ bị treo ứng dụng. Luôn bọc nó trong một khối try-catch trong trường hợp thiết bị không cài đặt ứng dụng máy ảnh nào.
private fun openCamera() {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
try {
startActivityForResult(intent, REQUEST_IMAGE_CAPTURE)
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, "Không tìm thấy ứng dụng máy ảnh", Toast.LENGTH_SHORT).show()
}
}
Xác minh: Kiểm tra quy trình
- **Xóa sạch:** Gỡ cài đặt ứng dụng khỏi thiết bị để xóa các lựa chọn trước đó.
- **Lần chạy đầu tiên:** Kích hoạt máy ảnh. Đảm bảo hộp thoại hệ thống xuất hiện và ghi "Cho phép [Tên ứng dụng] chụp ảnh?"
- **Kiểm tra từ chối:** Từ chối quyền một lần. Thử lại và đảm bảo phần giải thích "Lý do" (Rationale) của bạn xuất hiện.
- **Phê duyệt cuối cùng:** Cấp quyền và xác minh rằng máy ảnh thực sự mở ra.
Phòng ngừa và các thực hành tốt nhất
- **Kiểm tra mọi lúc:** Người dùng có thể thu hồi quyền trong Cài đặt hệ thống bất kỳ lúc nào. Không bao giờ lưu trạng thái quyền trong một biến; hãy luôn gọi `checkSelfPermission`.
- **Đừng yêu cầu quá mức:** Chỉ yêu cầu máy ảnh khi người dùng thực sự nhấn vào nút liên quan đến máy ảnh. Yêu cầu 5 quyền trong lần khởi chạy đầu tiên là cách nhanh nhất để người dùng gỡ cài đặt ứng dụng của bạn.
- **Bảo mật toàn diện:** Hãy nhớ rằng các quyền không chỉ dành cho thiết bị di động. Nếu ứng dụng của bạn tải những bức ảnh này lên máy chủ, bạn cũng sẽ cần các quyền tệp chính xác ở đó. Đối với các cấu hình backend, các công cụ như [Unix Permissions Calculator](https://toolcraft.app/en/tools/developer/unix-permissions) là vô giá để đảm bảo các thư mục tải lên của bạn an toàn nhưng vẫn hoạt động tốt.
- **Xử lý lỗi khéo léo:** Nếu người dùng từ chối quyền truy cập, đừng làm hỏng ứng dụng. Chỉ cần vô hiệu hóa nút chụp ảnh và cung cấp một liên kết đến Cài đặt ứng dụng.

