ActivityResultLauncher เป็นส่วนประกอบของ Android Activity Result API ที่ถูกนำเสนอในเวอร์ชัน Activity 1.2.0 ของไลบรารี androidx.activity มันแทนที่เมธอด startActivityForResult และ onActivityResult ที่เลิกใช้แล้ว ซึ่งเป็นส่วนหนึ่งของ Android SDK ตั้งแต่เริ่มก่อตั้ง ตาม Android Developers (2024) API ใหม่ช่วยขจัดปัญหาการผูกพันอย่างแน่นแฟ้นกับ Activity และการขาดความปลอดภัยของชนิดข้อมูล ActivityResultLauncher จะถูกลงทะเบียนล่วงหน้าและใช้ Contract สำหรับการกำหนดชนิดข้อมูลอินพุตและเอาต์พุตอย่างเข้มงวด
ประเด็นสำคัญ
ActivityResultLauncher เป็นคลาสจากแพ็คเกจ androidx.activity.result ที่ให้กลไกที่ปลอดภัยด้านชนิดข้อมูลสำหรับการเริ่ม Activity และรับผลลัพธ์ Launcher ถูกสร้างขึ้นผ่านเมธอด registerForActivityResult ซึ่งรับพารามิเตอร์สองตัว: Contract (อธิบายชนิดอินพุตและเอาต์พุต) และ ActivityResultCallback (ตัวจัดการผลลัพธ์) หลังจากการลงทะเบียน launcher พร้อมที่จะถูกเรียกผ่านเมธอด launch
ความแตกต่างหลัก จาก API เก่า คือการแยกการลงทะเบียนและการเริ่มทำงาน การลงทะเบียนจะดำเนินการในขั้นตอนการเริ่มต้น (Activity.onCreate หรือ Fragment.onCreate) callback จะถูกผูกกับ launcher เพียงครั้งเดียวและรับประกันว่าจะทำงานเมื่อผลลัพธ์กลับมา ซึ่งช่วยขจัดปัญหาที่ onActivityResult ทำงานในลำดับที่ไม่คาดคิดหรือบน Activity ที่ถูกทำลาย
ActivityResultLauncher รองรับ ทุกสถานการณ์ ที่เคยถูกจัดการผ่าน onActivityResult: การเริ่มกล้อง แกลเลอรี การขอรายชื่อติดต่อ สิทธิ์ และ Activity ที่กำหนดเอง นอกจากนี้ API ยังสามารถขยายได้: นักพัฒนาสามารถสร้าง Contract ที่กำหนดเองสำหรับสถานการณ์การแลกเปลี่ยนข้อมูลเฉพาะระหว่าง Activity
startActivityForResult เป็นส่วนหนึ่งของ Android SDK ตั้งแต่ API Level 1 (2008) และยังคงเป็นวิธีหลักในการรับผลลัพธ์จาก Activity มานานกว่า 12 ปี อย่างไรก็ตาม เมธอดนี้มีข้อบกพร่องพื้นฐานที่ Google แก้ไขใน Activity Result API มาดูปัญหาหลักและวิธีที่ API ใหม่แก้ปัญหาเหล่านี้กัน
เมธอด startActivityForResult ผูกติดกับ Activity และ Fragment ผ่าน requestCode — จำนวนเต็มตามอำเภอใจที่ส่งไปยัง onActivityResult นักพัฒนาต้องจับคู่รหัสกับการทำงานที่เริ่มต้นด้วยตนเอง ซึ่งนำไปสู่ข้อผิดพลาดในการนำโค้ดกลับมาใช้ใหม่และการสืบทอด ActivityResultLauncher กำจัด requestCode อย่างสมบูรณ์: callback จะถูกผูกกับ launcher เฉพาะในเวลาที่ลงทะเบียนและจะถูกเรียกสำหรับ launcher นั้นเท่านั้น
ระหว่าง การเปลี่ยนแปลงการกำหนดค่า (การหมุนหน้าจอ การเปลี่ยนภาษา) Activity ถูกสร้างใหม่และ onActivityResult อาจไม่ทำงาน — callback สูญหาย Activity Result API จะบันทึกและกู้คืนสถานะ launcher โดยอัตโนมัติผ่าน SavedStateRegistry เพื่อรับประกันว่าจะได้รับผลลัพธ์แม้หลังจากการสร้าง Activity ใหม่
API เก่าส่ง ผลลัพธ์ ผ่าน Intent พร้อม Bundle ซึ่งคีย์และชนิดข้อมูลไม่ถูกตรวจสอบโดยคอมไพเลอร์ Activity Result API ใช้ Contract — อินเทอร์เฟซทั่วไปที่กำหนดชนิดข้อมูลอินพุต (I) และชนิดผลลัพธ์ (O) ข้อผิดพลาดประเภทไม่ตรงกันจะถูกตรวจจับในเวลาคอมไพล์ ไม่ใช่เวลารันไทม์
| คุณลักษณะ | startActivityForResult | ActivityResultLauncher |
|---|---|---|
| RequestCode | ต้องการการจัดการด้วยตนเอง | อัตโนมัติ ไม่ต้องการ |
| ความปลอดภัยของชนิด | ไม่มี | Contract ทั่วไป |
| การบันทึกเมื่อหมุน | สูญหาย | SavedStateRegistry |
| API ขั้นต่ำ | API Level 1 | Activity 1.2.0 |
| การใช้งานใน Compose | ไม่รองรับ | rememberLauncherForActivityResult |
Contract คืออินเทอร์เฟซ ActivityResultContract<I, O> ซึ่งกำหนดวิธีเริ่ม Activity และวิธีตีความผลลัพธ์ Google มีชุดคอนแทรกต์ในตัวสำหรับสถานการณ์ทั่วไปที่ครอบคลุมความต้องการของนักพัฒนาส่วนใหญ่
StartIntentSenderForResult — คอนแทรกต์พื้นฐานสำหรับการเริ่ม IntentSender ใช้ในสถานการณ์ของระบบ เช่น เมื่ออนุญาตผ่าน Google Sign-In หรือชำระเงินผ่าน Google Pay พารามิเตอร์อินพุตคือ PendingIntent เอาต์พุตคือ ActivityResult พร้อมรหัสและ Intent
RequestMultiplePermissions — คอนแทรกต์สำหรับขอสิทธิ์หลายรายการพร้อมกันบน Android 6.0+ พารามิเตอร์อินพุตคืออาร์เรย์ String ที่มีชื่อสิทธิ์ เอาต์พุตคือ Map<String, Boolean> พร้อมผลลัพธ์ของแต่ละคำขอ ก่อนหน้านี้ต้องมีการแยกวิเคราะห์ด้วยตนเองใน onRequestPermissionsResult พร้อมการจับคู่รหัสคำขอ
TakePicture — คอนแทรกต์สำหรับถ่ายภาพผ่านกล้องของระบบ อินพุตคือ Uri ที่จะบันทึกภาพ เอาต์พุตคือ Boolean (สำเร็จ) TakeVideo ทำงานคล้ายกันกับวิดีโอ คอนแทรกต์เหล่านี้แทนที่ MediaStore.ACTION_IMAGE_CAPTURE ที่เลิกใช้แล้วซึ่งมีพฤติกรรมไม่เสถียรบนอุปกรณ์ต่างกัน
GetContent — คอนแทรกต์สำหรับเลือกเนื้อหาผ่านตัวเลือกระบบ อินพุตคือชนิด MIME (เช่น image/*) เอาต์พุตคือ Uri ของไฟล์ที่เลือก OpenDocument แตกต่างตรงที่รองรับการเลือกหลายรายการและการกรองตามชนิดเอกสาร คอนแทรกต์ทั้งสองทำงานผ่าน SAF (Storage Access Framework)
CreateDocument — คอนแทรกต์สำหรับสร้างเอกสารใหม่ผ่านไดอะล็อกระบบ ผู้ใช้เลือกชื่อและโฟลเดอร์ ระบบส่งคืน Uri สำหรับการเขียน OpenDocumentTree ให้การเข้าถึงไดเรกทอรีทั้งหมด — ผู้ใช้เลือกโฟลเดอร์ และแอปจะได้รับ tree-uri สำหรับอ่านและเขียนไฟล์ทั้งหมดภายใน
รูปแบบพื้นฐาน สำหรับการใช้ ActivityResultLauncher ใน Android ดั้งเดิมประกอบด้วยสองขั้นตอน: การลงทะเบียนผ่าน registerForActivityResult ในขั้นตอนการเริ่มต้นและการเรียก launch เพื่อตอบสนองต่อการกระทำของผู้ใช้ มาดูตัวอย่างทั่วไปของการเลือกรูปภาพจากแกลเลอรี
ลงทะเบียน launcher ใน onCreate ของ Activity — ซึ่งรับประกันว่า callback พร้อมก่อนการเรียกใดๆ ที่อาจเกิดขึ้น อย่าลงทะเบียน launcher ทันทีก่อนเริ่มทำงาน — ซึ่งละเมิดสัญญา API และอาจทำให้สูญเสียผลลัพธ์เมื่อ Activity ถูกสร้างใหม่
class MainActivity : AppCompatActivity() {
private val pickImageLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let { binding.imageView.setImageURI(it) }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pickImageLauncher.launch("image/*")
}
}
ใน Fragment การลงทะเบียนจะดำเนินการใน onCreate, onAttach หรือการเริ่มต้นใน onCreateView FragmentActivity ส่ง launcher ผ่าน Activity หลัก ดังนั้นผลลัพธ์จะถูกประมวลผลภายใน Fragment ไม่ใช่ใน Activity ซึ่งช่วยปรับปรุงการห่อหุ้มเมื่อเทียบกับ onActivityResult ที่ผลลัพธ์ทั้งหมดจาก Fragment ทั้งหมดถูกรวบรวมในเมธอดเดียวของ Activity
class ProfileFragment : Fragment() {
private val cameraLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) { updateProfilePhoto() }
}
fun takePhoto(photoUri: Uri) {
cameraLauncher.launch(photoUri)
}
}
Jetpack Compose มีฟังก์ชัน composable พิเศษสำหรับ Activity Result API — rememberLauncherForActivityResult แตกต่างจากวิธีการดั้งเดิม ใน Compose launcher ถูกสร้างเป็นออบเจ็กต์ที่ผูกกับวงจรชีวิตของ composable ผ่าน remember ซึ่งช่วยให้ใช้ Activity Result API ในรูปแบบประกาศได้อย่างสมบูรณ์โดยไม่ต้องเข้าถึง Activity หรือ Fragment โดยตรง
rememberLauncherForActivityResult รับ Contract และ callback และส่งคืน ActivityResultLauncher Launcher จะถูกเก็บรักษาไว้ระหว่างการประกอบใหม่และถูกล้างโดยอัตโนมัติเมื่อออกจากองค์ประกอบ การเรียก launch เกิดขึ้นเพื่อตอบสนองต่อเหตุการณ์ — ตัวอย่างเช่น การคลิกปุ่มหรือการเปลี่ยนแปลงสถานะ
@Composable
fun PhotoPicker() {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> handleImage(uri) }
Button(onClick = { launcher.launch("image/*") }) {
Text("เลือกรูปภาพ")
}
}
การขอสิทธิ์ ใน Compose ก็ทำผ่าน rememberLauncherForActivityResult ด้วยคอนแทรกต์ RequestPermission หรือ RequestMultiplePermissions Google แนะนำให้ใช้ accompanist-permissions แต่ภายในก็ใช้ Activity Result API เช่นกัน ในการติดตามสถานะสิทธิ์ ควรเก็บสถานะใน remember หรือ ViewModel
Activity Result API ขจัดปัญหามากมายของวิธีการเก่า แต่การใช้งานที่ไม่ถูกต้องอาจนำไปสู่ข้อผิดพลาดรูปแบบใหม่ มาดูปัญหาที่พบบ่อยที่สุดและวิธีหลีกเลี่ยง
การลงทะเบียน launcher ต้องดำเนินการระหว่างการเริ่มต้นคอมโพเนนต์ — ใน onCreate ของ Activity หรือตัวเริ่มต้น Fragment หากคุณลงทะเบียน launcher ภายใน lambda, callback หรือ coroutine เมื่อ Activity ถูกสร้างใหม่ การลงทะเบียนอาจถูกดำเนินการอีกครั้งและ launcher เก่าจะสูญเสียการเชื่อมต่อกับผลลัพธ์
แต่ละ launcher จะได้รับคีย์เฉพาะสำหรับการบันทึกสถานะ หากคุณลงทะเบียน launcher สองตัวด้วย Contract เดียวกันในคอมโพเนนต์เดียว SavedStateRegistry อาจเขียนทับสถานะของตัวหนึ่งด้วยอีกตัวหนึ่ง Android Studio เตือนเกี่ยวกับเรื่องนี้ผ่านกฎ lint UnnecessaryRegisterForActivityResult แต่ควรควบคุมความเป็นเอกลักษณ์ด้วยตนเองจะดีกว่า
ผู้ใช้อาจ ยกเลิก การกระทำ — กดปุ่มกลับของระบบ ย่อแอป หรือสลับไปยังแอปอื่น ในกรณีนี้ callback จะได้รับ null หรือ ActivityResult พร้อม RESULT_CANCELED ตรวจสอบผลลัพธ์ว่าเป็น null ก่อนใช้งานเสมอเพื่อหลีกเลี่ยง NullPointerException
หากแอปของคุณเริ่ม สถานการณ์ที่คล้ายกัน บ่อยครั้ง — เช่น การเลือกรายชื่อติดต่อและส่งคืนชื่อและเบอร์โทรศัพท์ — ให้สร้าง Contract ที่กำหนดเอง ซึ่งช่วยเพิ่มความสามารถในการอ่านโค้ดและอนุญาตให้เปลี่ยนแปลงตรรกะการเริ่มและการจัดการผลลัพธ์จากส่วนกลาง
class PickContactContract : ActivityResultContract<Void, ContactData?>() {
override fun createIntent(context: Context, input: Void?) =
Intent(Intent.ACTION_PICK).setType(ContactsContract.Contacts.CONTENT_TYPE)
override fun parseResult(resultCode: Int, intent: Intent?) =
intent?.data?.let { queryContact(it) }
}
คำถามที่พบบ่อย
ไม่ — ActivityResultLauncher ต้องการบริบทของ Activity หรือ Fragment สำหรับการลงทะเบียน ใช้ ViewModel สำหรับเก็บสถานะเท่านั้น และสร้าง launcher ใน Activity หรือ Fragment แล้วส่งผลลัพธ์ไปยัง ViewModel
Activity Result API พร้อมใช้งานตั้งแต่ไลบรารี activity-ktx 1.2.0 SDK ขั้นต่ำคือ API Level 14 (Android 4.0) แต่คอนแทรกต์ส่วนใหญ่ทำงานบน API Level 19+ เท่านั้น
การเรียกซ้ำ launch ก่อนที่การดำเนินการแรกจะเสร็จสมบูรณ์จะถูกละเว้น Activity Result API ไม่รองรับการเริ่มพร้อมกัน — รอ callback จากการดำเนินการแรกก่อนทำการเรียกใหม่
การย้าย ทำได้โดยแทนที่การเรียก startActivityForResult ด้วย registerForActivityResult โดยใช้ Contract ที่เหมาะสม ลบ onActivityResult และจัดการผลลัพธ์ใน callback ของ launcher Google มีคู่มือการย้ายในเอกสาร Android Developers
ได้ หลายไลบรารีรองรับการรวมผ่าน ActivityResultContracts ตัวอย่างเช่น ML Kit Barcode Scanner ใช้ StartIntentSenderForResult เพื่อเริ่มสแกนเนอร์ ตรวจสอบเอกสารของไลบรารีนั้นๆ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม