ขอสิทธิ์รันไทม์

แอป Android ทุกแอปจะทำงานในแซนด์บ็อกซ์ที่จำกัดการเข้าถึง หากแอปของคุณจำเป็นต้องใช้ทรัพยากรหรือข้อมูลนอกแซนด์บ็อกซ์ของแอปเอง คุณสามารถประกาศสิทธิ์รันไทม์และตั้งค่าคำขอสิทธิ์ที่ให้สิทธิ์เข้าถึงนี้ได้ ขั้นตอนเหล่านี้เป็นส่วนหนึ่งของเวิร์กโฟลว์สำหรับการใช้สิทธิ์

หากคุณประกาศสิทธิ์ที่เป็นอันตราย และหากแอปของคุณติดตั้งในอุปกรณ์ที่ใช้ Android 6.0 (ระดับ API 23) ขึ้นไป คุณต้องขอสิทธิ์ที่เป็นอันตรายที่รันไทม์โดยทำตามขั้นตอนในคู่มือนี้

หากคุณไม่ได้ประกาศสิทธิ์ที่เป็นอันตราย หรือหากแอปติดตั้งอยู่ในอุปกรณ์ที่ใช้ Android 5.1 (ระดับ API 22) หรือต่ำกว่า ระบบจะให้สิทธิ์โดยอัตโนมัติ และคุณไม่จำเป็นต้องทำตามขั้นตอนที่เหลือในหน้านี้

หลักการพื้นฐาน

หลักการพื้นฐานในการขอสิทธิ์ที่รันไทม์มีดังนี้

  • ขอสิทธิ์ในบริบทเมื่อผู้ใช้เริ่มโต้ตอบกับฟีเจอร์ที่ต้องใช้สิทธิ์นั้น
  • อย่าบล็อกผู้ใช้ ให้ตัวเลือกในการยกเลิกขั้นตอน UI เพื่อการศึกษาเสมอ เช่น ขั้นตอนที่อธิบายเหตุผลในการขอสิทธิ์
  • หากผู้ใช้ปฏิเสธหรือเพิกถอนสิทธิ์ที่ฟีเจอร์หนึ่งต้องใช้ ให้ลดแอปลงอย่างค่อยเป็นค่อยไปเพื่อให้ผู้ใช้ใช้แอปของคุณต่อไปได้ ซึ่งอาจทำได้โดยปิดใช้ฟีเจอร์ที่ต้องใช้สิทธิ์
  • อย่าคาดเดาพฤติกรรมของระบบ ตัวอย่างเช่น อย่าคิดว่าสิทธิ์จะปรากฏในกลุ่มสิทธิ์เดียวกัน กลุ่มสิทธิ์เพียงช่วยให้ระบบลดจำนวนกล่องโต้ตอบของระบบที่แสดงต่อผู้ใช้เมื่อแอปขอสิทธิ์ที่เกี่ยวข้องอย่างใกล้ชิด

เวิร์กโฟลว์สำหรับการขอสิทธิ์

ก่อนที่จะประกาศและขอสิทธิ์รันไทม์ในแอป ให้ประเมินว่าแอปจำเป็นต้องทำเช่นนั้นหรือไม่ คุณสามารถดำเนินการตาม Use Case หลายอย่างในแอปได้ เช่น การถ่ายภาพ การหยุดเล่นสื่อชั่วคราว และการแสดงโฆษณาที่เกี่ยวข้อง โดยไม่จำเป็นต้องประกาศสิทธิ์ใดๆ

