Việc bắt đầu một hoạt động khác (dù là hoạt động trong ứng dụng hay từ ứng dụng khác) không nhất thiết phải là quá trình một chiều. Bạn cũng có thể bắt đầu một hoạt động và nhận lại kết quả. Ví dụ: Ứng dụng của bạn có thể khởi động ứng dụng máy ảnh rồi 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 người liên hệ, nhờ đó bạn sẽ nhận được thông tin liên hệ.
Tuy các API cơ bản startActivityForResult()
và onActivityResult()
có sẵn trên lớp Activity
ở tất cả cấp độ API, bạn nên sử dụng Activity Result API được giới thiệu trong lớp Activity
và Fragment
của AndroidX.
Activity Result API cung cấp các thành phần để đăng ký kết quả, khởi chạy hoạt động tạo ra kết quả và xử lý kết quả khi kết quả đó do hệ thống gửi đi.
Đăng ký lệnh gọi lại cho kết quả hoạt động
Khi bắt đầu một hoạt động cho một kết quả, có thể (và trong trường hợp những hoạt động cần nhiều bộ nhớ như sử dụng máy ảnh) gần như chắc chắn là 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ì cần phải có sẵn lệnh gọi lại kết quả khi quá trình và hoạt động đượ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 tạo hoạt động, ngay cả khi logic để chạy hoạt động khác chỉ diễn 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
rồi 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 này cung cấp 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 hợp đồng tuỳ chỉ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 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 } });
Nếu có nhiều lệnh gọi kết quả hoạt động, đồng thời bạn đang sử dụng nhiều hợp đồng khác nhau hoặc muốn 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 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ể an toàn gọi lệnh registerForActivityResult()
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 biến thành phần cho các thực thể ActivityResultLauncher
đã trả về.
Khởi chạy một hoạt động cho kết quả
Tuy đă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 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/*"); } }); }
Phiên bản nạp chồng của launch()
cho phép bạn truyền thêm ActivityOptionsCompat
cho giá trị đầu vào.
Nhận kết quả hoạt động trong một lớp riêng
Tuy lớp ComponentActivity
và Fragment
triển khai giao diện ActivityResultCaller
để cho phép bạn sử dụng registerForActivityResult()
API, nhưng bạn cũng có thể nhận được kết quả hoạt động trong một lớp riêng biệt chứ không triển khai ActivityResultCaller
bằng cách trực tiếp sử dụng ActivityResultRegistry
.
Ví dụ: Có thể bạn nên triển khai LifecycleObserver
, giúp xử lý việc đăng ký hợp đồng cùng với 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(); } }); } }
Đặc biệt, khi sử dụng ActivityResultRegistry
API, bạn nên sử dụng API lấy LifecycleOwner
, vì LifecycleOwner
sẽ tự động xoá trình chạy đã đăng ký khi Lifecycle
bị huỷ. Tuy nhiên, trong trường hợp không có LifecycleOwner
, thì giải pháp thay thế là mỗi lớp ActivityResultLauncher
sẽ cho phép bạn gọi unregister()
theo cách thủ công.
Kiểm thử
Theo mặc định, registerForActivityResult()
sẽ 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 mà bạn có thể sử dụng để kiểm thử lệnh gọi kết quả hoạt động mà không thực sự khởi chạy hoạt động khác.
Khi kiểm thử mảnh của ứng dụng, bạn sẽ 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 theo cách 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 chương trình 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)
}
}
Chương trình kiểm thử hoàn chỉnh sẽ tạo ra kết quả dự kiến, xây dựng 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 sử dụng các 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
Tuy ActivityResultContracts
có chứa một số lớp ActivityResultContract
dựng 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 theo đúng loại bạn cần.
Mỗi ActivityResultContract
đòi hỏi các lớp đầu vào và đầu ra đã được xác định, sử dụng Void
làm loại đầu vào nếu không có bất cứ yêu cầu nào về đầu vào (trong Kotlin, hãy sử dụng Void?
hoặc Unit
).
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 cùng startActivityForResult()
.
Mỗi hợp đồng cũng phải triển khai parseResult()
để tạo dữ liệu đầu ra từ resultCode
cho trước (ví dụ: Activity.RESULT_OK
hoặc Activity.RESULT_CANCELED
, cũng như Intent
).
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 cho trước 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ả.
Ví dụ sau đây cho thấy cách tạo 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); } }
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 khung, lấy bất cứ 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)); } }); }