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

แอป 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()][12] เมธอดนี้จะแสดงผลเป็น PERMISSION_GRANTED หรือ PERMISSION_DENIED โดยขึ้นอยู่กับว่าแอปของคุณมีสิทธิ์หรือไม่

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

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

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

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

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

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

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

ขอสิทธิ์

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

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

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

หากต้องการอนุญาตให้ระบบจัดการรหัสคำขอที่เชื่อมโยงกับคำขอสิทธิ์ ให้เพิ่มทรัพยากร 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()][15]

กลุ่มสิทธิ์ที่เฉพาะเจาะจง

ข้อมูลโค้ดต่อไปนี้แสดงวิธีขอสิทธิ์โดยใช้รหัสคำขอ

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

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

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

ในขณะเดียวกัน แอปของคุณต้องเคารพการตัดสินใจของผู้ใช้ในการปฏิเสธที่จะให้สิทธิ์ ตั้งแต่ 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]
...

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

[12]: /reference/androidx/core/content/ContextCompat#checkSelfPermission(android.content.Context, java.lang.String)

[15]: /reference/androidx/core/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int) [16]: /reference/androidx/core/app/ActivityCompat#shouldShowRequestPermissionRationale(android.app.Activity, java.lang.String)