หากคุณสรุปได้ว่าแอปต้องประกาศและขอสิทธิ์รันไทม์ ให้ทำตามขั้นตอนต่อไปนี้

  1. ประกาศสิทธิ์ที่แอปของคุณอาจต้องขอในไฟล์ Manifest ของแอป
  2. ออกแบบประสบการณ์ของผู้ใช้ (UX) ของแอปเพื่อให้การดำเนินการที่เฉพาะเจาะจงในแอปเชื่อมโยงกับสิทธิ์รันไทม์ที่เฉพาะเจาะจง แจ้งให้ผู้ใช้ทราบว่าการดำเนินการใดบ้างที่อาจต้องให้ผู้ใช้ให้สิทธิ์แอปของคุณเข้าถึงข้อมูลส่วนตัวของผู้ใช้
  3. รอให้ผู้ใช้เรียกใช้ภารกิจหรือการดำเนินการในแอปที่ต้องเข้าถึงข้อมูลส่วนตัวที่เฉพาะเจาะจงของผู้ใช้ ในตอนนั้น แอปของคุณจะขอสิทธิ์รันไทม์ที่จำเป็นสำหรับการเข้าถึงข้อมูลดังกล่าวได้
  4. ตรวจสอบว่าผู้ใช้ได้ให้สิทธิ์

    สิทธิ์รันไทม์ที่แอปของคุณต้องการ หากเป็นเช่นนั้น แอปของคุณจะเข้าถึงข้อมูลส่วนตัวของผู้ใช้ได้ หากไม่ ให้ลองทำขั้นตอนถัดไป

    คุณต้องตรวจสอบว่าคุณมีสิทธิ์หรือไม่ทุกครั้งที่ดำเนินการที่ต้องใช้สิทธิ์นั้น

  5. ตรวจสอบว่าแอปควรแสดงเหตุผลแก่ผู้ใช้หรือไม่ โดยอธิบายว่าเหตุใดแอปจึงต้องการให้ผู้ใช้ให้สิทธิ์รันไทม์บางอย่าง หากระบบพิจารณาว่าแอปไม่ควรแสดงเหตุผล ให้ไปยังขั้นตอนถัดไปโดยตรงโดยไม่ต้องแสดงองค์ประกอบ UI

    อย่างไรก็ตาม หากระบบพิจารณาว่าแอปของคุณควรแสดงเหตุผลประกอบ ให้นำเสนอเหตุผลประกอบต่อผู้ใช้ในองค์ประกอบ UI ในเหตุผลนี้ ให้อธิบายอย่างชัดเจนว่าแอปของคุณพยายามเข้าถึงข้อมูลใดและแอปจะให้ประโยชน์อะไรแก่ผู้ใช้หากผู้ใช้ให้สิทธิ์รันไทม์ หลังจากผู้ใช้รับทราบเหตุผลแล้ว ให้ไปยังขั้นตอนถัดไป

  6. ขอสิทธิ์รันไทม์ที่แอปของคุณต้องใช้เพื่อเข้าถึงข้อมูลส่วนตัวของผู้ใช้ ระบบจะแสดงข้อความแจ้งสิทธิ์รันไทม์ เช่น ข้อความที่แสดงในหน้าภาพรวมสิทธิ์

  7. ตรวจสอบการตอบสนองของผู้ใช้ว่าเลือกให้หรือปฏิเสธสิทธิ์รันไทม์

  8. หากผู้ใช้ให้สิทธิ์แก่แอป คุณจะเข้าถึงข้อมูลส่วนตัวของผู้ใช้ได้ หากผู้ใช้ปฏิเสธสิทธิ์แทน ให้ลดระดับประสบการณ์การใช้งานแอปอย่างค่อยเป็นค่อยไปเพื่อให้แอปมีฟังก์ชันการทำงานแก่ผู้ใช้โดยไม่มีข้อมูลที่ได้รับการปกป้องโดยสิทธิ์นั้น

รูปที่ 1 แสดงเวิร์กโฟลว์และชุดการตัดสินใจที่เกี่ยวข้องกับกระบวนการนี้

ผังงานที่แสดงกระบวนการตัดสินใจในการขอสิทธิ์รันไทม์ โดยเริ่มจากการดำเนินการของแอป การตรวจสอบว่าได้รับสิทธิ์หรือไม่ การตรวจสอบว่าจำเป็นต้องมีเหตุผลประกอบหรือไม่ การขอสิทธิ์ และการจัดการคำตอบของผู้ใช้
รูปที่ 1 แผนภาพที่แสดงเวิร์กโฟลว์สำหรับการประกาศและขอสิทธิ์รันไทม์ใน Android

ตรวจสอบว่าแอปของคุณได้รับสิทธิ์แล้วหรือยัง

หากต้องการตรวจสอบว่าผู้ใช้ให้สิทธิ์บางอย่างแก่แอปของคุณแล้วหรือยัง ให้ส่งสิทธิ์นั้นไปยังเมธอด ContextCompat.checkSelfPermission() เมธอดนี้จะแสดงผลเป็น PERMISSION_GRANTED หรือ PERMISSION_DENIED ขึ้นอยู่กับว่าแอปของคุณมีสิทธิ์หรือไม่

อธิบายเหตุผลที่แอปของคุณต้องใช้สิทธิ์

