แอป Android ทุกแอปทำงานในแซนด์บ็อกซ์ที่มีการจำกัดการเข้าถึง หากแอปต้องใช้แหล่งข้อมูลหรือข้อมูลนอกแซนด์บ็อกซ์ของตัวเอง คุณสามารถประกาศสิทธิ์รันไทม์และตั้งค่าคำขอสิทธิ์ที่ให้สิทธิ์เข้าถึงนี้ได้ ขั้นตอนเหล่านี้เป็นส่วนหนึ่งของเวิร์กโฟลว์การใช้สิทธิ์
หากคุณประกาศสิทธิ์ที่เป็นอันตราย และหากติดตั้งแอปในอุปกรณ์ที่ใช้ Android 6.0 (API ระดับ 23) ขึ้นไป คุณต้องขอสิทธิ์ที่เป็นอันตรายขณะรันไทม์โดยทำตามขั้นตอนในคู่มือนี้
หากคุณไม่ได้ประกาศสิทธิ์ที่เป็นอันตราย หรือหากติดตั้งแอปในอุปกรณ์ที่ใช้ Android 5.1 (API ระดับ 22) หรือต่ำกว่า ระบบจะให้สิทธิ์โดยอัตโนมัติ และคุณไม่จําเป็นต้องทำตามขั้นตอนที่เหลือในหน้านี้
หลักการพื้นฐาน
หลักการพื้นฐานในการขอสิทธิ์ระหว่างรันไทม์มีดังนี้
- ขอสิทธิ์ในบริบทเมื่อผู้ใช้เริ่มโต้ตอบกับฟีเจอร์ที่ต้องใช้สิทธิ์
- ไม่ต้องบล็อกผู้ใช้ ให้ตัวเลือกในการยกเลิกโฟลว์ UI เพื่อการศึกษาเสมอ เช่น โฟลว์ที่อธิบายเหตุผลในการขอสิทธิ์
- หากผู้ใช้ปฏิเสธหรือเพิกถอนสิทธิ์ที่ฟีเจอร์หนึ่งต้องใช้ ให้ลดแอปลงอย่างค่อยเป็นค่อยไปเพื่อให้ผู้ใช้ใช้แอปของคุณต่อไปได้ ซึ่งอาจเป็นด้วยการปิดใช้ฟีเจอร์ที่ต้องใช้สิทธิ์
- อย่าคาดเดาการทำงานของระบบ ตัวอย่างเช่น อย่าคิดว่าสิทธิ์จะปรากฏในกลุ่มสิทธิ์เดียวกัน กลุ่มสิทธิ์เพียงช่วยให้ระบบลดจำนวนกล่องโต้ตอบของระบบที่แสดงต่อผู้ใช้เมื่อแอปขอสิทธิ์ที่เกี่ยวข้องกันมาก
เวิร์กโฟลว์สำหรับการขอสิทธิ์
ก่อนที่จะประกาศและขอสิทธิ์รันไทม์ในแอป ให้ประเมินว่าแอปจำเป็นต้องขอสิทธิ์ดังกล่าวหรือไม่ คุณสามารถทำตาม Use Case ต่างๆ ในแอปได้ เช่น การถ่ายภาพ หยุดเล่นสื่อชั่วคราว และแสดงโฆษณาที่เกี่ยวข้อง โดยไม่ต้องประกาศสิทธิ์ใดๆ
หากคุณสรุปได้ว่าแอปของคุณต้องประกาศและขอสิทธิ์รันไทม์ ให้ทำตามขั้นตอนต่อไปนี้
- ในไฟล์ Manifest ของแอป ให้ประกาศสิทธิ์ที่แอปอาจต้องขอ
- ออกแบบ UX ของแอปเพื่อให้การดำเนินการบางอย่างในแอปเชื่อมโยงกับสิทธิ์รันไทม์ที่เฉพาะเจาะจง แจ้งให้ผู้ใช้ทราบว่าการดำเนินการใดบ้างที่อาจกำหนดให้ผู้ใช้ต้องให้สิทธิ์แก่แอปในการเข้าถึงข้อมูลส่วนตัว
- รอให้ผู้ใช้เรียกใช้งานหรือการดำเนินการในแอปที่จำเป็นต้องใช้สิทธิ์เข้าถึงข้อมูลส่วนตัวเฉพาะของผู้ใช้ ในเวลาดังกล่าว แอปจะขอสิทธิ์รันไทม์ที่จําเป็นสําหรับการเข้าถึงข้อมูลนั้นได้
ตรวจสอบว่าผู้ใช้ได้ให้สิทธิ์รันไทม์ที่แอปของคุณต้องใช้แล้วหรือยัง หากใช่ แอปของคุณจะเข้าถึงข้อมูลส่วนตัวของผู้ใช้ได้ หากไม่ ให้ไปยังขั้นตอนถัดไป
คุณต้องตรวจสอบว่าคุณมีสิทธิ์ทุกครั้งที่ดำเนินการซึ่งต้องใช้สิทธิ์นั้น
ตรวจสอบว่าแอปควรแสดงเหตุผลต่อผู้ใช้หรือไม่ โดยอธิบายเหตุผลที่แอปต้องให้ผู้ใช้มอบสิทธิ์รันไทม์หนึ่งๆ โดยเฉพาะ หากระบบพิจารณาว่าแอปของคุณไม่ควรแสดงเหตุผล ให้ไปยังขั้นตอนถัดไปโดยตรงโดยไม่ต้องแสดงองค์ประกอบ UI
อย่างไรก็ตาม หากระบบพิจารณาว่าแอปของคุณควรแสดงเหตุผล ให้แสดงเหตุผลต่อผู้ใช้ในองค์ประกอบ UI ในเหตุผลนี้ ให้อธิบายอย่างชัดเจนว่าแอปพยายามเข้าถึงข้อมูลใดและแอปจะให้ประโยชน์ใดแก่ผู้ใช้หากผู้ใช้ให้สิทธิ์รันไทม์ หลังจากผู้ใช้รับทราบเหตุผลแล้ว ให้ไปยังขั้นตอนถัดไป
ขอสิทธิ์รันไทม์ที่แอปของคุณต้องใช้เพื่อเข้าถึงข้อมูลส่วนตัวของผู้ใช้ ระบบจะแสดงพรอมต์สิทธิ์รันไทม์ เช่น พรอมต์ที่แสดงในหน้าภาพรวมสิทธิ์
ตรวจสอบคําตอบของผู้ใช้ว่าเลือกให้หรือปฏิเสธสิทธิ์รันไทม์
หากผู้ใช้ให้สิทธิ์แอปของคุณ คุณก็จะเข้าถึงข้อมูลส่วนตัวของผู้ใช้ได้ หากผู้ใช้ปฏิเสธสิทธิ์ ให้ลดประสบการณ์การใช้งานแอปอย่างค่อยเป็นค่อยไปเพื่อให้แอปทำงานได้โดยไม่ต้องใช้ข้อมูลที่ได้รับความคุ้มครองจากสิทธิ์นั้น
รูปที่ 1 แสดงเวิร์กโฟลว์และชุดการตัดสินใจที่เกี่ยวข้องกับกระบวนการนี้
ตรวจสอบว่าแอปได้รับสิทธิ์แล้วหรือยัง
หากต้องการตรวจสอบว่าผู้ใช้ให้สิทธิ์หนึ่งๆ แก่แอปของคุณแล้วหรือไม่ ให้ส่งสิทธิ์ดังกล่าวไปยังเมธอด ContextCompat.checkSelfPermission()
เมธอดนี้จะแสดงผลเป็น PERMISSION_GRANTED
หรือ PERMISSION_DENIED
โดยขึ้นอยู่กับว่าแอปของคุณมีสิทธิ์หรือไม่
อธิบายเหตุผลที่แอปของคุณต้องใช้สิทธิ์
กล่องโต้ตอบสิทธิ์ที่ระบบแสดงเมื่อคุณเรียกใช้ requestPermissions()
จะบอกสิทธิ์ที่แอปต้องการ แต่ไม่บอกเหตุผล ในบางกรณี ผู้ใช้อาจสับสน คุณควรอธิบายให้ผู้ใช้ทราบว่าเหตุใดแอปจึงต้องการสิทธิ์ก่อนที่จะเรียกใช้ requestPermissions()
งานวิจัยแสดงให้เห็นว่าผู้ใช้จะรู้สึกสบายใจมากขึ้นเมื่อแอปขอสิทธิ์หากผู้ใช้ทราบว่าแอปต้องใช้สิทธิ์ดังกล่าวเพื่ออะไร เช่น จำเป็นต้องขอสิทธิ์เพื่อรองรับฟีเจอร์หลักของแอปหรือเพื่อโฆษณา ดังนั้น หากคุณใช้การเรียก API เพียงบางส่วนซึ่งอยู่ภายใต้กลุ่มสิทธิ์ ก็ควรระบุสิทธิ์เหล่านั้นที่คุณใช้และเหตุผลอย่างชัดเจน เช่น หากคุณใช้ตำแหน่งแบบหยาบเท่านั้น โปรดแจ้งให้ผู้ใช้ทราบในคำอธิบายแอปหรือบทความความช่วยเหลือเกี่ยวกับแอป
ในบางกรณี การแจ้งให้ผู้ใช้ทราบเกี่ยวกับการเข้าถึงข้อมูลที่ละเอียดอ่อนแบบเรียลไทม์ก็เป็นประโยชน์เช่นกัน ตัวอย่างเช่น หากคุณเข้าถึงกล้องหรือไมโครโฟน คุณควรแจ้งให้ผู้ใช้ทราบโดยใช้ไอคอนการแจ้งเตือนที่ใดที่หนึ่งในแอปหรือในถาดการแจ้งเตือน (หากแอปพลิเคชันทำงานอยู่เบื้องหลัง) เพื่อไม่ให้ดูเหมือนว่าคุณกำลังรวบรวมข้อมูลโดยไม่เปิดเผย
ท้ายที่สุดแล้ว หากคุณต้องขอสิทธิ์เพื่อให้บางอย่างในแอปใช้งานได้ แต่เหตุผลที่ผู้ใช้ไม่ชัดเจนคือหาวิธีแจ้งให้ผู้ใช้ทราบถึงเหตุผลที่คุณต้องมีสิทธิ์ที่ละเอียดอ่อนที่สุด
หากเมธอด ContextCompat.checkSelfPermission()
แสดงผลลัพธ์เป็น PERMISSION_DENIED
ให้เรียกใช้ shouldShowRequestPermissionRationale()
หากเมธอดนี้แสดงผลเป็น true
ให้แสดง UI ที่ให้ข้อมูลแก่ผู้ใช้ ใน UI นี้ ให้อธิบายว่าเหตุใดฟีเจอร์ที่ผู้ใช้ต้องการเปิดใช้จึงต้องใช้สิทธิ์หนึ่งๆ
นอกจากนี้ หากแอปขอสิทธิ์ที่เกี่ยวข้องกับตำแหน่ง ไมโครโฟน หรือกล้อง ให้ลองอธิบายเหตุผลที่แอปจำเป็นต้องเข้าถึงข้อมูลนี้
ขอสิทธิ์
หลังจากผู้ใช้ดู UI ที่ให้ความรู้ หรือค่าที่แสดงผลของ shouldShowRequestPermissionRationale()
ระบุว่าคุณไม่จำเป็นต้องแสดง UI ที่ให้ความรู้ ให้ขอสิทธิ์ ผู้ใช้จะเห็นกล่องโต้ตอบสิทธิ์ของระบบ ซึ่งสามารถเลือกได้ว่าจะให้สิทธิ์บางอย่างแก่แอปของคุณหรือไม่
โดยใช้สัญญา RequestPermission
ซึ่งรวมอยู่ในไลบรารี AndroidX โดยคุณอนุญาตให้ระบบจัดการโค้ดคำขอสิทธิ์ให้คุณ เนื่องจากการใช้สัญญา RequestPermission
จะทำให้ตรรกะของคุณง่ายขึ้น เราจึงขอแนะนำวิธีแก้ปัญหานี้หากเป็นไปได้ อย่างไรก็ตาม หากจำเป็น คุณยังจัดการโค้ดคำขอด้วยตนเองเป็นส่วนหนึ่งของคำขอสิทธิ์และรวมโค้ดคำขอนี้ไว้ในตรรกะ Callback ของสิทธิ์ได้ด้วย
อนุญาตให้ระบบจัดการรหัสคำขอสิทธิ์
หากต้องการให้ระบบจัดการโค้ดคําขอที่เชื่อมโยงกับคําขอสิทธิ์ ให้เพิ่มการพึ่งพาในไลบรารีต่อไปนี้ในไฟล์ build.gradle
ของโมดูล
androidx.activity
เวอร์ชัน 1.2.0 ขึ้นไปandroidx.fragment
เวอร์ชัน 1.3.0 ขึ้นไป
จากนั้นคุณสามารถใช้ชั้นเรียนต่อไปนี้
- หากต้องการขอสิทธิ์รายการเดียว ให้ใช้
RequestPermission
- หากต้องการขอสิทธิ์หลายรายการพร้อมกัน ให้ใช้
RequestMultiplePermissions
ขั้นตอนต่อไปนี้จะแสดงวิธีใช้สัญญา RequestPermission
ขั้นตอนจะคล้ายกันสำหรับสัญญา RequestMultiplePermissions
ในตรรกะการเริ่มต้นของกิจกรรมหรือแฟรกเมนต์ ให้ส่งผ่านการใช้งาน
ActivityResultCallback
ไปยังการเรียกใช้registerForActivityResult()
ActivityResultCallback
จะกำหนดวิธีที่แอปจัดการกับการตอบกลับคำขอสิทธิ์ของผู้ใช้เก็บการอ้างอิงค่าที่แสดงผลของ
registerForActivityResult()
ซึ่งอยู่ในรูปแบบActivityResultLauncher
หากต้องการแสดงกล่องโต้ตอบสิทธิ์ของระบบเมื่อจำเป็น ให้เรียกใช้เมธอด
launch()
ในอินสแตนซ์ของActivityResultLauncher
ที่คุณบันทึกไว้ในขั้นตอนก่อนหน้าหลังจากเรียก
launch()
แล้ว กล่องโต้ตอบสิทธิ์ของระบบจะปรากฏขึ้น เมื่อผู้ใช้เลือกตัวเลือก ระบบจะเรียกใช้การติดตั้งใช้งานActivityResultCallback
แบบแอซิงโครนัสที่คุณกำหนดไว้ในขั้นตอนก่อนหน้าหมายเหตุ: แอปของคุณไม่สามารถปรับแต่งกล่องโต้ตอบที่ปรากฏเมื่อโทรหา
launch()
หากต้องการให้ข้อมูลเพิ่มเติมหรือบริบทแก่ผู้ใช้ ให้เปลี่ยน UI ของแอปเพื่อให้ผู้ใช้เข้าใจได้ง่ายขึ้นว่าเหตุใดฟีเจอร์ในแอปจึงต้องใช้สิทธิ์บางอย่าง เช่น คุณอาจเปลี่ยนข้อความในปุ่มที่เปิดใช้ฟีเจอร์นอกจากนี้ ข้อความในกล่องโต้ตอบสิทธิ์ของระบบจะอ้างอิงกลุ่มสิทธิ์ที่เชื่อมโยงกับสิทธิ์ที่คุณขอ การจัดกลุ่มสิทธิ์นี้ออกแบบมาเพื่อการใช้งานระบบที่ง่ายดาย และแอปของคุณไม่ควรอาศัยสิทธิ์ที่อยู่ภายในหรือภายนอกกลุ่มสิทธิ์ที่เฉพาะเจาะจง
ข้อมูลโค้ดต่อไปนี้แสดงวิธีจัดการการตอบกลับสิทธิ์
Kotlin
// Register the permissions callback, which handles the user's response to the // system permissions dialog. Save the return value, an instance of // ActivityResultLauncher. You can use either a val, as shown in this snippet, // or a lateinit var in your onAttach() or onCreate() method. val requestPermissionLauncher = registerForActivityResult(RequestPermission() ) { isGranted: Boolean -> if (isGranted) { // Permission is granted. Continue the action or workflow in your // app. } else { // Explain to the user that the feature is unavailable because the // feature requires a permission that the user has denied. At the // same time, respect the user's decision. Don't link to system // settings in an effort to convince the user to change their // decision. } }
Java
// Register the permissions callback, which handles the user's response to the // system permissions dialog. Save the return value, an instance of // ActivityResultLauncher, as an instance variable. private ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult(new RequestPermission(), isGranted -> { if (isGranted) { // Permission is granted. Continue the action or workflow in your // app. } else { // Explain to the user that the feature is unavailable because the // feature requires a permission that the user has denied. At the // same time, respect the user's decision. Don't link to system // settings in an effort to convince the user to change their // decision. } });
ตัวอย่างโค้ดนี้แสดงกระบวนการที่แนะนําในการตรวจสอบสิทธิ์และขอสิทธิ์จากผู้ใช้เมื่อจําเป็น
Kotlin
when { ContextCompat.checkSelfPermission( CONTEXT, Manifest.permission.REQUESTED_PERMISSION ) == PackageManager.PERMISSION_GRANTED -> { // You can use the API that requires the permission. } ActivityCompat.shouldShowRequestPermissionRationale( this, Manifest.permission.REQUESTED_PERMISSION) -> { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected, and what // features are disabled if it's declined. In this UI, include a // "cancel" or "no thanks" button that lets the user continue // using your app without granting the permission. showInContextUI(...) } else -> { // You can directly ask for the permission. // The registered ActivityResultCallback gets the result of this request. requestPermissionLauncher.launch( Manifest.permission.REQUESTED_PERMISSION) } }
Java
if (ContextCompat.checkSelfPermission( CONTEXT, Manifest.permission.REQUESTED_PERMISSION) == PackageManager.PERMISSION_GRANTED) { // You can use the API that requires the permission. performAction(...); } else if (ActivityCompat.shouldShowRequestPermissionRationale( this, Manifest.permission.REQUESTED_PERMISSION)) { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected, and what // features are disabled if it's declined. In this UI, include a // "cancel" or "no thanks" button that lets the user continue // using your app without granting the permission. showInContextUI(...); } else { // You can directly ask for the permission. // The registered ActivityResultCallback gets the result of this request. requestPermissionLauncher.launch( Manifest.permission.REQUESTED_PERMISSION); }
จัดการโค้ดคำขอสิทธิ์ด้วยตนเอง
นอกเหนือจากการอนุญาตให้ระบบจัดการรหัสคำขอสิทธิ์แล้ว คุณยังจัดการรหัสคำขอสิทธิ์ด้วยตนเองได้ โดยใส่รหัสคำขอในการเรียกใช้ requestPermissions()
ข้อมูลโค้ดต่อไปนี้แสดงวิธีขอสิทธิ์โดยใช้รหัสคำขอ
Kotlin
when { ContextCompat.checkSelfPermission( CONTEXT, Manifest.permission.REQUESTED_PERMISSION ) == PackageManager.PERMISSION_GRANTED -> { // You can use the API that requires the permission. performAction(...) } ActivityCompat.shouldShowRequestPermissionRationale( this, Manifest.permission.REQUESTED_PERMISSION) -> { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected, and what // features are disabled if it's declined. In this UI, include a // "cancel" or "no thanks" button that lets the user continue // using your app without granting the permission. showInContextUI(...) } else -> { // You can directly ask for the permission. requestPermissions(CONTEXT, arrayOf(Manifest.permission.REQUESTED_PERMISSION), REQUEST_CODE) } }
Java
if (ContextCompat.checkSelfPermission( CONTEXT, Manifest.permission.REQUESTED_PERMISSION) == PackageManager.PERMISSION_GRANTED) { // You can use the API that requires the permission. performAction(...); } else if (ActivityCompat.shouldShowRequestPermissionRationale( this, Manifest.permission.REQUESTED_PERMISSION)) { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected, and what // features are disabled if it's declined. In this UI, include a // "cancel" or "no thanks" button that lets the user continue // using your app without granting the permission. showInContextUI(...); } else { // You can directly ask for the permission. requestPermissions(CONTEXT, new String[] { Manifest.permission.REQUESTED_PERMISSION }, REQUEST_CODE); }
หลังจากที่ผู้ใช้ตอบกลับกล่องโต้ตอบสิทธิ์ของระบบแล้ว ระบบจะเรียกใช้onRequestPermissionsResult()
ของแอป ระบบจะส่งการตอบกลับของผู้ใช้ต่อกล่องโต้ตอบสิทธิ์ รวมถึงรหัสคำขอที่คุณกำหนด ดังที่แสดงในข้อมูลโค้ดต่อไปนี้
Kotlin
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { when (requestCode) { PERMISSION_REQUEST_CODE -> { // If request is cancelled, the result arrays are empty. if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // Permission is granted. Continue the action or workflow // in your app. } else { // Explain to the user that the feature is unavailable because // the feature requires a permission that the user has denied. // At the same time, respect the user's decision. Don't link to // system settings in an effort to convince the user to change // their decision. } return } // Add other 'when' lines to check for other // permissions this app might request. else -> { // Ignore all other requests. } } }
Java
@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case PERMISSION_REQUEST_CODE: // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Permission is granted. Continue the action or workflow // in your app. } else { // Explain to the user that the feature is unavailable because // the feature requires a permission that the user has denied. // At the same time, respect the user's decision. Don't link to // system settings in an effort to convince the user to change // their decision. } return; } // Other 'case' lines to check for other // permissions this app might request. } }
ขอสิทธิ์เข้าถึงตำแหน่ง
เมื่อขอสิทธิ์เข้าถึงตำแหน่ง ให้ทำตามแนวทางปฏิบัติแนะนำเดียวกันกับสิทธิ์รันไทม์อื่นๆ ความแตกต่างที่สําคัญอย่างหนึ่งเกี่ยวกับสิทธิ์เข้าถึงตําแหน่งคือระบบมีสิทธิ์หลายรายการที่เกี่ยวข้องกับตําแหน่ง สิทธิ์ที่คุณขอและวิธีส่งคำขอจะขึ้นอยู่กับข้อกำหนดสถานที่ตั้งสำหรับกรณีการใช้งานของแอป
ตำแหน่งเบื้องหน้า
หากแอปมีฟีเจอร์ที่แชร์หรือรับข้อมูลตำแหน่งเพียงครั้งเดียวหรือเป็นระยะเวลาที่กําหนดไว้ ฟีเจอร์ดังกล่าวจําเป็นต้องเข้าถึงตําแหน่งในเบื้องหน้า ตัวอย่างบางส่วนมีดังนี้
- ฟีเจอร์ภายในแอปการนำทางช่วยให้ผู้ใช้ขอเส้นทางแบบเลี้ยวต่อเลี้ยวได้
- ฟีเจอร์ในแอปการรับส่งข้อความช่วยให้ผู้ใช้แชร์ตำแหน่งปัจจุบันกับผู้ใช้รายอื่นได้
ระบบจะถือว่าแอปของคุณใช้ตำแหน่งในเบื้องหน้าหากฟีเจอร์ของแอปเข้าถึงตำแหน่งปัจจุบันของอุปกรณ์ในสถานการณ์ใดสถานการณ์หนึ่งต่อไปนี้
- กิจกรรมที่เป็นของแอปจะปรากฏขึ้น
แอปของคุณกำลังใช้บริการที่ทำงานอยู่เบื้องหน้า เมื่อบริการที่ทำงานอยู่เบื้องหน้าทำงานอยู่ ระบบจะแจ้งให้ผู้ใช้ทราบด้วยการแสดงการแจ้งเตือนแบบถาวร แอปจะยังคงมีสิทธิ์เข้าถึงเมื่อทำงานอยู่เบื้องหลัง เช่น เมื่อผู้ใช้กดปุ่มหน้าแรกบนอุปกรณ์หรือปิดจอแสดงผลของอุปกรณ์
ใน Android 10 (API ระดับ 29) ขึ้นไป คุณต้องประกาศประเภทบริการที่ทำงานอยู่เบื้องหน้าเป็น
location
ดังที่แสดงในข้อมูลโค้ดต่อไปนี้ ใน Android เวอร์ชันเก่า เราขอแนะนำให้คุณประกาศประเภทบริการที่ทำงานอยู่เบื้องหน้านี้<!-- Recommended for Android 9 (API level 28) and lower. --> <!-- Required for Android 10 (API level 29) and higher. --> <service android:name="MyNavigationService" android:foregroundServiceType="location" ... > <!-- Any inner elements go here. --> </service>
คุณประกาศความต้องการตำแหน่งในเบื้องหน้าเมื่อแอปขอสิทธิ์ ACCESS_COARSE_LOCATION
หรือสิทธิ์ ACCESS_FINE_LOCATION
ดังที่แสดงในข้อมูลโค้ดต่อไปนี้
<manifest ... > <!-- Include this permission any time your app needs location information. --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- Include only if your app benefits from precise location access. --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> </manifest>
ตำแหน่งในเบื้องหลัง
แอปต้องใช้สิทธิ์เข้าถึงตำแหน่งในเบื้องหลังหากฟีเจอร์ภายในแอปแชร์ตำแหน่งกับผู้ใช้รายอื่นอย่างต่อเนื่องหรือใช้ Geofencing ตัวอย่างบางส่วนมีดังนี้
- ภายในแอปการแชร์ตำแหน่งของครอบครัว ฟีเจอร์ช่วยให้ผู้ใช้แชร์ตำแหน่งกับสมาชิกในครอบครัวได้อย่างต่อเนื่อง
- ภายในแอป IoT ฟีเจอร์หนึ่งจะอนุญาตให้ผู้ใช้กำหนดค่าอุปกรณ์ในบ้าน เช่น ปิดอุปกรณ์เมื่อผู้ใช้ออกจากบ้านและเปิดอีกครั้งเมื่อผู้ใช้กลับถึงบ้าน
ระบบจะถือว่าแอปของคุณใช้ตำแหน่งในเบื้องหลังหากแอปเข้าถึงตำแหน่งปัจจุบันของอุปกรณ์ในสถานการณ์อื่นนอกเหนือจากที่อธิบายไว้ในส่วนตำแหน่งในเบื้องหน้า ความแม่นยำของตำแหน่งในเบื้องหลังจะเหมือนกับความแม่นยำของตำแหน่งในเบื้องหน้า ซึ่งขึ้นอยู่กับสิทธิ์เข้าถึงตำแหน่งที่แอปประกาศ
ใน Android 10 (API ระดับ 29) ขึ้นไป คุณต้องประกาศสิทธิ์ ACCESS_BACKGROUND_LOCATION
ในไฟล์ Manifest ของแอปเพื่อขอสิทธิ์เข้าถึงตำแหน่งในเบื้องหลังขณะรันไทม์ ใน Android เวอร์ชันเก่า เมื่อแอปได้รับสิทธิ์เข้าถึงตำแหน่งเมื่อทำงานอยู่เบื้องหน้า ก็จะได้รับสิทธิ์เข้าถึงตำแหน่งเมื่อทำงานอยู่เบื้องหลังด้วยโดยอัตโนมัติ
<manifest ... > <!-- Required only when requesting background location access on Android 10 (API level 29) and higher. --> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> </manifest>
จัดการการปฏิเสธสิทธิ์
หากผู้ใช้ปฏิเสธคำขอสิทธิ์ แอปของคุณควรช่วยให้ผู้ใช้เข้าใจผลกระทบของการปฏิเสธสิทธิ์ โดยเฉพาะอย่างยิ่ง แอปของคุณควรแจ้งให้ผู้ใช้ทราบถึงฟีเจอร์ที่ใช้งานไม่ได้เนื่องจากไม่มีสิทธิ์ เมื่อคุณดำเนินการดังกล่าว โปรดคำนึงถึงแนวทางปฏิบัติแนะนำต่อไปนี้
ชี้นำความสนใจของผู้ใช้ ไฮไลต์ส่วนที่เฉพาะเจาะจงของ UI ของแอปที่ฟังก์ชันการทํางานถูกจํากัดเนื่องจากแอปไม่มีสิทธิ์ที่จําเป็น ตัวอย่างสิ่งที่คุณจะทำได้มีดังต่อไปนี้
- แสดงข้อความในตำแหน่งที่ผลลัพธ์หรือข้อมูลของฟีเจอร์ควรจะปรากฏ
- แสดงปุ่มอื่นที่มีไอคอนและสีข้อผิดพลาด
ใช้คำที่เฉพาะเจาะจง อย่าแสดงข้อความทั่วไป แต่ควรชี้แจงให้ชัดเจนว่าฟีเจอร์ใดที่ ไม่พร้อมใช้งานเพราะแอปของคุณไม่มีสิทธิ์ที่จำเป็น
อย่าบล็อกอินเทอร์เฟซผู้ใช้ กล่าวคือ ไม่แสดงข้อความเตือนแบบเต็มหน้าจอที่ป้องกันไม่ให้ผู้ใช้ใช้แอปของคุณต่อเลย
ในขณะเดียวกัน แอปของคุณควรเคารพการตัดสินใจของผู้ใช้ในการปฏิเสธการให้สิทธิ์ ตั้งแต่ Android 11 (API ระดับ 30) เป็นต้นไป หากผู้ใช้แตะปฏิเสธสิทธิ์บางอย่างมากกว่า 1 ครั้งตลอดอายุการติดตั้งแอปในอุปกรณ์ ผู้ใช้จะไม่เห็นกล่องโต้ตอบสิทธิ์ของระบบหากแอปขอสิทธิ์นั้นอีกครั้ง การดำเนินการของผู้ใช้จะถือว่า "ไม่ต้องถามอีก" ในเวอร์ชันก่อนหน้า ผู้ใช้จะเห็นกล่องโต้ตอบสิทธิ์ของระบบทุกครั้งที่แอปของคุณขอสิทธิ์ เว้นแต่ผู้ใช้จะเลือกช่องทำเครื่องหมายหรือตัวเลือก "ไม่ต้องถามอีก" ไว้ก่อนหน้านี้
หากผู้ใช้ปฏิเสธคำขอสิทธิ์มากกว่า 1 ครั้ง ระบบจะถือว่าเป็นการปฏิเสธอย่างถาวร คุณควรแสดงข้อความแจ้งให้ผู้ใช้ขอสิทธิ์ก็ต่อเมื่อต้องการเข้าถึงฟีเจอร์หนึ่งๆ ไม่เช่นนั้นคุณอาจสูญเสียความสามารถในการขอสิทธิ์อีกครั้งโดยไม่ตั้งใจ
ในบางสถานการณ์ ระบบอาจปฏิเสธสิทธิ์โดยอัตโนมัติโดยที่ผู้ใช้ไม่ต้องดำเนินการใดๆ (อาจมีการให้สิทธิ์ โดยอัตโนมัติด้วย) สิ่งสำคัญคืออย่าคาดเดาเกี่ยวกับลักษณะการทํางานอัตโนมัติ ทุกครั้งที่แอปต้องการเข้าถึงฟังก์ชันที่ต้องใช้สิทธิ์ ให้ตรวจสอบว่าแอปยังคงได้รับสิทธิ์นั้นอยู่
หากต้องการให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ดีที่สุดเมื่อขอสิทธิ์ของแอป โปรดดูแนวทางปฏิบัติแนะนำเกี่ยวกับสิทธิ์ของแอป
ตรวจสอบสถานะการปฏิเสธขณะทดสอบและแก้ไขข้อบกพร่อง
หากต้องการระบุว่าแอปถูกปฏิเสธสิทธิ์อย่างถาวรหรือไม่ (เพื่อวัตถุประสงค์ในการแก้ไขข้อบกพร่องและการทดสอบ) ให้ใช้คําสั่งต่อไปนี้
adb shell dumpsys package PACKAGE_NAME
โดย PACKAGE_NAME คือชื่อแพ็กเกจที่จะตรวจสอบ
เอาต์พุตของคำสั่งมีส่วนที่มีลักษณะเช่นนี้
... runtime permissions: android.permission.POST_NOTIFICATIONS: granted=false, flags=[ USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED] android.permission.ACCESS_FINE_LOCATION: granted=false, flags=[ USER_SET|USER_FIXED|USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED] android.permission.BLUETOOTH_CONNECT: granted=false, flags=[ USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED] ...
สิทธิ์ที่ผู้ใช้ปฏิเสธ 1 ครั้งจะมีการแจ้งว่าไม่เหมาะสมโดย USER_SET
สิทธิ์ที่ถูกปฏิเสธอย่างถาวรโดยการเลือกปฏิเสธ 2 ครั้งจะได้รับการแจ้งว่าไม่อนุญาตโดย USER_FIXED
เพื่อให้แน่ใจว่าผู้ทดสอบจะเห็นกล่องโต้ตอบคำขอระหว่างการทดสอบ ให้รีเซ็ตแฟล็กเหล่านี้เมื่อแก้ไขข้อบกพร่องของแอปเสร็จแล้ว ให้ใช้คำสั่งต่อไปนี้
adb shell pm clear-permission-flags PACKAGE_NAME PERMISSION_NAME user-set user-fixed
PERMISSION_NAME คือชื่อของสิทธิ์ที่คุณต้องการรีเซ็ต
โปรดไปที่หน้าอ้างอิง API สิทธิ์เพื่อดูรายการสิทธิ์ของแอป Android ทั้งหมด
สิทธิ์แบบครั้งเดียว
เริ่มตั้งแต่ Android 11 (API ระดับ 30) เมื่อใดก็ตามที่แอปขอสิทธิ์ที่เกี่ยวข้องกับตำแหน่ง ไมโครโฟน หรือกล้อง กล่องโต้ตอบสิทธิ์ที่แสดงต่อผู้ใช้จะมีตัวเลือกชื่อเฉพาะครั้งนี้ ดังที่แสดงในรูปที่ 2 หากผู้ใช้เลือกตัวเลือกนี้ในกล่องโต้ตอบ แอปของคุณจะได้รับสิทธิ์แบบครั้งเดียวชั่วคราว
จากนั้นแอปจะเข้าถึงข้อมูลที่เกี่ยวข้องได้เป็นเวลาหนึ่งๆ โดยขึ้นอยู่กับลักษณะการทํางานของแอปและการดําเนินการของผู้ใช้ ดังนี้
- ขณะที่กิจกรรมของแอปปรากฏอยู่ แอปจะเข้าถึงข้อมูลได้
- หากผู้ใช้ส่งแอปของคุณไปยังเบื้องหลัง แอปจะยังเข้าถึงข้อมูลต่อไปได้เป็นเวลาสั้นๆ
- หากคุณเปิดบริการที่ทำงานอยู่เบื้องหน้าขณะที่กิจกรรมแสดงอยู่ และผู้ใช้ย้ายแอปไปยังเบื้องหลัง แอปจะเข้าถึงข้อมูลต่อไปได้จนกว่าบริการที่ทำงานอยู่เบื้องหน้าจะหยุดลง
กระบวนการของแอปจะสิ้นสุดลงเมื่อเพิกถอนสิทธิ์
หากผู้ใช้เพิกถอนสิทธิ์แบบครั้งเดียว เช่น ในการตั้งค่าระบบ แอปของคุณจะเข้าถึงข้อมูลไม่ได้ ไม่ว่าคุณจะเปิดใช้บริการที่ทำงานอยู่เบื้องหน้าหรือไม่ก็ตาม เช่นเดียวกับสิทธิ์อื่นๆ หากผู้ใช้เพิกถอนสิทธิ์แบบครั้งเดียวของแอป กระบวนการของแอปจะสิ้นสุดลง
เมื่อผู้ใช้เปิดแอปของคุณในครั้งถัดไปและฟีเจอร์ในแอปขอเข้าถึงตำแหน่ง ไมโครโฟน หรือกล้อง ผู้ใช้จะได้รับข้อความแจ้งให้ขอสิทธิ์อีกครั้ง
รีเซ็ตสิทธิ์ที่ไม่ได้ใช้
Android มีวิธีรีเซ็ตสิทธิ์รันไทม์ที่ไม่ได้ใช้กลับเป็นสถานะเริ่มต้นที่ปฏิเสธได้หลายวิธี ดังนี้
- API ที่คุณนำสิทธิ์เข้าถึงของแอปออกสำหรับสิทธิ์รันไทม์ที่ไม่ได้ใช้ด้วยตนเองได้
- กลไกของระบบที่จะรีเซ็ตสิทธิ์ของแอปที่ไม่ได้ใช้โดยอัตโนมัติ
นำสิทธิ์เข้าถึงแอปออก
ใน Android 13 (API ระดับ 33) ขึ้นไป คุณสามารถนําสิทธิ์เข้าถึงรันไทม์ที่แอปไม่จําเป็นต้องใช้แล้วออกจากแอปได้ เมื่ออัปเดตแอป ให้ทําตามขั้นตอนนี้เพื่อให้ผู้ใช้มีแนวโน้มที่จะเข้าใจสาเหตุที่แอปขอสิทธิ์บางอย่างต่อไป ความรู้นี้จะช่วยสร้างความไว้วางใจให้กับผู้ใช้ในแอป
หากต้องการนำสิทธิ์เข้าถึงรันไทม์ออก ให้ส่งชื่อสิทธิ์นั้นไปที่ revokeSelfPermissionOnKill()
หากต้องการยกเลิกสิทธิ์เข้าถึงกลุ่มสิทธิ์รันไทม์พร้อมกัน ให้ส่งคอลเล็กชันชื่อสิทธิ์ไปยัง revokeSelfPermissionsOnKill()
กระบวนการนำสิทธิ์ออกจะทำงานแบบไม่พร้อมกันและจะหยุดกระบวนการทั้งหมดที่เชื่อมโยงกับ UID ของแอป
หากต้องการให้ระบบนำสิทธิ์เข้าถึงของแอปออก คุณต้องหยุดกระบวนการทั้งหมดที่เชื่อมโยงกับแอป เมื่อคุณเรียก API ระบบจะพิจารณาว่าเมื่อใดที่ควรหยุดกระบวนการเหล่านี้ โดยปกติแล้วระบบจะรอจนกระทั่งแอปใช้เวลานานขึ้นในการทำงานในเบื้องหลังแทนที่จะอยู่เบื้องหน้า
หากต้องการแจ้งให้ผู้ใช้ทราบว่าแอปไม่จำเป็นต้องเข้าถึงสิทธิ์รันไทม์บางอย่างอีกต่อไป ให้แสดงกล่องโต้ตอบเมื่อผู้ใช้เปิดแอปในครั้งถัดไป โดยกล่องโต้ตอบนี้อาจรวมรายการสิทธิ์ไว้ด้วย
รีเซ็ตสิทธิ์ของแอปที่ไม่ได้ใช้โดยอัตโนมัติ
หากแอปกำหนดเป้าหมายเป็น Android 11 (API ระดับ 30) ขึ้นไปและไม่ได้ใช้งานเป็นเวลา 2-3 เดือน ระบบจะปกป้องข้อมูลผู้ใช้โดยรีเซ็ตสิทธิ์รันไทม์ที่มีความละเอียดอ่อนซึ่งผู้ใช้ให้สิทธิ์แอปโดยอัตโนมัติ ดูข้อมูลเพิ่มเติมในคู่มือเกี่ยวกับโหมดพักของแอป
ขอเป็นตัวแฮนเดิลเริ่มต้น หากจำเป็น
แอปบางแอปจำเป็นต้องเข้าถึงข้อมูลที่ละเอียดอ่อนของผู้ใช้ที่เกี่ยวข้องกับบันทึกการโทรและข้อความ SMS หากต้องการขอสิทธิ์เฉพาะสำหรับบันทึกการโทรและข้อความ SMS และเผยแพร่แอปใน Play Store คุณต้องแจ้งให้ผู้ใช้ตั้งค่าแอปของคุณเป็นตัวแฮนเดิลเริ่มต้นสำหรับฟังก์ชันหลักของระบบก่อนที่จะขอสิทธิ์รันไทม์เหล่านี้
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับตัวแฮนเดิลเริ่มต้น รวมถึงคำแนะนำในการแสดงข้อความแจ้งของตัวแฮนเดิลเริ่มต้นแก่ผู้ใช้ โปรดดูคำแนะนำเกี่ยวกับสิทธิ์ที่ใช้ในตัวแฮนเดิลเริ่มต้นเท่านั้น
ให้สิทธิ์รันไทม์ทั้งหมดเพื่อวัตถุประสงค์ในการทดสอบ
หากต้องการให้สิทธิ์รันไทม์ทั้งหมดโดยอัตโนมัติเมื่อคุณติดตั้งแอปในโปรแกรมจำลองหรืออุปกรณ์ทดสอบ ให้ใช้ตัวเลือก -g
สำหรับคำสั่ง adb shell install
ดังที่แสดงในตัวอย่างโค้ดต่อไปนี้
adb shell install -g PATH_TO_APK_FILE
แหล่งข้อมูลเพิ่มเติม
อ่านข้อมูลเพิ่มเติมเกี่ยวกับสิทธิ์ได้ในบทความต่อไปนี้
ดูข้อมูลเพิ่มเติมเกี่ยวกับการขอสิทธิ์ได้ในตัวอย่างสิทธิ์
นอกจากนี้ คุณยังทำCodelab ที่แสดงแนวทางปฏิบัติแนะนำด้านความเป็นส่วนตัวนี้ให้เสร็จสมบูรณ์ได้ด้วย