Intent และตัวกรอง Intent

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

  • การเริ่มกิจกรรม

    Activity แสดงหน้าจอเดียวในแอป คุณสามารถเริ่มอินสแตนซ์ใหม่ของ Activity โดยส่ง Intent ไปยัง startActivity() Intent จะอธิบายกิจกรรมที่จะเริ่มและมีข้อมูลที่จำเป็น

    หากต้องการรับผลลัพธ์จากกิจกรรมเมื่อกิจกรรมเสร็จสิ้น ให้เรียกใช้ startActivityForResult() กิจกรรมของคุณจะได้รับผลลัพธ์เป็นออบเจ็กต์ Intent แยกต่างหากใน Callback onActivityResult() ของกิจกรรม ดูข้อมูลเพิ่มเติมได้ที่คู่มือกิจกรรม

  • การเริ่มให้บริการ

    Service คือคอมโพเนนต์ที่ดำเนินการในเบื้องหลังโดยไม่มีอินเทอร์เฟซผู้ใช้ ใน Android 5.0 (ระดับ API 21) ขึ้นไป คุณสามารถเริ่มบริการด้วย JobScheduler ได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับ JobScheduler ได้ที่API-reference documentation

    สำหรับเวอร์ชันที่เก่ากว่า Android 5.0 (ระดับ API 21) คุณสามารถเริ่มบริการได้โดยใช้วิธีการของคลาส Service คุณสามารถเริ่มบริการเพื่อดำเนินการแบบครั้งเดียว (เช่น การดาวน์โหลดไฟล์) โดยส่ง Intent ไปยัง startService() Intent อธิบายบริการที่จะเริ่มและมีข้อมูลที่จำเป็น

    หากบริการได้รับการออกแบบด้วยอินเทอร์เฟซไคลเอ็นต์-เซิร์ฟเวอร์ คุณสามารถเชื่อมโยงกับบริการจากคอมโพเนนต์อื่นได้โดยส่ง Intent ไปยัง bindService() ดูข้อมูลเพิ่มเติมได้ในคู่มือบริการ

  • การส่งประกาศ

    การออกอากาศคือข้อความที่แอปใดๆ ก็รับได้ ระบบจะส่งการออกอากาศต่างๆ สำหรับเหตุการณ์ของระบบ เช่น เมื่อระบบเปิดเครื่องหรืออุปกรณ์เริ่มชาร์จ คุณสามารถส่งการออกอากาศไปยังแอปอื่นๆ ได้โดยส่ง Intent ไปยัง sendBroadcast() หรือ sendOrderedBroadcast()

ส่วนที่เหลือของหน้านี้จะอธิบายวิธีการทำงานของ Intent และวิธีใช้ ดูข้อมูลที่เกี่ยวข้องได้ที่การโต้ตอบกับแอปอื่นๆ และการแชร์เนื้อหา

ประเภท Intent

Intent มี 2 ประเภท ได้แก่

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

รูปที่ 1 แสดงวิธีใช้ Intent เมื่อเริ่มกิจกรรม เมื่อออบเจ็กต์ Intent ระบุชื่อคอมโพเนนต์กิจกรรมที่เฉพาะเจาะจงอย่างชัดแจ้ง ระบบจะเริ่มคอมโพเนนต์นั้นทันที

รูปที่ 1 วิธีที่ระบบส่ง Intent โดยนัยเพื่อเริ่มกิจกรรมอื่น: [1] กิจกรรม A สร้าง Intent พร้อมคำอธิบายการดำเนินการและส่งไปยัง startActivity() [2] ระบบ Android จะค้นหาแอปทั้งหมดเพื่อหาตัวกรอง Intent ที่ตรงกับ Intent เมื่อพบรายการที่ตรงกัน [3] ระบบจะเริ่มกิจกรรมการจับคู่ (กิจกรรม B) โดยเรียกใช้เมธอด onCreate() และส่ง Intent ไปให้

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

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

ข้อควรระวัง: เพื่อให้แอปปลอดภัย ให้ใช้ Intent ที่ชัดเจนเสมอเมื่อเริ่ม Service และอย่าประกาศตัวกรอง Intent สำหรับบริการของคุณ การใช้ Intent โดยนัยเพื่อเริ่มบริการเป็นอันตรายด้านความปลอดภัยเนื่องจากคุณไม่แน่ใจว่าบริการใดจะตอบสนองต่อ Intent และผู้ใช้ไม่เห็นว่าบริการใดเริ่มทำงาน ตั้งแต่ Android 5.0 (ระดับ API 21) เป็นต้นไป ระบบจะส่งข้อยกเว้นหากคุณเรียกใช้ bindService() ด้วย Intent แบบไม่เจาะจงปลายทาง

การสร้าง Intent

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

ข้อมูลหลักที่อยู่ใน Intent มีดังนี้

ชื่อคอมโพเนนต์
ชื่อคอมโพเนนต์ที่จะเริ่ม

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

หมายเหตุ: เมื่อเริ่ม Service ให้ระบุชื่อคอมโพเนนต์เสมอ ไม่เช่นนั้น คุณจะไม่แน่ใจว่าบริการใดจะตอบสนองต่อ Intent และผู้ใช้จะไม่เห็นว่าบริการใดเริ่มทำงาน

ช่องนี้ของ Intent เป็นออบเจ็กต์ ComponentName ซึ่งคุณระบุได้โดยใช้ชื่อคลาสที่สมบูรณ์ในตัวเองของคอมโพเนนต์เป้าหมาย รวมถึงชื่อแพ็กเกจของแอป เช่น com.example.ExampleActivity คุณสามารถตั้งชื่อคอมโพเนนต์ด้วย setComponent(), setClass(), setClassName() หรือด้วยคอนสตรัคเตอร์ Intent

การดำเนินการ
สตริงที่ระบุการดำเนินการทั่วไปที่จะทำ (เช่น view หรือ pick)

ในกรณีของ Intent การออกอากาศ นี่คือการดำเนินการที่เกิดขึ้นและกำลังรายงาน การดำเนินการจะเป็นตัวกำหนดโครงสร้างของ Intent ที่เหลือเป็นส่วนใหญ่ โดยเฉพาะข้อมูลที่อยู่ในข้อมูลและข้อมูลเพิ่มเติม

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