กล่องโต้ตอบสิทธิ์ที่ระบบแสดงเมื่อคุณเรียกใช้ requestPermissions() จะระบุสิทธิ์ที่แอปต้องการ แต่ไม่ได้ระบุเหตุผล ในบางกรณี ผู้ใช้อาจรู้สึกสับสน คุณควรอธิบายให้ผู้ใช้ทราบถึงสาเหตุที่แอปต้องการสิทธิ์ก่อนที่จะเรียกใช้ requestPermissions()

งานวิจัยแสดงให้เห็นว่าผู้ใช้จะสบายใจกับคำขอสิทธิ์มากกว่าหากทราบว่าเหตุใดแอปจึงต้องการสิทธิ์นั้น เช่น สิทธิ์นั้นจำเป็นต่อการรองรับฟีเจอร์หลักของแอปหรือเพื่อการโฆษณา ด้วยเหตุนี้ หากคุณใช้การเรียก API เพียงส่วนหนึ่งซึ่งอยู่ภายใต้กลุ่มสิทธิ์ ให้ระบุสิทธิ์ที่คุณใช้อย่างชัดเจนและเหตุผลที่ใช้ เช่น หากคุณใช้เฉพาะตำแหน่งคร่าวๆ ให้แจ้งให้ผู้ใช้ทราบในคำอธิบายแอปหรือในบทความช่วยเหลือเกี่ยวกับแอป

ในบางกรณี การแจ้งให้ผู้ใช้ทราบเกี่ยวกับการเข้าถึงข้อมูลที่ละเอียดอ่อนแบบเรียลไทม์ก็เป็นประโยชน์เช่นกัน ตัวอย่างเช่น หากคุณเข้าถึงกล้องหรือไมโครโฟน ให้พิจารณาแจ้งให้ผู้ใช้ทราบโดยใช้ไอคอนการแจ้งเตือนที่ใดที่หนึ่งในแอป หรือในถาดการแจ้งเตือน (หากแอปพลิเคชันทำงานอยู่เบื้องหลัง) เพื่อไม่ให้ดูเหมือนว่าคุณกำลังรวบรวมข้อมูลอย่างลับๆ

หากคุณต้องการขอสิทธิ์เพื่อให้ฟีเจอร์ทำงานได้ แต่เหตุผลไม่ชัดเจนสำหรับผู้ใช้ ให้หาวิธีอธิบายว่าทำไมคุณจึงต้องการสิทธิ์ที่มีความละเอียดอ่อน

หากเมธอด ContextCompat.checkSelfPermission() แสดงผลเป็น PERMISSION_DENIED ให้เรียกใช้ shouldShowRequestPermissionRationale() หากเมธอดนี้แสดงผลเป็น true ให้แสดง UI เพื่อให้ความรู้แก่ผู้ใช้ ใน UI นี้ ให้อธิบายว่าทำไมฟีเจอร์ที่ผู้ใช้ต้องการเปิดใช้จึงต้องมีสิทธิ์บางอย่าง

นอกจากนี้ หากแอปขอสิทธิ์ที่เกี่ยวข้องกับตำแหน่ง ไมโครโฟน หรือกล้อง ให้พิจารณาอธิบายเหตุผลที่แอปต้องเข้าถึงข้อมูลนี้

ขอสิทธิ์

หลังจากที่ผู้ใช้ดู UI เพื่อการศึกษาหรือค่าที่แสดงผลของ shouldShowRequestPermissionRationale() บ่งชี้ว่าคุณไม่จำเป็นต้องแสดง UI เพื่อการศึกษา ให้ขอสิทธิ์ ผู้ใช้จะเห็นกล่องโต้ตอบสิทธิ์ของระบบ ซึ่งสามารถเลือกได้ว่าจะให้สิทธิ์บางอย่างแก่แอปของคุณหรือไม่

โดยใช้สัญญา RequestPermission ซึ่งรวมอยู่ในไลบรารี AndroidX ที่คุณอนุญาตให้ระบบจัดการรหัสคำขอสิทธิ์ให้คุณ เนื่องจากการใช้สัญญา RequestPermission จะทำให้ตรรกะของคุณง่ายขึ้น จึงเป็นโซลูชันที่แนะนำเมื่อเป็นไปได้ อย่างไรก็ตาม หากจำเป็น คุณยังสามารถจัดการรหัสคำขอด้วยตนเองเป็นส่วนหนึ่งของคำขอสิทธิ์ และรวมรหัสคำขอนี้ไว้ในตรรกะ Callback ของสิทธิ์ได้

