启动另一个活动,无论是在您的应用内还是来自其他应用,都不需要是一次性操作。您还可以启动一个活动并接收返回的结果。例如,您的应用可以启动相机应用,并将捕获的照片作为结果接收。或者,您可能会启动联系人应用,让用户选择一个联系人,然后将联系人详细信息作为结果接收。
虽然底层 startActivityForResult()
和 onActivityResult()
API 在所有 API 级别上的 Activity
类中可用,但 Google 强烈建议使用 AndroidX Activity
和 Fragment
类中引入的 Activity Result API。
Activity Result API 提供了用于注册结果、启动产生结果的活动以及在系统调度结果后处理结果的组件。
注册活动结果的回调
在启动活动以获取结果时,您的进程和您的活动可能会由于内存不足而被销毁,这在相机使用等内存密集型操作的情况下是可能的,并且几乎是肯定的。
出于这个原因,ActivityResult API 将结果回调与启动其他 Activity 的代码位置解耦。由于结果回调需要在您的进程和 Activity 重新创建时可用,因此回调必须在每次创建 Activity 时都无条件地注册,即使启动其他 Activity 的逻辑仅基于用户输入或其他业务逻辑。
在 ComponentActivity
或 Fragment
中,ActivityResult 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,您也可以在不实现 ActivityResultCaller
的单独类中接收 Activity 结果,方法是直接使用 ActivityResultRegistry
。
例如,您可能希望实现一个 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,因为 LifecycleOwner
会在 Lifecycle
被销毁时自动删除您注册的启动器。但是,在没有 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)); } }); }