ACTION_VIEW
ใช้การดำเนินการนี้ใน Intent ที่มี startActivity() เมื่อคุณมีข้อมูลบางอย่างที่กิจกรรมสามารถแสดงต่อผู้ใช้ได้ เช่น รูปภาพที่จะดูในแอปแกลเลอรี หรือที่อยู่ที่จะดูในแอปแผนที่
ACTION_SEND
หรือที่เรียกว่า Intent แชร์ คุณควรใช้ Intent นี้กับ startActivity() เมื่อมีข้อมูลที่ผู้ใช้สามารถแชร์ผ่านแอปอื่นได้ เช่น แอปอีเมลหรือแอปการแชร์ผ่านโซเชียล

ดูค่าคงที่เพิ่มเติมที่กำหนดการดำเนินการทั่วไปได้ในข้อมูลอ้างอิงคลาส Intent การดำเนินการอื่นๆ จะกำหนดไว้ที่อื่นในเฟรมเวิร์ก Android เช่น ใน Settings สำหรับการดำเนินการที่เปิดหน้าจอเฉพาะในแอปการตั้งค่าของระบบ

คุณสามารถระบุการดำเนินการสำหรับ Intent ด้วย setAction() หรือด้วยตัวสร้าง Intent

หากคุณกำหนดการดำเนินการของคุณเอง โปรดตรวจสอบว่าได้ใส่ชื่อแพ็กเกจของแอปเป็นคำนำหน้า ดังที่แสดงในตัวอย่างต่อไปนี้

Kotlin

const val ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL"

Java

static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
ข้อมูล
URI (ออบเจ็กต์ Uri) ที่อ้างอิงข้อมูลที่จะดำเนินการและ/หรือประเภท MIME ของข้อมูลนั้น โดยทั่วไปแล้ว ประเภทของข้อมูลที่ระบุจะกำหนดโดยการดำเนินการของ Intent เช่น หากการดำเนินการคือ ACTION_EDIT ข้อมูลควรมี URI ของเอกสารที่จะแก้ไข

เมื่อสร้าง Intent สิ่งสำคัญคือต้องระบุประเภทข้อมูล (ประเภท MIME) นอกเหนือจาก URI ตัวอย่างเช่น กิจกรรมที่แสดงรูปภาพได้อาจเล่นไฟล์เสียงไม่ได้ แม้ว่ารูปแบบ URI จะคล้ายกันก็ตาม การระบุประเภท MIME ของข้อมูลจะช่วยให้ระบบ Android ค้นหาคอมโพเนนต์ที่ดีที่สุดเพื่อรับ Intent ของคุณ อย่างไรก็ตาม บางครั้งอาจอนุมานประเภท MIME ได้จาก URI โดยเฉพาะเมื่อข้อมูลเป็น URI content: URI content: บ่งชี้ว่าข้อมูลอยู่ในอุปกรณ์และควบคุมโดย ContentProvider ซึ่งทำให้ระบบมองเห็นประเภท MIME ของข้อมูล

หากต้องการตั้งค่าเฉพาะ URI ข้อมูล ให้เรียกใช้ setData() หากต้องการตั้งค่าเฉพาะประเภท MIME ให้เรียกใช้ setType() หากจำเป็น คุณสามารถตั้งค่าทั้ง 2 อย่างอย่างชัดเจนด้วย setDataAndType()

ข้อควรระวัง: หากต้องการตั้งค่าทั้ง URI และประเภท MIME อย่าเรียกใช้ setData() และ setType() เนื่องจากแต่ละเมธอดจะลบล้างค่าของอีกเมธอดหนึ่ง ใช้ setDataAndType() เพื่อตั้งค่าทั้ง URI และประเภท MIME เสมอ

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

ดูรายการหมวดหมู่ทั้งหมดได้ในคำอธิบายคลาส Intent

คุณระบุหมวดหมู่ได้ด้วย addCategory()

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

Extras
คู่คีย์-ค่าที่มีข้อมูลเพิ่มเติมที่จำเป็นต่อการดำเนินการตามคำขอ เช่นเดียวกับการดำเนินการบางอย่างที่ใช้ URI ข้อมูลบางประเภท การดำเนินการบางอย่างก็ใช้ข้อมูลเพิ่มเติมบางอย่างด้วย

คุณสามารถเพิ่มข้อมูลเพิ่มเติมด้วยเมธอด putExtra() ต่างๆ ซึ่งแต่ละเมธอดจะยอมรับพารามิเตอร์ 2 รายการ ได้แก่ ชื่อคีย์และค่า นอกจากนี้ คุณยังสร้างออบเจ็กต์ Bundle ที่มีข้อมูลเพิ่มเติมทั้งหมด แล้วแทรก Bundle ใน Intent ด้วย putExtras() ได้ด้วย

ตัวอย่างเช่น เมื่อสร้าง Intent เพื่อส่งอีเมลด้วย ACTION_SEND คุณสามารถระบุผู้รับ to ด้วยคีย์ EXTRA_EMAIL และระบุ subject ด้วยคีย์ EXTRA_SUBJECT

คลาส Intent จะระบุค่าคงที่ EXTRA_* หลายค่าสำหรับประเภทข้อมูลที่ได้มาตรฐาน หากต้องการประกาศคีย์เพิ่มเติมของคุณเอง (สำหรับ Intent ที่แอปของคุณได้รับ) โปรดตรวจสอบว่าได้ใส่ชื่อแพ็กเกจของแอปเป็นคำนำหน้า ดังที่แสดงในตัวอย่างต่อไปนี้

Kotlin

const val EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS"

Java

static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";

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

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

ดูข้อมูลเพิ่มเติมได้ที่เมธอด setFlags()

ตัวอย่างความตั้งใจที่ชัดเจน

