Dangerous Permission คือหมวดหมู่ของสิทธิ์ใน Android ที่ต้องได้รับความยินยอมอย่างชัดแจ้งจากผู้ใช้ผ่านไดอะล็อกขณะรันไทม์ระหว่างที่แอปพลิเคชันทำงาน ตาม คู่มือนักพัฒนา Android, 2024 สิทธิ์อันตราย มี ProtectionLevel dangerous และให้การเข้าถึงข้อมูลที่ละเอียดอ่อน: กล้อง ไมโครโฟน ตำแหน่งที่ตั้ง และรายชื่อติดต่อ หากไม่ได้รับความยินยอมอย่างชัดแจ้งจากผู้ใช้ แอปพลิเคชันจะไม่สามารถใช้คุณสมบัติเหล่านี้ได้
ประเด็นสำคัญ
Dangerous Permission คือหมวดหมู่ของสิทธิ์ระบบ Android ที่ให้การเข้าถึงข้อมูลที่ละเอียดอ่อนของผู้ใช้ ซึ่งแตกต่างจากสิทธิ์ทั่วไป สิทธิ์อันตรายจะไม่ถูกให้โดยอัตโนมัติระหว่างการติดตั้ง — แอปพลิเคชันต้องขออย่างชัดแจ้งขณะรันไทม์ผ่านกลไกที่นำมาใช้ใน Android 6.0 Marshmallow (API 23)
ความจำเป็นในการขออย่างชัดแจ้งเกิดจากลักษณะของข้อมูลที่สิทธิ์เหล่านี้ปกป้อง: ตำแหน่งที่ตั้งของผู้ใช้ รายชื่อติดต่อส่วนตัว เนื้อหากล้องและไมโครโฟน ประวัติการโทร และ SMS Android ถือว่าข้อมูลเหล่านี้เป็นข้อมูลที่ละเอียดอ่อนและต้องการให้ผู้ใช้อนุญาตการเข้าถึงอย่างมีสติ ตาม Android Privacy Sandbox (2024) โดยเฉลี่ยแล้วผู้ใช้ปฏิเสธประมาณ 30 เปอร์เซ็นต์ของคำขอขณะรันไทม์
คุณสมบัติที่สำคัญของ Dangerous Permission คือความสามารถในการเพิกถอนได้ทุกเมื่อ ผู้ใช้สามารถไปที่ การตั้งค่า — แอป — สิทธิ์ และปิดสิทธิ์อันตรายใด ๆ แอปพลิเคชันต้องเตรียมพร้อมสำหรับการที่สิทธิ์ที่เคยให้ไว้อาจถูกเพิกถอนได้ทุกเมื่อโดยไม่ต้องรีสตาร์ท
ระดับการป้องกัน dangerous ถูกกำหนดในนิยามสิทธิ์ของระบบในระดับ OS เมื่อแอปพลิเคชันประกาศ uses-permission ด้วย protectionLevel นี้ ระบบจะทำเครื่องหมายสิทธิ์ว่าต้องมีการขอขณะรันไทม์ ซึ่งแตกต่างจาก normal สิทธิ์อันตรายจะแสดงใน UI การจัดการสิทธิ์ของระบบเสมอและสามารถเพิกถอนได้
สิทธิ์อันตรายทั้งหมดถูกจัดกลุ่มเป็น Permission Groups ตามหมวดหมู่การทำงาน ตัวอย่างเช่น CAMERA และ CAMERA2 อยู่ในกลุ่ม CAMERA, ACCESS_FINE_LOCATION และ ACCESS_COARSE_LOCATION อยู่ในกลุ่ม LOCATION หากผู้ใช้ให้สิทธิ์หนึ่งรายการจากกลุ่ม สิทธิ์ที่เหลือในกลุ่มเดียวกันจะถูกให้โดยอัตโนมัติโดยไม่ต้องมีไดอะล็อกเพิ่มเติม
คำขอขณะรันไทม์ เป็นกลไกที่แอปพลิเคชันเรียก API ของระบบเพื่อแสดงไดอะล็อกขอสิทธิ์ ผู้ใช้จะเห็นหน้าต่างโมดัลพร้อมชื่อสิทธิ์และปุ่ม Allow และ Deny หลังจากตอบกลับ ระบบจะเรียก callback onRequestPermissionsResult พร้อมผลลัพธ์
วงจรสมบูรณ์ประกอบด้วยสามขั้นตอน: การตรวจสอบสถานะผ่าน checkSelfPermission, การเรียก requestPermissions หากยังไม่ได้รับสิทธิ์, และการจัดการผลลัพธ์ใน onRequestPermissionsResult การตรวจสอบสถานะเป็นสิ่งจำเป็นเพราะผู้ใช้อาจเพิกถอนสิทธิ์ได้ทุกเมื่อผ่านการตั้งค่า และการเรียกฟังก์ชันโดยไม่ตรวจสอบจะนำไปสู่ SecurityException
fun checkAndRequestCameraPermission() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
openCamera()
}
else -> {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA),
REQUEST_CAMERA_CODE
)
}
}
}
การจัดการผลลัพธ์เกิดขึ้นใน ActivityResultLauncher หรือ onRequestPermissionsResult วิธีที่ทันสมัยที่แนะนำคือการใช้ ActivityResultContracts.RequestPermission ซึ่งให้ API ที่สะอาดกว่าโดยไม่ต้องใช้รหัสคำขอที่ชัดเจน สัญญานี้คืนค่า Boolean — ว่าสิทธิ์ได้รับหรือไม่
ขอสิทธิ์อันตรายอย่างเคร่งครัดในบริบทของการใช้คุณสมบัติ ไม่ใช่เมื่อเริ่มต้นแอปพลิเคชัน หากผู้ใช้กดปุ่มกล้อง — ขอ CAMERA หากเปิดแผนที่ — ขอ LOCATION คำขอตามบริบท ได้รับการอนุญาตมากเป็นสองเท่าเมื่อเทียบกับการขอสิทธิ์ทั้งหมดในการเปิดครั้งแรก นอกจากนี้ยังแนะนำให้ขอไม่เกินหนึ่งสิทธิ์ในแต่ละครั้งเพื่อให้ผู้ใช้เข้าใจว่าคุณสมบัติใดต้องการการเข้าถึง
Android กำหนด กลุ่มสิทธิ์อันตรายหลายกลุ่ม แต่ละกลุ่มมีค่าคงที่หนึ่งค่าหรือมากกว่า รายการที่สมบูรณ์ที่สุดมีอยู่ในคลาส Manifest.permission ด้านล่างคือกลุ่มหลักและสิทธิ์ที่ใช้ในการพัฒนา
| กลุ่มสิทธิ์ | สิทธิ์ | การเข้าถึง API |
|---|---|---|
| CAMERA | CAMERA | Camera API, CameraX |
| LOCATION | ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION | FusedLocationProvider, Geofence |
| MICROPHONE | RECORD_AUDIO | MediaRecorder, AudioRecord |
| PHONE | READ_PHONE_STATE, CALL_PHONE, READ_CALL_LOG | TelephonyManager |
| CONTACTS | READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS | ContactsContract |
| SMS | READ_SMS, SEND_SMS, RECEIVE_SMS | SmsManager |
| STORAGE | READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE | MediaStore, File API |
| CALENDAR | READ_CALENDAR, WRITE_CALENDAR | CalendarContract |
เริ่มตั้งแต่ Android 12 Google ได้เพิ่มความเข้มงวดของข้อกำหนดสำหรับสิทธิ์บางอย่าง ตัวอย่างเช่น BLUETOOTH_CONNECT และ BLUETOOTH_SCAN กลายเป็นสิทธิ์อันตรายและต้องมีการขอขณะรันไทม์ สิทธิ์ BODY_SENSORS_BACKGROUND ถูกเพิ่มสำหรับการเข้าถึงเซ็นเซอร์ในเบื้องหลัง นักพัฒนาต้องอัปเดต targetSdkVersion และทดสอบคำขอในเวอร์ชัน OS ปัจจุบัน
Android 13 (API 33) นำเสนอสิทธิ์ใหม่สำหรับการแจ้งเตือน (POST_NOTIFICATIONS) และไฟล์มีเดีย (READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO) แทนที่ READ_EXTERNAL_STORAGE ทั่วไป ตอนนี้การเข้าถึงรูปภาพ วิดีโอ และเสียงถูกขอแยกต่างหากผ่านสิทธิ์เฉพาะทางโดยไม่มีไดอะล็อกเดียว
สิทธิ์ Dangerous และ Normal แตกต่างกันโดยพื้นฐานในวิธีการให้ ความสามารถในการเพิกถอน และ UX Normal ถูกให้โดยอัตโนมัติระหว่างการติดตั้ง Dangerous ต้องใช้ไดอะล็อกขณะรันไทม์อย่างชัดแจ้ง Normal ไม่สามารถเพิกถอนผ่านการตั้งค่าได้ Dangerous สามารถปิดใช้งานได้ทุกเมื่อ ความไม่สมมาตรนี้สร้างรูปแบบการพัฒนาที่แตกต่างกัน
จากมุมมองของโค้ด สิทธิ์อันตรายต้องการงานมากกว่า: checkSelfPermission, requestPermissions, การจัดการการปฏิเสธ สำหรับสิทธิ์ทั่วไป บรรทัดเดียวใน AndroidManifest.xml ก็เพียงพอ อย่างไรก็ตาม Dangerous Permission ให้การควบคุมแก่ผู้ใช้ ซึ่งเพิ่มความไว้วางใจ โดยเฉพาะอย่างยิ่งสำหรับคุณสมบัติที่ละเอียดอ่อนอย่างกล้องหรือตำแหน่งที่ตั้ง
การเลือกระหว่างหมวดหมู่ไม่ได้ขึ้นอยู่กับนักพัฒนา — ระบบจะเป็นผู้กำหนด นักพัฒนาเพียงประกาศ uses-permission และระบบกำหนดหมวดหมู่ตาม protectionLevel อย่างไรก็ตาม กลยุทธ์การขอสิทธิ์อันตรายส่งผลต่อประสบการณ์ผู้ใช้: ไดอะล็อกที่บ่อยหรือไม่เหมาะสมจะลดคะแนนของแอปพลิเคชัน
วิธีที่ทันสมัย ในการขอสิทธิ์ใน Kotlin คือการใช้ ActivityResultContracts.RequestMultiplePermissions หรือ RequestPermission สัญญาเหล่านี้เป็นส่วนหนึ่งของไลบรารี androidx.activity และให้ API ที่สะอาดบนพื้นฐาน lambda โดยไม่ต้อง override onRequestPermissionsResult
class CameraActivity : AppCompatActivity() {
private val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
openCamera()
} else {
showPermissionDeniedMessage()
}
}
fun requestCamera() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED ->
openCamera()
ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.CAMERA
) ->
showRationaleDialog()
else ->
requestPermissionLauncher.launch(
Manifest.permission.CAMERA
)
}
}
}
เมื่อแอปพลิเคชันต้องการสิทธิ์อันตรายหลายรายการพร้อมกัน ให้ใช้ RequestMultiplePermissions สัญญาจะคืนค่า Map<String, Boolean> โดยที่คีย์คือชื่อสิทธิ์และค่าคือผลลัพธ์ ซึ่งมีประโยชน์ในการเปิดครั้งแรกเมื่อคุณต้องขอ CAMERA และ RECORD_AUDIO สำหรับการบันทึกวิดีโอ
หากผู้ใช้ปฏิเสธคำขอ เมธอด shouldShowRequestPermissionRationale จะคืนค่า true ซึ่งเป็นสัญญาณให้แสดงคำอธิบายว่าทำไมจึงจำเป็นต้องใช้สิทธิ์นั้น แนวทางปฏิบัติที่ดีที่สุดคือแสดงไดอะล็อกที่กำหนดเองพร้อมคำอธิบายและปุ่มลองอีกครั้ง หากผู้ใช้ปฏิเสธคำขออีกครั้งโดยทำเครื่องหมายที่ช่อง Never Ask Again shouldShowRequestPermissionRationale จะคืนค่า false และคุณต้องนำทางไปยังการตั้งค่า
Never Ask Again คือแฟล็กที่ผู้ใช้สามารถตั้งค่าเมื่อปฏิเสธไดอะล็อกขณะรันไทม์เป็นครั้งที่สอง หลังจากนั้น ไดอะล็อกมาตรฐานจะไม่แสดงอีกสำหรับสิทธิ์นั้น วิธีเดียวที่จะให้การเข้าถึงคือนำทางผู้ใช้ไปยังการตั้งค่าแอปพลิเคชันของระบบ
นักพัฒนาต้องแยกแยะระหว่างสองสถานการณ์การปฏิเสธ: กรณีแรก เมื่อ shouldShowRequestPermissionRationale คืนค่า true (ผู้ใช้ปฏิเสธแต่ยังสามารถแสดงไดอะล็อกได้) และกรณีที่สอง เมื่อเมธอดคืนค่า false (Never Ask Again ทำงานอยู่หรือสิทธิ์ถูกบล็อกโดยนโยบาย) ในกรณีที่สอง คุณควรแสดงปุ่มเปิดการตั้งค่า
fun handlePermissionDenied(permission: String) {
if (ActivityCompat.shouldShowRequestPermissionRationale(
this, permission
)) {
showRationaleDialog(permission)
} else {
showSettingsRedirectDialog(permission)
}
}
private fun showSettingsRedirectDialog(permission: String) {
AlertDialog.Builder(this)
.setTitle("ปฏิเสธการเข้าถึง")
.setMessage(
"สิทธิ์ถูกบล็อก เปิดการตั้งค่า"
)
.setPositiveButton("การตั้งค่า") { _, _ ->
val intent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts(
"package", packageName, null
)
)
startActivity(intent)
}
.show()
}
สิ่งสำคัญคือไม่ต้องขอสิทธิ์อีกครั้งหาก shouldShowRequestPermissionRationale คืนค่า false การเรียก requestPermissions ซ้ำในกรณีนี้จะไม่แสดงไดอะล็อก — ผลลัพธ์จะกลับมาทันทีด้วย DENIED โดยไม่มีคำอธิบาย ผู้ใช้จะพบกับพฤติกรรมที่ไม่ชัดเจน ซึ่งส่งผลเสียต่อประสบการณ์การใช้แอปพลิเคชัน
คำถามที่พบบ่อย
สิทธิ์อันตราย รวมถึงสิทธิ์ที่มี ProtectionLevel dangerous: CAMERA, RECORD_AUDIO, ACCESS_FINE_LOCATION, READ_CONTACTS, READ_SMS, READ_CALENDAR และอื่น ๆ รายการทั้งหมดมีอยู่ในคลาส Manifest.permission
ใช้ ContextCompat.checkSelfPermission โดยส่ง context และชื่อสิทธิ์ เมธอดคืนค่า PERMISSION_GRANTED หรือ PERMISSION_DENIED ควรทำการตรวจสอบก่อนทุกการเรียก API ที่ต้องการสิทธิ์อันตราย
Permission Group จัดกลุ่มสิทธิ์อันตรายที่เกี่ยวข้องกัน หากผู้ใช้ให้สิทธิ์หนึ่งรายการจากกลุ่ม สิทธิ์ที่เหลือจะถูกให้โดยอัตโนมัติ ตัวอย่างเช่น LOCATION ประกอบด้วย ACCESS_FINE_LOCATION และ ACCESS_COARSE_LOCATION
ตรวจสอบ shouldShowRequestPermissionRationale หลังจากการปฏิเสธ หากเมธอดคืนค่า false และสิทธิ์ยังไม่ได้รับ — Never Ask Again กำลังทำงานอยู่ นำทางผู้ใช้ไปยังการตั้งค่าผ่าน Intent ด้วย ACTION_APPLICATION_DETAILS_SETTINGS
ใช่ ยังคงจำเป็นต้องใช้ บน Android 13+ สิทธิ์บางอย่างเปลี่ยนแปลงไป: POST_NOTIFICATIONS กลายเป็นสิทธิ์ขณะรันไทม์แยกต่างหาก และ READ_EXTERNAL_STORAGE ถูกแทนที่ด้วย READ_MEDIA_IMAGES สำหรับการเข้าถึงไฟล์มีเดียแบบละเอียด
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม