รับผลลัพธ์จากกิจกรรม

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

ขณะที่กลยุทธ์ startActivityForResult() และ onActivityResult() API สามารถใช้ได้ในคลาส Activity ใน API ทุกระดับ Google แนะนำให้ใช้ API ของผลการค้นหากิจกรรมซึ่งเปิดตัวใน AndroidX Activity และ Fragment ชั้นเรียน

API ของผลการค้นหากิจกรรมมีคอมโพเนนต์สำหรับลงทะเบียนผลลัพธ์ เปิดใช้งานกิจกรรมที่สร้างผลลัพธ์ และจัดการผลลัพธ์ทันที ส่งโดยระบบ

ลงทะเบียน Callback สำหรับผลของกิจกรรม

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

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

เมื่ออยู่ใน ComponentActivity หรือ Fragment, ผลลัพธ์ของกิจกรรม API มี registerForActivityResult() API สำหรับการลงทะเบียน Callback ผลลัพธ์ registerForActivityResult() ทำประตู ActivityResultContract และ ActivityResultCallback และแสดงค่า ActivityResultLauncher ที่คุณใช้ในการเริ่มกิจกรรมอื่น

ActivityResultContract กำหนดประเภทอินพุตที่จำเป็นต่อการสร้างผลลัพธ์ พร้อมกับประเภทเอาต์พุตของผลการค้นหา API มี สัญญาเริ่มต้น สำหรับการดำเนินการผ่าน Intent พื้นฐาน เช่น การถ่ายภาพ การขอสิทธิ์ เปิดอยู่ นอกจากนี้คุณยัง สร้างสัญญาที่กำหนดเอง

ActivityResultCallback เป็นอินเทอร์เฟซเมธอดเดียวที่มี onActivityResult() ซึ่งจะนำออบเจ็กต์ของประเภทเอาต์พุตที่ระบุใน ActivityResultContract:

Kotlin

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

Java

// GetContent creates an ActivityResultLauncher<String> to let you pass
// in the mime type you want to let the user select
ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(),
    new ActivityResultCallback<Uri>() {
        @Override
        public void onActivityResult(Uri uri) {
            // Handle the returned Uri
        }
});

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

เรียก registerForActivityResult() ได้อย่างปลอดภัยก่อนส่วนย่อยหรือกิจกรรม สร้างขึ้น โดยให้นำมาใช้ได้โดยตรงเมื่อประกาศตัวแปรสมาชิก สำหรับอินสแตนซ์ ActivityResultLauncher รายการที่แสดงผล

เปิดกิจกรรมสำหรับผลลัพธ์

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

ถ้ามีอินพุตอยู่แล้ว ตัวเรียกใช้งานจะใช้อินพุตที่ตรงกับประเภทของ ActivityResultContract การโทร launch() เริ่มกระบวนการสร้างผลลัพธ์ เมื่อผู้ใช้ดำเนินการต่างๆ เสร็จแล้ว กิจกรรมและการคืนสินค้าที่ตามมา onActivityResult() จาก จากนั้น ระบบจะดำเนินการ ActivityResultCallback ดังที่แสดงในตัวอย่างต่อไปนี้

Kotlin

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    val selectButton = findViewById<Button>(R.id.select_button)

    selectButton.setOnClickListener {
        // Pass in the mime type you want to let the user select
        // as the input
        getContent.launch("image/*")
    }
}

Java

ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(),
    new ActivityResultCallback<Uri>() {
        @Override
        public void onActivityResult(Uri uri) {
            // Handle the returned Uri
        }
});

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    // ...

    Button selectButton = findViewById(R.id.select_button);

    selectButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            // Pass in the mime type you want to let the user select
            // as the input
            mGetContent.launch("image/*");
        }
    });
}

เวอร์ชันโอเวอร์โหลด launch() ซึ่งช่วยให้คุณส่งผ่าน ActivityOptionsCompat นอกเหนือจากอินพุต

รับผลของกิจกรรมในชั้นเรียนแยกต่างหาก

แม้ว่าคลาส ComponentActivity และ Fragment จะใช้เมธอด ActivityResultCaller ให้คุณใช้ registerForActivityResult() API ได้ ได้รับผลลัพธ์กิจกรรมในชั้นเรียนแยกต่างหากที่ไม่ได้ใช้ ActivityResultCaller โดยใช้ ActivityResultRegistry โดยตรง

ตัวอย่างเช่น คุณอาจต้องการใช้ LifecycleObserver ที่จัดการการลงทะเบียนสัญญาพร้อมการเปิดตัว Launcher ดังนี้

Kotlin

class MyLifecycleObserver(private val registry : ActivityResultRegistry)
        : DefaultLifecycleObserver {
    lateinit var getContent : ActivityResultLauncher<String>

    override fun onCreate(owner: LifecycleOwner) {
        getContent = registry.register("key", owner, GetContent()) { uri ->
            // Handle the returned Uri
        }
    }

    fun selectImage() {
        getContent.launch("image/*")
    }
}