Explicit Intent คือ Intent ที่คุณใช้เพื่อเปิดคอมโพเนนต์แอปที่เฉพาะเจาะจง เช่น กิจกรรมหรือบริการหนึ่งๆ ในแอป หากต้องการสร้าง Explicit Intent ให้กำหนดชื่อคอมโพเนนต์สำหรับออบเจ็กต์ Intent ส่วนพร็อพเพอร์ตี้ Intent อื่นๆ ทั้งหมดเป็นแบบไม่บังคับ

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

Kotlin

// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
val downloadIntent = Intent(this, DownloadService::class.java).apply {
    data = Uri.parse(fileUrl)
}
startService(downloadIntent)

Java

// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

เครื่องมือสร้าง Intent(Context, Class) จะจัดหา Context ของแอปและออบเจ็กต์ Class ของคอมโพเนนต์ ดังนั้น Intent นี้จึงเริ่มคลาส DownloadService ในแอปอย่างชัดเจน

ดูข้อมูลเพิ่มเติมเกี่ยวกับการสร้างและการเริ่มบริการได้ที่คู่มือบริการ

ตัวอย่าง Intent โดยนัย

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

เช่น หากคุณมีเนื้อหาที่ต้องการให้ผู้ใช้แชร์กับผู้อื่น ให้สร้าง Intent ที่มีการดำเนินการ ACTION_SEND และเพิ่มส่วนเสริมที่ระบุเนื้อหาที่จะแชร์ เมื่อคุณเรียกใช้ startActivity() ด้วย Intent ดังกล่าว ผู้ใช้จะเลือกแอปที่จะแชร์เนื้อหาได้

Kotlin

// Create the text message with a string.
val sendIntent = Intent().apply {
    action = Intent.ACTION_SEND
    putExtra(Intent.EXTRA_TEXT, textMessage)
    type = "text/plain"
}

// Try to invoke the intent.
try {
    startActivity(sendIntent)
} catch (e: ActivityNotFoundException) {
    // Define what your app should do if no activity can handle the intent.
}

Java

// Create the text message with a string.
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");

// Try to invoke the intent.
try {
    startActivity(sendIntent);
} catch (ActivityNotFoundException e) {
    // Define what your app should do if no activity can handle the intent.
}

เมื่อมีการเรียกใช้ startActivity() ระบบจะตรวจสอบแอปที่ติดตั้งไว้ทั้งหมดเพื่อพิจารณาว่าแอปใดสามารถจัดการ Intent ประเภทนี้ได้ (Intent ที่มีการดำเนินการ ACTION_SEND และมีข้อมูล "text/plain") หากมีแอปเดียวที่จัดการได้ แอปนั้นจะเปิดขึ้นทันทีและได้รับ Intent หากไม่มีแอปอื่นที่จัดการได้ แอปของคุณจะตรวจจับ ActivityNotFoundException ที่เกิดขึ้นได้ หากมีกิจกรรมหลายรายการที่ยอมรับ Intent ระบบจะแสดงกล่องโต้ตอบดังที่แสดงในรูปที่ 2 เพื่อให้ผู้ใช้เลือกแอปที่จะใช้ได้

ดูข้อมูลเพิ่มเติมเกี่ยวกับการเปิดแอปอื่นๆ ได้ในคำแนะนำเกี่ยวกับการส่งผู้ใช้ไปยังแอปอื่น

รูปที่ 2 กล่องโต้ตอบตัวเลือก

การบังคับใช้เครื่องมือเลือกแอป

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

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

หากต้องการแสดงตัวเลือก ให้สร้าง Intent โดยใช้ createChooser() และส่งไปยัง startActivity() ดังที่แสดงในตัวอย่างต่อไปนี้ ตัวอย่างนี้แสดงกล่องโต้ตอบที่มีรายการแอปที่ตอบสนองต่อ Intent ที่ส่งไปยังเมธอด createChooser() และใช้ข้อความที่ระบุเป็นชื่อกล่องโต้ตอบ

Kotlin

val sendIntent = Intent(Intent.ACTION_SEND)
...

// Always use string resources for UI text.
// This says something like "Share this photo with"
val title: String = resources.getString(R.string.chooser_title)
// Create intent to show the chooser dialog
val chooser: Intent = Intent.createChooser(sendIntent, title)

// Verify the original intent will resolve to at least one activity
if (sendIntent.resolveActivity(packageManager) != null) {
    startActivity(chooser)
}

Java

Intent sendIntent = new Intent(Intent.ACTION_SEND);
...

// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show the chooser dialog
Intent chooser = Intent.createChooser(sendIntent, title);

// Verify the original intent will resolve to at least one activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(chooser);
}

ตรวจจับการเปิดใช้ Intent ที่ไม่ปลอดภัย

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

หากแอปของคุณดำเนินการทั้ง 2 อย่างต่อไปนี้ ระบบจะตรวจพบการเปิด Intent ที่ไม่ปลอดภัย และเกิดการละเมิด StrictMode

  1. แอปของคุณแยกแพ็กเกจ Intent ที่ฝังอยู่จากข้อมูลเพิ่มเติมของ Intent ที่ส่งมา
  2. แอปของคุณจะเริ่มคอมโพเนนต์แอปทันทีโดยใช้ Intent ที่ฝังนั้น เช่น การส่ง Intent ไปยัง startActivity(), startService() หรือ bindService()

อ่านรายละเอียดเพิ่มเติมเกี่ยวกับวิธีระบุสถานการณ์นี้และทำการเปลี่ยนแปลงในแอปได้ที่บล็อกโพสต์เกี่ยวกับ Android Nesting Intents บน Medium

ตรวจสอบการเปิด Intent ที่ไม่ปลอดภัย

หากต้องการตรวจสอบการเปิด Intent ที่ไม่ปลอดภัยในแอป ให้เรียก detectUnsafeIntentLaunch() เมื่อกำหนดค่า VmPolicy ดังที่แสดงในข้อมูลโค้ดต่อไปนี้ หากแอปตรวจพบการละเมิด StrictMode คุณอาจต้องหยุดการดำเนินการของแอปเพื่อปกป้องข้อมูลที่อาจมีความละเอียดอ่อน

Kotlin