อนุญาตให้ระบบจัดการรหัสคำขอสิทธิ์

หากต้องการอนุญาตให้ระบบจัดการรหัสคำขอที่เชื่อมโยงกับคำขอสิทธิ์ ให้เพิ่มทรัพยากร Dependency ในไลบรารีต่อไปนี้ในไฟล์ build.gradle ของโมดูล

จากนั้นคุณจะใช้คลาสใดคลาสหนึ่งต่อไปนี้ได้

  • หากต้องการขอสิทธิ์เดียว ให้ใช้ RequestPermission
  • หากต้องการขอสิทธิ์หลายรายการพร้อมกัน ให้ใช้ RequestMultiplePermissions

ขั้นตอนต่อไปนี้แสดงวิธีใช้สัญญา RequestPermission กระบวนการนี้เกือบจะเหมือนกับสัญญา RequestMultiplePermissions

  1. ในตรรกะการเริ่มต้นของกิจกรรมหรือ Fragment ให้ส่งการใช้งาน ActivityResultCallback ไปยังการเรียกใช้ registerForActivityResult() ActivityResultCallback จะกำหนดวิธีที่แอปจัดการกับการตอบสนองของผู้ใช้ต่อคำขอสิทธิ์

    เก็บการอ้างอิงค่าที่แสดงผลของ registerForActivityResult() ซึ่งเป็นประเภท ActivityResultLauncher ไว้

  2. หากต้องการแสดงกล่องโต้ตอบสิทธิ์ของระบบเมื่อจำเป็น ให้เรียกใช้เมธอด launch() ในอินสแตนซ์ของ ActivityResultLauncher ที่คุณบันทึกไว้ในขั้นตอนก่อนหน้า

    หลังจากเรียกใช้ launch() แล้ว กล่องโต้ตอบสิทธิ์ของระบบจะปรากฏขึ้น เมื่อผู้ใช้เลือก ระบบจะเรียกใช้การติดตั้งใช้งาน ActivityResultCallback แบบอะซิงโครนัส ซึ่งคุณได้กำหนดไว้ในขั้นตอนก่อนหน้า

    หมายเหตุ: แอปของคุณไม่สามารถปรับแต่งกล่องโต้ตอบที่ปรากฏขึ้นเมื่อคุณเรียกใช้ launch() หากต้องการให้ข้อมูลหรือบริบทเพิ่มเติมแก่ผู้ใช้ ให้เปลี่ยน UI ของแอปเพื่อให้ผู้ใช้เข้าใจได้ง่ายขึ้นว่าเหตุใดฟีเจอร์ในแอปจึงต้องใช้สิทธิ์บางอย่าง เช่น คุณอาจเปลี่ยนข้อความในปุ่มที่เปิดใช้ฟีเจอร์

    นอกจากนี้ ข้อความในกล่องโต้ตอบสิทธิ์ของระบบจะอ้างอิงกลุ่มสิทธิ์ที่เชื่อมโยงกับสิทธิ์ที่คุณขอ การจัดกลุ่มสิทธิ์นี้ออกแบบมาเพื่อให้ระบบใช้งานง่าย และแอปของคุณไม่ควรพึ่งพาสิทธิ์ที่อยู่ภายในหรือภายนอกกลุ่มสิทธิ์ที่เฉพาะเจาะจง

ข้อมูลโค้ดต่อไปนี้แสดงวิธีจัดการการตอบกลับสิทธิ์

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);
}

และข้อมูลโค้ดนี้แสดงกระบวนการที่แนะนำในการตรวจสอบสิทธิ์และขอสิทธิ์จากผู้ใช้เมื่อจำเป็น

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.
    }
}

ขอสิทธิ์เข้าถึงตำแหน่ง

เมื่อขอสิทธิ์เข้าถึงตำแหน่ง ให้ทำตามแนวทางปฏิบัติแนะนำเดียวกันกับสิทธิ์รันไทม์อื่นๆ ความแตกต่างที่สำคัญอย่างหนึ่งเมื่อพูดถึงสิทธิ์เข้าถึงตำแหน่งคือ ระบบมีสิทธิ์หลายรายการที่เกี่ยวข้องกับตำแหน่ง สิทธิ์ที่คุณขอและวิธีขอสิทธิ์จะขึ้นอยู่กับข้อกำหนดสถานที่ตั้งสำหรับ Use Case ของแอป