class MyFragment : Fragment() {
    lateinit var observer : MyLifecycleObserver

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        observer = MyLifecycleObserver(requireActivity().activityResultRegistry)
        lifecycle.addObserver(observer)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val selectButton = view.findViewById<Button>(R.id.select_button)

        selectButton.setOnClickListener {
            // Open the activity to select an image
            observer.selectImage()
        }
    }
}

Java

class MyLifecycleObserver implements DefaultLifecycleObserver {
    private final ActivityResultRegistry mRegistry;
    private ActivityResultLauncher<String> mGetContent;

    MyLifecycleObserver(@NonNull ActivityResultRegistry registry) {
        mRegistry = registry;
    }

    public void onCreate(@NonNull LifecycleOwner owner) {
        // ...

        mGetContent = mRegistry.register(“key”, owner, new GetContent(),
            new ActivityResultCallback<Uri>() {
                @Override
                public void onActivityResult(Uri uri) {
                    // Handle the returned Uri
                }
            });
    }

    public void selectImage() {
        // Open the activity to select an image
        mGetContent.launch("image/*");
    }
}

class MyFragment extends Fragment {
    private MyLifecycleObserver mObserver;

    @Override
    void onCreate(Bundle savedInstanceState) {
        // ...

        mObserver = new MyLifecycleObserver(requireActivity().getActivityResultRegistry());
        getLifecycle().addObserver(mObserver);
    }

    @Override
    void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        Button selectButton = findViewById(R.id.select_button);
        selectButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                mObserver.selectImage();
            }
        });
    }
}

Google ขอแนะนำอย่างยิ่งให้ใช้ ActivityResultRegistry API API ที่ใช้ LifecycleOwner ซึ่งเป็น LifecycleOwner โดยอัตโนมัติ นำ Launcher ที่ลงทะเบียนไว้ออกเมื่อทำลาย Lifecycle อย่างไรก็ตาม ในกรณีที่ LifecycleOwner ไม่พร้อมใช้งาน ชั้นเรียน ActivityResultLauncher ให้คุณโทรหาด้วยตนเองได้ unregister() ไว้ใช้แทน

ทดสอบ

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

เมื่อทดสอบส่วนย่อยของแอป คุณ ระบุ ActivityResultRegistry ทดสอบโดยใช้ FragmentFactory จึงจะผ่าน ใน ActivityResultRegistry ไปยังตัวสร้างของส่วนย่อย

เช่น ส่วนย่อยที่ใช้สัญญา TakePicturePreview เพื่อสร้าง ภาพขนาดย่อ ของรูปภาพ อาจเขียนได้ดังนี้

Kotlin

class MyFragment(
    private val registry: ActivityResultRegistry
) : Fragment() {
    val thumbnailLiveData = MutableLiveData<Bitmap?>

    val takePicture = registerForActivityResult(TakePicturePreview(), registry) {
        bitmap: Bitmap? -> thumbnailLiveData.setValue(bitmap)
    }

    // ...
}

Java

public class MyFragment extends Fragment {
    private final ActivityResultRegistry mRegistry;
    private final MutableLiveData<Bitmap> mThumbnailLiveData = new MutableLiveData();
    private final ActivityResultLauncher<Void> mTakePicture =
        registerForActivityResult(new TakePicturePreview(), mRegistry, new ActivityResultCallback<Bitmap>() {
            @Override
            public void onActivityResult(Bitmap thumbnail) {
                mThumbnailLiveData.setValue(thumbnail);
            }
        });

    public MyFragment(@NonNull ActivityResultRegistry registry) {
        super();
        mRegistry = registry;
    }

    @VisibleForTesting
    @NonNull
    ActivityResultLauncher<Void> getTakePicture() {
        return mTakePicture;
    }

    @VisibleForTesting
    @NonNull
    LiveData<Bitmap> getThumbnailLiveData() {
        return mThumbnailLiveData;
    }

    // ...
}

เมื่อสร้าง ActivityResultRegistry เฉพาะการทดสอบ คุณต้องใช้ เวลา onLaunch() การทดสอบของคุณแทนการโทรหา startActivityForResult() สามารถเรียกใช้ dispatchResult() โดยตรง ซึ่งจะให้ผลลัพธ์ที่คุณต้องการใช้ในการทดสอบ

val testRegistry = object : ActivityResultRegistry() {
    override fun <I, O> onLaunch(
            requestCode: Int,
            contract: ActivityResultContract<I, O>,
            input: I,
            options: ActivityOptionsCompat?
    ) {
        dispatchResult(requestCode, expectedResult)
    }
}

การทดสอบที่สมบูรณ์สร้างผลลัพธ์ที่คาดหวัง และสร้างการทดสอบ ActivityResultRegistry ส่งไปยังส่วนย่อย เรียก Launcher โดยตรงหรือใช้ API ทดสอบอื่นๆ เช่น Espresso แล้วยืนยัน ผลลัพธ์:

@Test
fun activityResultTest {
    // Create an expected result Bitmap
    val expectedResult = Bitmap.createBitmap(1, 1, Bitmap.Config.RGBA_F16)

    // Create the test ActivityResultRegistry
    val testRegistry = object : ActivityResultRegistry() {
            override fun <I, O> onLaunch(
            requestCode: Int,
            contract: ActivityResultContract<I, O>,
            input: I,
            options: ActivityOptionsCompat?
        ) {
            dispatchResult(requestCode, expectedResult)
        }
    }

    // Use the launchFragmentInContainer method that takes a
    // lambda to construct the Fragment with the testRegistry
    with(launchFragmentInContainer { MyFragment(testRegistry) }) {
            onFragment { fragment ->
                // Trigger the ActivityResultLauncher
                fragment.takePicture()
                // Verify the result is set
                assertThat(fragment.thumbnailLiveData.value)
                        .isSameInstanceAs(expectedResult)
            }
    }
}

สร้างสัญญาที่กำหนดเอง

ขณะที่ ActivityResultContracts มีชั้นเรียน ActivityResultContract ที่สร้างไว้ล่วงหน้าหลายรายการเพื่อให้คุณใช้งาน มีสัญญาของคุณเองซึ่งมอบ API ประเภทที่ปลอดภัยและแม่นยำอย่างที่คุณต้องการ

ActivityResultContract แต่ละรายการต้องมีคลาสอินพุตและเอาต์พุตที่กำหนด ให้ใช้ Void เป็นประเภทอินพุตหากคุณ คุณไม่ต้องป้อนข้อมูลใดๆ (ใน Kotlin ให้ใช้ Void? หรือ Unit)

สัญญาแต่ละฉบับต้องดำเนินการตาม createIntent() ซึ่งจะใช้ Context และอินพุต แล้วสร้าง Intent ที่ ใช้ไปแล้ว กับ startActivityForResult()

สัญญาแต่ละฉบับต้องดำเนินการ parseResult(), ซึ่งจะสร้างเอาต์พุตจาก resultCode ที่ระบุ เช่น Activity.RESULT_OK หรือ Activity.RESULT_CANCELED และ Intent

สัญญาสามารถนำมาใช้งานได้ getSynchronousResult() หากเป็นไปได้ที่จะกำหนดผลลัพธ์สำหรับข้อมูลอินพุต จำเป็นต้องโทรหา createIntent() เริ่มกิจกรรมอื่น และใช้ parseResult() เพื่อสร้างผลลัพธ์

ตัวอย่างต่อไปนี้แสดงวิธีการสร้าง ActivityResultContract

Kotlin

class PickRingtone : ActivityResultContract<Int, Uri?>() {
    override fun createIntent(context: Context, ringtoneType: Int) =
        Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
            putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType)
        }

    override fun parseResult(resultCode: Int, result: Intent?) : Uri? {
        if (resultCode != Activity.RESULT_OK) {
            return null
        }
        return result?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
    }
}

Java

public class PickRingtone extends ActivityResultContract<Integer, Uri> {
    @NonNull
    @Override
    public Intent createIntent(@NonNull Context context, @NonNull Integer ringtoneType) {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType.intValue());
        return intent;
    }

    @Override
    public Uri parseResult(int resultCode, @Nullable Intent result) {
        if (resultCode != Activity.RESULT_OK || result == null) {
            return null;
        }
        return result.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
    }
}

หากคุณไม่ต้องการสัญญาที่กำหนดเอง คุณสามารถใช้ StartActivityForResult สัญญา นี่เป็นสัญญาทั่วไปที่ใช้ Intent ใดๆ เป็นอินพุตและ แสดงผลเป็น ActivityResult, ซึ่งช่วยให้คุณดึงข้อมูล resultCode และ Intent เป็นส่วนหนึ่งของ Callback ดังที่ปรากฏในตัวอย่างต่อไปนี้

Kotlin

val startForResult = registerForActivityResult(StartActivityForResult()) { result: ActivityResult ->
    if (result.resultCode == Activity.RESULT_OK) {
        val intent = result.data
        // Handle the Intent
    }
}

override fun onCreate(savedInstanceState: Bundle) {
    // ...

    val startButton = findViewById(R.id.start_button)

    startButton.setOnClickListener {
        // Use the Kotlin extension in activity-ktx
        // passing it the Intent you want to start
        startForResult.launch(Intent(this, ResultProducingActivity::class.java))
    }
}

Java

ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(new StartActivityForResult(),
        new ActivityResultCallback<ActivityResult>() {
    @Override
    public void onActivityResult(ActivityResult result) {
        if (result.getResultCode() == Activity.RESULT_OK) {
            Intent intent = result.getData();
            // Handle the Intent
        }
    }
});

@Override
public void onCreate(@Nullable savedInstanceState: Bundle) {
    // ...

    Button startButton = findViewById(R.id.start_button);

    startButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            // The launcher with the Intent you want to start
            mStartForResult.launch(new Intent(this, ResultProducingActivity.class));
        }
    });
}