fun onCreate() {
    StrictMode.setVmPolicy(VmPolicy.Builder()
        // Other StrictMode checks that you've previously added.
        // ...
        .detectUnsafeIntentLaunch()
        .penaltyLog()
        // Consider also adding penaltyDeath()
        .build())
}

Java

protected void onCreate() {
    StrictMode.setVmPolicy(new VmPolicy.Builder()
        // Other StrictMode checks that you've previously added.
        // ...
        .detectUnsafeIntentLaunch()
        .penaltyLog()
        // Consider also adding penaltyDeath()
        .build());
}

ใช้ Intent อย่างมีความรับผิดชอบมากขึ้น

โปรดทำตามแนวทางปฏิบัติแนะนำต่อไปนี้เพื่อลดโอกาสที่จะเกิดการเปิด Intent ที่ไม่ปลอดภัยและการละเมิด StrictMode

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

อย่าส่งออกคอมโพเนนต์ของแอปโดยไม่จำเป็น ตัวอย่างเช่น หากคุณตั้งใจจะเปิดคอมโพเนนต์แอปโดยใช้ Intent ที่ฝังภายใน ให้ตั้งค่าแอตทริบิวต์ android:exported ของคอมโพเนนต์นั้นเป็น false

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

แผนภาพในรูปที่ 2 แสดงวิธีที่ระบบส่งต่อการควบคุมจากแอป (ไคลเอ็นต์) ของคุณไปยังแอป (บริการ) อื่น และกลับมายังแอปของคุณ

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

รูปที่ 2 แผนภาพการสื่อสารระหว่างแอปเมื่อใช้ Intent รอดำเนินการที่ซ้อนกัน

การรับ Intent โดยนัย

หากต้องการโฆษณา Intent โดยนัยที่แอปของคุณรับได้ ให้ประกาศตัวกรอง Intent อย่างน้อย 1 รายการสำหรับคอมโพเนนต์แอปแต่ละรายการด้วยองค์ประกอบ <intent-filter> ในไฟล์ Manifest ตัวกรอง Intent แต่ละรายการจะระบุประเภทของ Intent ที่ยอมรับโดยอิงตามการดำเนินการ ข้อมูล และหมวดหมู่ของ Intent ระบบจะส่ง Intent โดยนัยไปยังคอมโพเนนต์ของแอปก็ต่อเมื่อ Intent นั้นผ่านตัวกรอง Intent ตัวใดตัวหนึ่งของคุณเท่านั้น

หมายเหตุ: ระบบจะส่ง Intent ที่ชัดแจ้งไปยังเป้าหมายเสมอ ไม่ว่าคอมโพเนนต์จะประกาศตัวกรอง Intent ใดก็ตาม

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

ตัวกรอง Intent แต่ละรายการจะกำหนดโดยองค์ประกอบ <intent-filter> ในไฟล์ Manifest ของแอป ซึ่งฝังอยู่ในคอมโพเนนต์แอปที่เกี่ยวข้อง (เช่น องค์ประกอบ <activity>)

ในคอมโพเนนต์ของแอปแต่ละรายการที่มีองค์ประกอบ <intent-filter> ให้ตั้งค่า android:exported อย่างชัดเจน แอตทริบิวต์นี้ระบุว่าแอปอื่นๆ เข้าถึงคอมโพเนนต์ของแอปได้หรือไม่ ในบางสถานการณ์ เช่น กิจกรรมที่มีตัวกรอง Intent รวมหมวดหมู่ LAUNCHER การตั้งค่าแอตทริบิวต์นี้เป็น true จะมีประโยชน์ มิฉะนั้น การตั้งค่าแอตทริบิวต์นี้เป็น false จะปลอดภัยกว่า

คำเตือน: หากกิจกรรม บริการ หรือ Broadcast Receiver ในแอปใช้ตัวกรอง Intent และไม่ได้ตั้งค่า android:exported ไว้อย่างชัดเจน คุณจะติดตั้งแอปในอุปกรณ์ที่ใช้ Android 12 ขึ้นไปไม่ได้

ภายใน <intent-filter> คุณสามารถระบุประเภทของ Intent ที่จะยอมรับโดยใช้องค์ประกอบ 3 อย่างต่อไปนี้อย่างน้อย 1 รายการ

<action>
ประกาศการดำเนินการผ่าน Intent ที่ยอมรับในแอตทริบิวต์ name ค่าต้องเป็นค่าสตริงตามตัวอักษรของการดำเนินการ ไม่ใช่ค่าคงที่ของคลาส
<data>
ประกาศประเภทข้อมูลที่ยอมรับ โดยใช้แอตทริบิวต์อย่างน้อย 1 รายการที่ระบุแง่มุมต่างๆ ของ URI ข้อมูล (scheme, host, port, path) และประเภท MIME
<category>
ประกาศหมวดหมู่ Intent ที่ยอมรับในแอตทริบิวต์ name ค่าต้องเป็นค่าสตริงตามตัวอักษรของการดำเนินการ ไม่ใช่ค่าคงที่ของคลาส

หมายเหตุ: หากต้องการรับ Intent โดยนัย คุณต้องรวมหมวดหมู่ CATEGORY_DEFAULT ไว้ในตัวกรอง Intent เมธอด startActivity() และ startActivityForResult() จะถือว่า Intent ทั้งหมดประกาศหมวดหมู่ CATEGORY_DEFAULT หากคุณไม่ประกาศหมวดหมู่นี้ในตัวกรอง Intent จะไม่มี Intent โดยนัยที่แก้ไขเป็นกิจกรรมของคุณ

ตัวอย่างเช่น ต่อไปนี้คือการประกาศกิจกรรมที่มีตัวกรอง Intent เพื่อรับ Intent ACTION_SEND เมื่อประเภทข้อมูลเป็นข้อความ

<activity android:name="ShareActivity" android:exported="false">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
</activity>

คุณสามารถสร้างตัวกรองที่มี <action>, <data> หรือ <category> มากกว่า 1 รายการได้ หากใช้ คุณต้องแน่ใจว่าคอมโพเนนต์สามารถจัดการองค์ประกอบตัวกรองเหล่านั้นได้ทุกรูปแบบ

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