ตำแหน่งที่ทำงานอยู่เบื้องหน้า

หากแอปของคุณมีฟีเจอร์ที่แชร์หรือรับข้อมูลตำแหน่งเพียงครั้งเดียวหรือเป็นระยะเวลาที่กำหนด ฟีเจอร์นั้นจะต้องมีสิทธิ์เข้าถึงตำแหน่งในเบื้องหน้า ตัวอย่างเช่น

  • ฟีเจอร์ในแอปการนำทางช่วยให้ผู้ใช้ได้รับคำแนะนำแบบเลี้ยวต่อเลี้ยว
  • ภายในแอปรับส่งข้อความ ฟีเจอร์หนึ่งช่วยให้ผู้ใช้แชร์ตำแหน่งปัจจุบันกับผู้ใช้รายอื่นได้

ระบบจะถือว่าแอปของคุณใช้ตำแหน่งในเบื้องหน้าหากฟีเจอร์ของแอปเข้าถึงตำแหน่งปัจจุบันของอุปกรณ์ในสถานการณ์ใดสถานการณ์หนึ่งต่อไปนี้

  • กิจกรรมที่เป็นของแอปของคุณจะปรากฏขึ้น
  • แอปของคุณกำลังเรียกใช้บริการที่ทำงานอยู่เบื้องหน้า เมื่อบริการที่ทำงานอยู่เบื้องหน้าทำงานอยู่ ระบบจะแจ้งให้ผู้ใช้ทราบโดยแสดงการแจ้งเตือนแบบถาวร แอปของคุณจะยังคงเข้าถึงได้เมื่ออยู่ในเบื้องหลัง เช่น เมื่อผู้ใช้กดปุ่มหน้าแรกบนอุปกรณ์หรือปิดจอแสดงผลของอุปกรณ์

    ใน 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 API ตัวอย่างมีดังนี้

  • ภายในแอปแชร์ตำแหน่งกับครอบครัว ฟีเจอร์หนึ่งช่วยให้ผู้ใช้แชร์ตำแหน่งกับสมาชิกในครอบครัวได้อย่างต่อเนื่อง
  • ภายในแอป 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 ของแอปซึ่งมีฟังก์ชันการทำงานที่จำกัดเนื่องจากแอปไม่มีสิทธิ์ที่จำเป็น ตัวอย่างสิ่งที่คุณทำได้มีดังนี้

    • แสดงข้อความในตำแหน่งที่ผลลัพธ์หรือข้อมูลของฟีเจอร์ควรจะปรากฏ
    • แสดงปุ่มอื่นที่มีไอคอนและสีข้อผิดพลาด
  • ใช้คำที่เฉพาะเจาะจง อย่าแสดงข้อความทั่วไป แต่ให้ระบุอย่างชัดเจนว่าฟีเจอร์ใดใช้งานไม่ได้เนื่องจากแอปของคุณไม่มีสิทธิ์ที่จำเป็น

  • อย่าบล็อกอินเทอร์เฟซผู้ใช้ กล่าวคือ อย่าแสดงข้อความเตือนแบบเต็มหน้าจอที่ทำให้ผู้ใช้ใช้แอปของคุณต่อไม่ได้เลย

  • ยอมรับความต้องการของผู้ใช้: แม้ว่าคุณจะสามารถสื่อสารกับผู้ใช้ได้ว่าฟังก์ชันการทำงานจะได้รับผลกระทบอย่างไรหากไม่ให้สิทธิ์เข้าถึงตามที่ขอ แต่ผู้ใช้ไม่ควรรู้สึกว่าถูกกดดันให้เปลี่ยนใจ การแจ้งให้ผู้ใช้ทราบว่าตนไม่ได้รับสิทธิ์เข้าถึงฟีเจอร์อย่างเต็มรูปแบบเมื่อเหมาะสม (เช่น เมื่อพยายามใช้ส่วนของแอปที่ถูกบล็อกโดยไม่ได้รับอนุญาต) เป็นสิ่งสำคัญ แต่การรบกวนให้พิจารณาใหม่ซ้ำๆ ถือเป็นการไม่เคารพตัวเลือกของผู้ใช้

