Việc bắt đầu một hoạt động khác, dù là hoạt động trong ứng dụng của bạn hay từ một ứng dụng khác, không cần phải là thao tác một chiều. Bạn cũng có thể bắt đầu một hoạt động khác và nhận lại kết quả. Ví dụ: Ứng dụng của bạn có thể khởi động một ứng dụng máy ảnh và sau đó nhận ảnh đã chụp. Hoặc bạn có thể khởi động ứng dụng Danh bạ để người dùng chọn một địa chỉ liên hệ, nhờ đó bạn sẽ nhận được thông tin liên hệ.
Mặc dù API startActivityForResult()
và onActivityResult()
cơ sở có sẵn trên lớp Activity
thuộc tất cả cấp độ API, nhưng bạn đặc biệt nên sử dụng Activity Result API được giới thiệu trong Activity (Hoạt động) và Fragment (Mảnh) AndroidX.
Activity Result API cung cấp các thành phần để đăng ký một kết quả, khởi động kết quả và xử lý kết quả sau khi hệ thống gửi đi.
Đăng ký lệnh gọi lại cho Kết quả Activity (Hoạt động)
Khi bắt đầu một hoạt động cho một kết quả, có thể (và gần như chắc chắn trong trường hợp các hoạt động cần nhiều bộ nhớ như sử dụng máy ảnh) quá trình và hoạt động của bạn sẽ bị huỷ bỏ do bộ nhớ sắp hết.
Vì lý do này, Activity Result API tách biệt lệnh gọi lại kết quả từ vị trí trong mã nơi bạn chạy hoạt động khác. Vì lệnh gọi lại kết quả cần có sẵn khi quy trình và hoạt động của bạn được tạo lại, nên lệnh gọi lại phải được đăng ký vô điều kiện mỗi khi bạn tạo hoạt động, ngay cả khi logic chỉ chạy hoạt động khác đã xảy ra dựa trên hoạt động đầu vào của người dùng hoặc logic kinh doanh khác.
Khi ở trong ComponentActivity
hoặc Fragment
, API Activity Result sẽ cung cấp một API registerForActivityResult()
để đăng ký lệnh gọi lại kết quả. registerForActivityResult()
lấy ActivityResultContract
và ActivityResultCallback
và trả về một ActivityResultLauncher
mà bạn sẽ sử dụng để chạy hoạt động khác.
ActivityResultContract
xác định loại đầu vào cần thiết để tạo ra kết quả cùng với loại đầu ra của kết quả. API cung cấp các hợp đồng mặc định cho các thao tác theo ý định cơ bản như chụp ảnh, yêu cầu quyền, v.v. Bạn cũng có thể tạo các hợp đồng tuỳ chỉnh của riêng mình.
ActivityResultCallback
là giao diện phương thức duy nhất có phương thức onActivityResult()
lấy đối tượng của loại đầu ra được xác định trong ActivityResultContract
:
Kotlin
val getContent = registerForActivityResult(GetContent()) { uri: Uri? -> // Handle the returned Uri }
Java
// GetContent creates an ActivityResultLauncher<String> to allow you to pass // in the mime type you'd like to allow the user to select ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(), new ActivityResultCallback<Uri>() { @Override public void onActivityResult(Uri uri) { // Handle the returned Uri } });
Nếu có nhiều lệnh gọi kết quả hoạt động sử dụng nhiều hợp đồng khác nhau hoặc muốn có các lệnh gọi lại riêng biệt, thì bạn có thể gọi registerForActivityResult()
nhiều lần để đăng ký nhiều thực thể ActivityResultLauncher
. Bạn phải luôn gọi registerForActivityResult()
theo cùng một thứ tự cho mỗi lượt tạo mảnh hoặc hoạt động để đảm bảo kết quả đang truyền được gửi tới đúng lệnh gọi lại.
Bạn có thể gọi lệnh registerForActivityResult()
một cách an toàn trước khi tạo mảnh hoặc hoạt động, nhờ vậy có thể sử dụng trực tiếp lệnh đó khi khai báo các biến thành viên cho các thực thể ActivityResultLauncher
đã trả về.
Khởi động một hoạt động cho kết quả
Dù đăng ký lệnh gọi lại của bạn nhưng registerForActivityResult()
không chạy hoạt động khác và bắt đầu yêu cầu kết quả. Thay vào đó, đây là trách nhiệm của thực thể ActivityResultLauncher
được trả về.
Nếu có giá trị đầu vào, trình chạy sẽ lấy giá trị đầu vào khớp với loại của ActivityResultContract
. Hoạt động gọi launch()
sẽ bắt đầu quy trình tạo kết quả. Khi người dùng thực hiện xong hoạt động sau đó và trả về, thì onActivityResult()
từ ActivityResultCallback
sẽ được thực thi, như trong ví dụ sau:
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'd like to allow the user to 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'd like to allow the user to select // as the input mGetContent.launch("image/*"); } }); }
Phiên bản nạp chồng của launch()
cho phép bạn truyền ActivityOptionsCompat
ngoài giá trị đầu vào.
Nhận kết quả hoạt động trong một lớp riêng
Mặc dù lớp ComponentActivity
và Fragment
triển khai giao diện ActivityResultCaller
để cho phép bạn sử dụng API registerForActivityResult()
, nhưng bạn cũng có thể nhận kết quả hoạt động trong một lớp riêng biệt không triển khai ActivityResultCaller
bằng cách sử dụng trực tiếp ActivityResultRegistry
.
Ví dụ: Bạn có thể triển khai LifecycleObserver
giúp xử lý việc đăng ký hợp đồng cùng với việc khởi động trình chạy:
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(); } }); } }
Khi sử dụng API ActivityResultRegistry
, bạn đặc biệt nên sử dụng API lấy LifecycleOwner
, vì LifecycleOwner
sẽ tự động xoá trình chạy đã đăng ký của bạn khi Lifecycle
bị huỷ. Tuy nhiên, trong trường hợp không có LifecycleOwner
, mỗi lớp ActivityResultLauncher
sẽ cho phép bạn gọi unregister()
theo cách thủ công làm phương thức thay thế.
Kiểm thử
Theo mặc định, registerForActivityResult()
tự động sử dụng ActivityResultRegistry
do hoạt động cung cấp. API này cũng cung cấp phương thức nạp chồng cho phép bạn truyền thực thể ActivityResultRegistry
riêng có thể dùng để kiểm thử các lệnh gọi kết quả hoạt động mà không thực sự chạy hoạt động khác.
Khi Kiểm thử các mảnh của ứng dụng, bạn có thể cung cấp ActivityResultRegistry
kiểm thử bằng cách sử dụng FragmentFactory
để truyền ActivityResultRegistry
đến hàm khởi tạo của mảnh.
Ví dụ: Một mảnh sử dụng hợp đồng TakePicturePreview
để tải hình thu nhỏ của hình ảnh có thể được ghi tương tự như sau:
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; } // ... }
Khi tạo một ActivityResultRegistry
kiểm thử cụ thể, bạn phải triển khai phương thức onLaunch()
. Thay vì gọi startActivityForResult()
, phương thức triển khai kiểm thử có thể gọi trực tiếp dispatchResult()
, cung cấp kết quả chính xác mà bạn muốn sử dụng trong kiểm thử của mình:
val testRegistry = object : ActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
dispatchResult(requestCode, expectedResult)
}
}
Việc kiểm thử hoàn chỉnh sẽ tạo ra kết quả dự kiến, tạo một ActivityResultRegistry
kiểm thử, truyền thông tin vào mảnh, kích hoạt trình chạy (trực tiếp hoặc thông qua API kiểm thử khác như Espresso), rồi xác minh kết quả:
@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)
}
}
}
Tạo hợp đồng tuỳ chỉnh
Mặc dù ActivityResultContracts
chứa một số lớp ActivityResultContract
được tạo sẵn để sử dụng, nhưng bạn có thể cung cấp hợp đồng riêng mang đến API an toàn loại chính xác mà bạn yêu cầu.
Mỗi ActivityResultContract
yêu cầu xác định các lớp đầu vào và đầu ra, sử dụng Void
(trong Kotlin, sử dụng Void?
hoặc Unit
) làm loại đầu vào nếu bạn không yêu cầu bất kỳ đầu vào nào.
Mỗi hợp đồng phải triển khai phương thức createIntent()
. Phương thức này sẽ lấy Context
và giá trị nhập cũng như tạo Intent
sẽ được sử dụng với startActivityForResult()
.
Mỗi hợp đồng cũng phải triển khai parseResult()
, tạo đầu ra từ resultCode
nhất định (ví dụ:
Activity.RESULT_OK
hoặc Activity.RESULT_CANCELED
) và Intent
.
Các hợp đồng có thể tuỳ ý triển khai getSynchronousResult()
nếu có thể xác định kết quả cho một giá trị đầu vào nhất định mà không cần gọi createIntent()
, bắt đầu hoạt động khác và sử dụng parseResult()
để tạo kết quả.
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); } }
Nếu không cần hợp đồng tuỳ chỉnh, bạn có thể sử dụng hợp đồng StartActivityForResult
. Đây là một hợp đồng chung, lấy bất kỳ Intent
nào làm giá trị đầu vào và trả về ActivityResult
, cho phép bạn trích xuất resultCode
và Intent
như một phần của lệnh gọi lại, như trong ví dụ sau:
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)); } }); }