ระบบจะทดสอบ Intent โดยนัยกับตัวกรองโดยเปรียบเทียบ Intent กับองค์ประกอบทั้ง 3 รายการ Intent ต้องผ่านการทดสอบทั้ง 3 รายการจึงจะส่งไปยังคอมโพเนนต์ได้ หากไม่ตรงกันแม้แต่รายการเดียว ระบบ Android จะไม่ส่ง Intent ไปยังคอมโพเนนต์ อย่างไรก็ตาม เนื่องจากคอมโพเนนต์อาจมีตัวกรอง Intent หลายรายการ Intent ที่ไม่ผ่านตัวกรองรายการหนึ่งของคอมโพเนนต์อาจผ่านตัวกรองอีกรายการหนึ่งได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีที่ระบบแก้ไขความตั้งใจได้ในส่วนด้านล่างเกี่ยวกับการแก้ไขความตั้งใจ

ข้อควรระวัง: การใช้ตัวกรอง Intent ไม่ใช่วิธีที่ปลอดภัยในการป้องกันไม่ให้แอปอื่นๆ เริ่มคอมโพเนนต์ของคุณ แม้ว่าตัวกรอง Intent จะจำกัดคอมโพเนนต์ให้ตอบสนองต่อ Intent โดยนัยบางประเภทเท่านั้น แต่แอปอื่นอาจเริ่มคอมโพเนนต์แอปของคุณได้โดยใช้ Intent โดยชัดแจ้งหากนักพัฒนาแอประบุชื่อคอมโพเนนต์ของคุณ หากจำเป็นต้องให้เฉพาะแอปของคุณเองเริ่มคอมโพเนนต์ของคุณได้ อย่าประกาศตัวกรอง Intent ในไฟล์ Manifest แต่ให้ตั้งค่าแอตทริบิวต์ exported เป็น "false" สำหรับคอมโพเนนต์นั้นแทน

ในทำนองเดียวกัน เพื่อหลีกเลี่ยงการเรียกใช้ Service ของแอปอื่นโดยไม่ตั้งใจ ให้ใช้ Intent ที่ชัดเจนเพื่อเริ่มบริการของคุณเองเสมอ

หมายเหตุ: สำหรับกิจกรรมทั้งหมด คุณต้องประกาศตัวกรอง Intent ในไฟล์ Manifest อย่างไรก็ตาม คุณสามารถลงทะเบียนตัวกรองสำหรับ Broadcast Receiver แบบไดนามิกได้โดยเรียกใช้ registerReceiver() จากนั้นคุณจะยกเลิกการลงทะเบียนอุปกรณ์รับสัญญาณกับ unregisterReceiver() ได้ การทำเช่นนี้จะช่วยให้แอปของคุณฟังการออกอากาศที่เฉพาะเจาะจงได้ในช่วงเวลาที่ระบุเท่านั้นขณะที่แอปทำงานอยู่

ตัวอย่างของตัวกรอง

ตัวอย่างจากไฟล์ Manifest ของแอปการแชร์ผ่านโซเชียลต่อไปนี้จะแสดงลักษณะการทำงานบางอย่างของตัวกรอง Intent

<activity android:name="MainActivity" android:exported="true">
    <!-- This activity is the main entry, should appear in app launcher -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ShareActivity" android:exported="false">
    <!-- This activity handles "SEND" actions with text data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <action android:name="android.intent.action.SEND_MULTIPLE"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
        <data android:mimeType="image/*"/>
        <data android:mimeType="video/*"/>
    </intent-filter>
</activity>

กิจกรรมแรก MainActivity คือจุดแรกเข้าหลักของแอป ซึ่งเป็นกิจกรรมที่เปิดขึ้นเมื่อผู้ใช้เปิดแอปครั้งแรกด้วยไอคอน Launcher

  • การดำเนินการ ACTION_MAIN บ่งชี้ว่านี่คือจุดแรกเข้าหลักและไม่คาดหวังข้อมูล Intent ใดๆ
  • หมวดหมู่ CATEGORY_LAUNCHER บ่งชี้ว่าควรวางไอคอนของกิจกรรมนี้ไว้ในตัวเปิดแอปของระบบ หากองค์ประกอบ <activity> ไม่ได้ระบุไอคอนที่มี icon ระบบจะใช้ไอคอนจากองค์ประกอบ <application>

ทั้ง 2 รายการนี้ต้องจับคู่กันเพื่อให้กิจกรรมปรากฏในตัวเปิดแอป

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

หมายเหตุ: ประเภท MIME application/vnd.google.panorama360+jpg เป็นประเภทข้อมูลพิเศษที่ระบุรูปภาพพาโนรามา ซึ่งคุณจัดการได้ด้วย Google panorama API

จับคู่ Intent กับตัวกรอง Intent ของแอปอื่นๆ

หากแอปอื่นกำหนดเป้าหมายเป็น Android 13 (API ระดับ 33) ขึ้นไป แอปนั้นจะจัดการ Intent ของแอปคุณได้ก็ต่อเมื่อ Intent ของคุณตรงกับการดำเนินการและหมวดหมู่ขององค์ประกอบ <intent-filter> ในแอปอื่นนั้น หากระบบไม่พบรายการที่ตรงกัน ระบบจะแสดง ActivityNotFoundException แอปที่ส่งต้องจัดการข้อยกเว้นนี้

ในทำนองเดียวกัน หากคุณอัปเดตแอปให้กำหนดเป้าหมายเป็น Android 13 ขึ้นไป ระบบจะนำส่ง Intent ทั้งหมดที่มาจากแอปภายนอกไปยังคอมโพเนนต์ที่ส่งออกของแอปเฉพาะในกรณีที่ Intent ดังกล่าวตรงกับการดำเนินการและหมวดหมู่ขององค์ประกอบ <intent-filter> ที่แอปประกาศไว้ ลักษณะการทำงานนี้จะเกิดขึ้นไม่ว่าเวอร์ชัน SDK เป้าหมายของแอปที่ส่งจะเป็นเวอร์ชันใดก็ตาม