การปฏิบัติตามหลักเกณฑ์เหล่านี้จะช่วยให้ผู้ใช้รู้สึกว่าตนเองมีอำนาจในการจัดการสิทธิ์เข้าถึงข้อมูลส่วนตัวในลักษณะที่สอดคล้องกับค่านิยมของตนเอง การไม่ยอมรับการตัดสินใจด้านความเป็นส่วนตัวของผู้ใช้ที่มองเห็นได้ชัดเจนจะทำลายความไว้วางใจและชื่อเสียงของแอปคุณ รวมถึงระบบนิเวศโดยรวม

นอกจากนี้ หากผู้ใช้แตะ "ปฏิเสธ" สิทธิ์หนึ่งๆ มากกว่า 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 คือชื่อของสิทธิ์ที่คุณต้องการรีเซ็ต

PERMISSION_NAME คือชื่อของสิทธิ์ที่คุณต้องการรีเซ็ต

หากต้องการดูรายการสิทธิ์ของแอป Android ทั้งหมด โปรดไปที่หน้าเอกสารอ้างอิง API สิทธิ์

สิทธิ์ครั้งเดียว

ตัวเลือกที่ชื่อว่า &quot;เฉพาะครั้งนี้&quot; เป็นปุ่มที่ 2 จาก 3 ปุ่มในกล่องโต้ตอบ
รูปที่ 2 กล่องโต้ตอบของระบบที่ปรากฏขึ้นเมื่อแอปขอสิทธิ์แบบครั้งเดียว

ตั้งแต่ Android 11 (ระดับ API 30) เป็นต้นไป เมื่อใดก็ตามที่แอปขอสิทธิ์ที่เกี่ยวข้องกับตำแหน่ง ไมโครโฟน หรือกล้อง กล่องโต้ตอบสิทธิ์ที่แสดงต่อผู้ใช้จะมีตัวเลือกชื่อ เฉพาะครั้งนี้ ดังที่แสดงในรูปที่ 2 หากผู้ใช้เลือกตัวเลือกนี้ในกล่องโต้ตอบ แอปของคุณจะได้รับสิทธิ์ครั้งเดียวแบบชั่วคราว

จากนั้นแอปจะเข้าถึงข้อมูลที่เกี่ยวข้องได้เป็นระยะเวลาหนึ่งซึ่งขึ้นอยู่กับลักษณะการทำงานของแอปและการดำเนินการของผู้ใช้ ดังนี้

  • แอปจะเข้าถึงข้อมูลได้ขณะที่กิจกรรมของแอปปรากฏอยู่
  • หากผู้ใช้ส่งแอปของคุณไปที่เบื้องหลัง แอปจะยังคงเข้าถึงข้อมูลได้เป็นระยะเวลาสั้นๆ
  • หากคุณเปิดบริการที่ทำงานอยู่เบื้องหน้าขณะที่กิจกรรมแสดงอยู่ และผู้ใช้ย้ายแอปไปที่เบื้องหลัง แอปจะยังคงเข้าถึงข้อมูลได้จนกว่าบริการที่ทำงานอยู่เบื้องหน้าจะหยุดทำงาน

กระบวนการของแอปจะสิ้นสุดลงเมื่อเพิกถอนสิทธิ์

หากผู้ใช้เพิกถอนสิทธิ์แบบครั้งเดียว เช่น ในการตั้งค่าระบบ แอปของคุณจะเข้าถึงข้อมูลไม่ได้ ไม่ว่าคุณจะเปิดบริการที่ทำงานอยู่เบื้องหน้าหรือไม่ก็ตาม เช่นเดียวกับสิทธิ์อื่นๆ หากผู้ใช้เพิกถอนสิทธิ์ครั้งเดียวของแอป กระบวนการของแอปจะสิ้นสุดลง

เมื่อผู้ใช้เปิดแอปของคุณในครั้งถัดไปและฟีเจอร์ในแอปขอเข้าถึงตำแหน่ง ไมโครโฟน หรือกล้อง ผู้ใช้จะได้รับข้อความแจ้งให้ให้สิทธิ์อีกครั้ง

รีเซ็ตสิทธิ์ที่ไม่ได้ใช้

Android มีวิธีรีเซ็ตสิทธิ์รันไทม์ที่ไม่ได้ใช้ให้กลับไปเป็นสถานะปฏิเสธตามค่าเริ่มต้นหลายวิธี ดังนี้

นำสิทธิ์เข้าถึงแอปออก

ใน 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 ที่สาธิตแนวทางปฏิบัติแนะนำด้านความเป็นส่วนตัวให้เสร็จสมบูรณ์ได้ด้วย