启动另一个 activity(无论是您应用内的 activity 还是来自另一个应用的 activity)不一定是一项单向操作。您也可以启动一个 activity 并接收返回的结果。例如,您的应用可以启动一个相机应用并接收捕获的照片作为结果。或者,您可以启动“联系人”应用,让用户选择一个联系人,然后接收联系人详细信息作为结果。
尽管底层的 startActivityForResult()
和 onActivityResult()
API 在所有 API 级别的 Activity
类上都可用,但 Google 强烈建议使用 AndroidX Activity
和 Fragment
类中引入的 Activity Result API。
Activity Result API 提供了用于注册结果、启动产生结果的 activity 以及在系统分派结果后处理结果的组件。
注册 activity 结果回调
当启动一个 activity 以获取结果时,您的进程和 activity 可能会因内存不足而被销毁,在内存密集型操作(例如相机使用)的情况下,这几乎是必然的。
因此,Activity Result API 将结果回调从您启动其他 activity 的代码位置解耦。由于在您的进程和 activity 重新创建时需要结果回调,因此每次创建 activity 时都必须无条件注册回调,即使启动其他 activity 的逻辑仅基于用户输入或其他业务逻辑发生。
在 ComponentActivity
或 Fragment
中,Activity Result API 提供了 registerForActivityResult()
API 用于注册结果回调。registerForActivityResult()
接受一个 ActivityResultContract
和一个 ActivityResultCallback
,并返回一个 ActivityResultLauncher
,您可以使用它来启动其他 activity。
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 } });
如果您有多个 activity 结果调用,并且您使用不同的契约或需要单独的回调,则可以多次调用 registerForActivityResult()
来注册多个 ActivityResultLauncher
实例。您必须在每次创建 Fragment 或 activity 时按相同顺序调用 registerForActivityResult()
,以便将正在进行的结果传递给正确的回调。
registerForActivityResult()
可以安全地在 Fragment 或 activity 创建之前调用,从而允许在为返回的 ActivityResultLauncher
实例声明成员变量时直接使用它。
启动 activity 以获取结果
尽管 registerForActivityResult()
注册了您的回调,但它**不会**启动其他 activity 并启动结果请求。相反,这是返回的 ActivityResultLauncher
实例的职责。
如果存在输入,启动器将接受与 ActivityResultContract
类型匹配的输入。调用 launch()
会启动产生结果的过程。当用户完成后续 activity 并返回时,将执行 ActivityResultCallback
中的 onActivityResult()
,如以下示例所示。
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
。
在单独的类中接收 activity 结果
尽管 ComponentActivity
和 Fragment
类实现了 ActivityResultCaller
接口以允许您使用 registerForActivityResult()
API,但您也可以通过直接使用 ActivityResultRegistry
在不实现 ActivityResultCaller
的单独类中接收 activity 结果。
例如,您可能希望实现一个 LifecycleObserver
,它负责注册契约以及启动启动器。
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(); } }); } }
使用 ActivityResultRegistry
API 时,Google 强烈建议使用接受 LifecycleOwner
的 API,因为当 Lifecycle
被销毁时,LifecycleOwner
会自动移除您注册的启动器。但是,在 LifecycleOwner
不可用的情况下,每个 ActivityResultLauncher
类都允许您手动调用 unregister()
作为替代方案。
测试
默认情况下,registerForActivityResult()
会自动使用 activity 提供的 ActivityResultRegistry
。它还提供了一个重载,允许您传入自己的 ActivityResultRegistry
实例,您可以使用该实例来测试您的 activity 结果调用,而无需实际启动另一个 activity。
测试您的应用 Fragment 时,您可以使用 FragmentFactory
提供一个测试 ActivityResultRegistry
,以将 ActivityResultRegistry
传递给 Fragment 的构造函数。
例如,使用 TakePicturePreview
契约获取图像缩略图的 Fragment 可以写成类似于以下内容:
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
,将其传递给 Fragment,直接或使用其他测试 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
和输入,并构造与 startActivityForResult()
一起使用的 Intent
。
每个契约还必须实现 parseResult()
,该方法根据给定的 resultCode
(例如 Activity.RESULT_OK
或 Activity.RESULT_CANCELED
)和 Intent
产生输出。
如果无需调用 createIntent()
、启动其他 activity 并使用 parseResult()
来构建结果即可确定给定输入的結果,契约可以选择实现 getSynchronousResult()
。
以下示例展示了如何构造 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
作为回调的一部分进行提取,如以下示例所示。
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)); } }); }