ระบบจะไม่บังคับใช้การจับคู่ความตั้งใจในกรณีต่อไปนี้

  • Intent ที่ส่งไปยังคอมโพเนนต์ที่ไม่ได้ประกาศตัวกรอง Intent ใดๆ
  • Intent ที่มาจากภายในแอปเดียวกัน
  • Intent ที่มาจากระบบ กล่าวคือ Intent ที่ส่งจาก "UID ของระบบ" (uid=1000) แอประบบรวมถึง system_server และแอปที่ตั้งค่า android:sharedUserId เป็น android.uid.system
  • Intent ที่มาจากรูท

ดูข้อมูลเพิ่มเติมเกี่ยวกับการจับคู่ความตั้งใจ

การใช้ Intent รอดำเนินการ

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

กรณีการใช้งานหลักสำหรับ Intent รอดำเนินการมีดังนี้

  • การประกาศ Intent ที่จะดำเนินการเมื่อผู้ใช้ดำเนินการกับการแจ้งเตือน (NotificationManager ของระบบ Android จะเรียกใช้ Intent)
  • ประกาศ Intent ที่จะดำเนินการเมื่อผู้ใช้ดำเนินการกับวิดเจ็ตแอป (แอปหน้าจอหลักจะเรียกใช้ Intent)
  • การประกาศ Intent ที่จะดำเนินการในเวลาที่ระบุในอนาคต (AlarmManager ของระบบ Android จะดำเนินการ Intent)

เช่นเดียวกับที่ออบเจ็กต์ Intent แต่ละรายการได้รับการออกแบบมาให้จัดการโดยคอมโพเนนต์แอปประเภทที่เฉพาะเจาะจง (Activity, Service หรือ BroadcastReceiver) PendingIntent ก็ต้องสร้างขึ้นโดยคำนึงถึงสิ่งเดียวกันนี้ด้วย เมื่อใช้ Intent รอดำเนินการ แอปจะไม่เรียกใช้ Intent ด้วยการเรียก เช่น startActivity() แต่คุณต้องประกาศประเภทคอมโพเนนต์ที่ต้องการเมื่อสร้าง PendingIntent โดยเรียกใช้เมธอดผู้สร้างที่เกี่ยวข้อง ดังนี้

เว้นแต่ว่าแอปของคุณจะรับ Intent รอดำเนินการจากแอปอื่นๆ วิธีการข้างต้นในการสร้าง PendingIntent อาจเป็นเพียงวิธีการเดียวของ PendingIntent ที่คุณจำเป็นต้องใช้

แต่ละเมธอดจะใช้ Context ของแอปปัจจุบัน, Intent ที่คุณต้องการรวม และแฟล็กอย่างน้อย 1 รายการที่ระบุวิธีใช้ Intent (เช่น Intent สามารถใช้ได้มากกว่า 1 ครั้งหรือไม่)

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

ระบุความสามารถในการเปลี่ยนแปลงได้

หากแอปกำหนดเป้าหมายเป็น Android 12 ขึ้นไป คุณต้องระบุความสามารถในการเปลี่ยนแปลงได้ของออบเจ็กต์ PendingIntent แต่ละรายการที่แอปสร้างขึ้น หากต้องการประกาศว่าออบเจ็กต์ PendingIntent ที่ระบุสามารถเปลี่ยนแปลงได้หรือเปลี่ยนแปลงไม่ได้ ให้ใช้แฟล็ก PendingIntent.FLAG_MUTABLE หรือ PendingIntent.FLAG_IMMUTABLE ตามลำดับ

หากแอปพยายามสร้างออบเจ็กต์ PendingIntent โดยไม่ตั้งค่าแฟล็กความสามารถในการเปลี่ยนแปลงได้ ระบบจะแสดง IllegalArgumentException และข้อความต่อไปนี้จะปรากฏใน Logcat

PACKAGE_NAME: Targeting S+ (version 31 and above) requires that one of \
FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.

Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if \
some functionality depends on the PendingIntent being mutable, e.g. if \
it needs to be used with inline replies or bubbles.

สร้าง Intent รอดำเนินการที่เปลี่ยนแปลงไม่ได้เมื่อเป็นไปได้

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

Kotlin

val pendingIntent = PendingIntent.getActivity(applicationContext,
        REQUEST_CODE, intent,
        /* flags */ PendingIntent.FLAG_IMMUTABLE)

Java

PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(),
        REQUEST_CODE, intent,
        /* flags */ PendingIntent.FLAG_IMMUTABLE);

อย่างไรก็ตาม Use Case บางอย่างต้องใช้ออบเจ็กต์ PendingIntent ที่เปลี่ยนแปลงได้แทน ดังนี้

  • รองรับการดำเนินการตอบกลับโดยตรงในการแจ้งเตือน การตอบกลับโดยตรงต้องมีการเปลี่ยนแปลงข้อมูลคลิปในออบเจ็กต์ PendingIntent ที่เชื่อมโยงกับการตอบกลับ โดยปกติแล้ว คุณจะขอการเปลี่ยนแปลงนี้โดยส่ง FILL_IN_CLIP_DATA เป็นแฟล็กไปยังเมธอด fillIn()
  • การเชื่อมโยงการแจ้งเตือนกับเฟรมเวิร์ก Android Auto โดยใช้อินสแตนซ์ของ CarAppExtender
  • การวางการสนทนาในบับเบิลโดยใช้อินสแตนซ์ของ PendingIntent ออบเจ็กต์ PendingIntent ที่เปลี่ยนแปลงได้ช่วยให้ระบบใช้แฟล็กที่ถูกต้องได้ เช่น FLAG_ACTIVITY_MULTIPLE_TASK และ FLAG_ACTIVITY_NEW_DOCUMENT
  • การขอข้อมูลตำแหน่งของอุปกรณ์โดยการเรียกใช้ requestLocationUpdates() หรือ API ที่คล้ายกัน ออบเจ็กต์ PendingIntent ที่เปลี่ยนแปลงได้ช่วยให้ระบบเพิ่ม Intent Extra ที่แสดงเหตุการณ์ในวงจรของตำแหน่งได้ เหตุการณ์เหล่านี้รวมถึงการเปลี่ยนแปลงสถานที่ตั้งและการที่ผู้ให้บริการพร้อมให้บริการ
  • การตั้งเวลาปลุกโดยใช้ AlarmManager ออบเจ็กต์ PendingIntent ที่เปลี่ยนแปลงได้ช่วยให้ระบบเพิ่ม Intent Extra EXTRA_ALARM_COUNT ได้ ข้อมูลเพิ่มเติมนี้แสดงจำนวนครั้งที่มีการทริกเกอร์การปลุกซ้ำ เมื่อมีข้อมูลเพิ่มเติมนี้ Intent จะแจ้งแอปได้อย่างถูกต้องว่ามีการทริกเกอร์การปลุกซ้ำหลายครั้งหรือไม่ เช่น เมื่ออุปกรณ์อยู่ในโหมดสลีป

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

ใช้ Intent ที่ชัดเจนภายใน Intent รอดำเนินการ

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

  1. ตรวจสอบว่ามีการตั้งค่าฟิลด์การดำเนินการ แพ็กเกจ และคอมโพเนนต์ของ Intent พื้นฐาน
  2. ใช้ FLAG_IMMUTABLE ซึ่งเพิ่มเข้ามาใน Android 6.0 (ระดับ API 23) เพื่อสร้าง Intent รอดำเนินการ แฟล็กนี้ช่วยป้องกันไม่ให้แอปที่ได้รับ PendingIntent ทำการเติมพร็อพเพอร์ตี้ที่ไม่ได้สร้างไว้ หาก minSdkVersion ของแอปเป็น 22 หรือต่ำกว่า คุณสามารถระบุความปลอดภัยและความเข้ากันได้ร่วมกันโดยใช้โค้ดต่อไปนี้

    if (Build.VERSION.SDK_INT >= 23) {
      // Create a PendingIntent using FLAG_IMMUTABLE.
    } else {
      // Existing code that creates a PendingIntent.
    }

การแปลง Intent

เมื่อระบบได้รับ Intent โดยนัยเพื่อเริ่มกิจกรรม ระบบจะค้นหากิจกรรมที่ดีที่สุดสำหรับ Intent นั้นโดยเปรียบเทียบกับตัวกรอง Intent โดยอิงตาม 3 ด้านต่อไปนี้

  • เริ่มถ่ายเลย
  • ข้อมูล (ทั้ง URI และประเภทข้อมูล)
  • หมวดหมู่

ส่วนต่อไปนี้อธิบายวิธีจับคู่ Intent กับคอมโพเนนต์ที่เหมาะสมตามการประกาศตัวกรอง Intent ในไฟล์ Manifest ของแอป

การทดสอบการดำเนินการ

หากต้องการระบุการดำเนินการผ่าน Intent ที่ยอมรับ ตัวกรอง Intent สามารถประกาศองค์ประกอบ <action> ได้ตั้งแต่ 0 รายการขึ้นไป ดังที่แสดงในตัวอย่างต่อไปนี้

<intent-filter>
    <action android:name="android.intent.action.EDIT" />
    <action android:name="android.intent.action.VIEW" />
    ...
</intent-filter>

หากต้องการผ่านตัวกรองนี้ การดำเนินการที่ระบุใน Intent ต้องตรงกับการดำเนินการรายการใดรายการหนึ่งที่แสดงในตัวกรอง

หากตัวกรองไม่แสดงการดำเนินการใดๆ ก็จะไม่มี Intent ที่ตรงกัน ดังนั้น Intent ทั้งหมดจึงไม่ผ่านการทดสอบ อย่างไรก็ตาม หาก Intent ไม่ได้ระบุการดำเนินการใดๆ ก็จะผ่านการทดสอบตราบใดที่ตัวกรองมีการดำเนินการอย่างน้อย 1 รายการ

การทดสอบหมวดหมู่

หากต้องการระบุหมวดหมู่ Intent ที่ยอมรับ ตัวกรอง Intent สามารถประกาศองค์ประกอบ <category> ได้ตั้งแต่ 0 รายการขึ้นไป ดังที่แสดงในตัวอย่างต่อไปนี้

<intent-filter>
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    ...
</intent-filter>

เพื่อให้ Intent ผ่านการทดสอบหมวดหมู่ ทุกหมวดหมู่ใน Intent ต้องตรงกับหมวดหมู่ในตัวกรอง แต่ไม่จำเป็นต้องทำในทางกลับกัน ตัวกรอง Intent อาจประกาศหมวดหมู่มากกว่าที่ระบุไว้ใน Intent และ Intent ก็ยังคงผ่าน ดังนั้น Intent ที่ไม่มีหมวดหมู่จะผ่านการทดสอบนี้เสมอ ไม่ว่าจะมีการประกาศหมวดหมู่ใดในตัวกรองก็ตาม

หมายเหตุ: Android จะใช้หมวดหมู่ CATEGORY_DEFAULT กับ Intent โดยนัยทั้งหมดที่ส่งไปยัง startActivity() และ startActivityForResult() โดยอัตโนมัติ หากต้องการให้กิจกรรมได้รับ Intent โดยนัย กิจกรรมนั้นต้องมีหมวดหมู่สำหรับ "android.intent.category.DEFAULT" ในตัวกรอง Intent ดังที่แสดงในตัวอย่าง <intent-filter> ก่อนหน้านี้

การทดสอบข้อมูล

หากต้องการระบุข้อมูล Intent ที่ยอมรับ ตัวกรอง Intent สามารถประกาศองค์ประกอบ <data> ได้ตั้งแต่ 0 รายการขึ้นไป ดังที่แสดงในตัวอย่างต่อไปนี้

<intent-filter>
    <data android:mimeType="video/mpeg" android:scheme="http" ... />
    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
    ...
</intent-filter>

องค์ประกอบ <data> แต่ละรายการสามารถระบุโครงสร้าง URI และประเภทข้อมูล (ประเภทสื่อ MIME) ได้ แต่ละส่วนของ URI เป็นแอตทริบิวต์แยกกัน ได้แก่ scheme, host, port และ path

<scheme>://<host>:<port>/<path>

ตัวอย่างต่อไปนี้แสดงค่าที่เป็นไปได้สำหรับแอตทริบิวต์เหล่านี้

content://com.example.project:200/folder/subfolder/etc

ใน URI นี้ รูปแบบคือ content, โฮสต์คือ com.example.project, พอร์ตคือ 200 และเส้นทางคือ folder/subfolder/etc

แอตทริบิวต์แต่ละรายการเหล่านี้ไม่บังคับในองค์ประกอบ <data> แต่มีการขึ้นต่อกันแบบเชิงเส้น ดังนี้

  • หากไม่ได้ระบุรูปแบบ ระบบจะไม่สนใจโฮสต์
  • หากไม่ได้ระบุโฮสต์ ระบบจะไม่สนใจพอร์ต
  • หากไม่ได้ระบุทั้งรูปแบบและโฮสต์ ระบบจะไม่สนใจเส้นทาง

เมื่อเปรียบเทียบ URI ใน Intent กับข้อกำหนดเฉพาะของ URI ในตัวกรอง ระบบจะเปรียบเทียบเฉพาะส่วนของ URI ที่รวมอยู่ในตัวกรองเท่านั้น เช่น

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

หมายเหตุ: ข้อกำหนดของเส้นทางสามารถมีเครื่องหมายดอกจัน (*) เพื่อกำหนดให้จับคู่ชื่อเส้นทางเพียงบางส่วนเท่านั้น

การทดสอบข้อมูลจะเปรียบเทียบทั้ง URI และประเภท MIME ใน Intent กับ URI และประเภท MIME ที่ระบุในตัวกรอง กฎมีดังนี้

  1. Intent ที่ไม่มีทั้ง URI และประเภท MIME จะผ่านการทดสอบก็ต่อเมื่อตัวกรองไม่ได้ระบุ URI หรือประเภท MIME ใดๆ
  2. Intent ที่มี URI แต่ไม่มีประเภท MIME (ไม่ว่าจะระบุอย่างชัดเจนหรืออนุมานได้จาก URI) จะผ่านการทดสอบก็ต่อเมื่อ URI ตรงกับรูปแบบ URI ของตัวกรอง และตัวกรองไม่ได้ระบุประเภท MIME เช่นกัน
  3. Intent ที่มีประเภท MIME แต่ไม่มี URI จะผ่านการทดสอบก็ต่อเมื่อตัวกรองแสดงประเภท MIME เดียวกันและไม่ได้ระบุรูปแบบ URI
  4. Intent ที่มีทั้ง URI และประเภท MIME (ไม่ว่าจะระบุอย่างชัดเจนหรืออนุมานได้จาก URI) จะผ่านส่วนประเภท MIME ของการทดสอบก็ต่อเมื่อประเภทนั้นตรงกับประเภทที่ระบุไว้ในตัวกรอง โดยจะผ่านการทดสอบส่วน URI หาก URI ตรงกับ URI ในตัวกรอง หรือหากมี URI content: หรือ file: และตัวกรองไม่ได้ระบุ URI กล่าวคือ ระบบจะถือว่าคอมโพเนนต์รองรับข้อมูล content: และ file: หากตัวกรองแสดงเฉพาะประเภท MIME

หมายเหตุ: หาก Intent ระบุ URI หรือประเภท MIME การทดสอบข้อมูลจะล้มเหลวหากไม่มีองค์ประกอบ <data> ใน <intent-filter>

กฎข้อสุดท้ายนี้ (กฎ (ง)) แสดงถึงความคาดหวังว่าคอมโพเนนต์จะสามารถรับข้อมูลในเครื่องจากไฟล์หรือ Content Provider ได้ ดังนั้น ตัวกรองจึงแสดงเฉพาะประเภทข้อมูลได้ และไม่จำเป็นต้องระบุชื่อรูปแบบ content: และ file: อย่างชัดเจน ตัวอย่างต่อไปนี้แสดงกรณีทั่วไปที่องค์ประกอบ <data> บอก Android ว่าคอมโพเนนต์สามารถรับข้อมูลรูปภาพจาก Content Provider และแสดงข้อมูลดังกล่าวได้

<intent-filter>
    <data android:mimeType="image/*" />
    ...
</intent-filter>

ตัวกรองที่ระบุประเภทข้อมูลแต่ไม่ระบุ URI อาจเป็นตัวกรองที่พบบ่อยที่สุด เนื่องจากข้อมูลที่มีอยู่ส่วนใหญ่จะได้รับการจ่ายโดย Content Provider

การกำหนดค่าที่พบบ่อยอีกอย่างหนึ่งคือตัวกรองที่มีรูปแบบและประเภทข้อมูล ตัวอย่างเช่น องค์ประกอบ <data> เช่น องค์ประกอบต่อไปนี้จะบอก Android ว่าคอมโพเนนต์สามารถดึงข้อมูลวิดีโอจากเครือข่ายเพื่อดำเนินการได้

<intent-filter>
    <data android:scheme="http" android:mimeType="video/*" />
    ...
</intent-filter>

การจับคู่ความตั้งใจ

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

แอปพลิเคชันของคุณสามารถใช้การจับคู่ Intent ในลักษณะที่คล้ายกับสิ่งที่แอป Home ทำ PackageManager มีชุดเมธอด query...() ที่แสดงผลคอมโพเนนต์ทั้งหมดที่ยอมรับ Intent หนึ่งๆ ได้ และชุดเมธอด resolve...() ที่คล้ายกันซึ่งกำหนดคอมโพเนนต์ที่ดีที่สุดในการตอบสนองต่อ Intent ตัวอย่างเช่น queryIntentActivities() จะแสดงรายการกิจกรรมทั้งหมดที่สามารถดำเนินการตาม Intent ที่ส่งเป็นอาร์กิวเมนต์ และ queryIntentServices() จะแสดงรายการบริการที่คล้ายกัน ทั้ง 2 วิธีไม่ได้เปิดใช้งานคอมโพเนนต์ แต่จะแสดงเฉพาะคอมโพเนนต์ที่ตอบสนองได้ มีเมธอดที่คล้ายกันคือ queryBroadcastReceivers() สำหรับ Broadcast Receiver