启动另一个活动,无论是您应用中的活动还是来自其他应用的活动,都不需要是一次性操作。您还可以启动一个活动并接收返回的结果。例如,您的应用可以启动相机应用并接收捕获的照片作为结果。或者,您可能启动联系人应用以供用户选择联系人,然后接收联系人详细信息作为结果。
虽然底层 startActivityForResult()
和 onActivityResult()
API 在所有 API 级别上的 Activity
类中可用,但 Google 强烈建议使用 AndroidX Activity
和 Fragment
类中引入的活动结果 API。
活动结果 API 提供了用于注册结果、启动产生结果的活动以及在系统分派结果后处理结果的组件。
为活动结果注册回调
在启动活动以获取结果时,您的进程和活动可能会由于内存不足而被销毁,这在内存密集型操作(如相机使用)的情况下是可能的,而且几乎是肯定的。
因此,活动结果 API 将结果回调与您在代码中启动其他活动的位置分离。因为结果回调需要在重新创建您的进程和活动时可用,所以每次创建活动时都必须无条件地注册回调,即使启动其他活动的逻辑仅基于用户输入或其他业务逻辑发生。
在 ComponentActivity
或 Fragment
中,活动结果 API 提供了一个 registerForActivityResult()
API 用于注册结果回调。registerForActivityResult()
获取一个 ActivityResultContract
和一个 ActivityResultCallback
并返回一个 ActivityResultLauncher
,您可以使用它来启动其他活动。
一个 ActivityResultContract
定义了生成结果所需的输入类型以及结果的输出类型。这些 API 为基本意图操作(如拍照、请求权限等)提供了 默认契约。您还可以 创建自定义契约。
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()
,以便将进行中的结果传递到正确的回调。
registerForActivityResult()
在创建片段或活动之前调用是安全的,允许在声明返回的 ActivityResultLauncher
实例的成员变量时直接使用它。
启动活动以获取结果
虽然registerForActivityResult()
注册了您的回调,但它不会启动另一个活动并开始请求结果。相反,这是返回的ActivityResultLauncher
实例的职责。
如果存在输入,则启动器将获取与ActivityResultContract
类型匹配的输入。调用launch()
开始生成结果的过程。当用户完成后续活动并返回时,将执行来自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
。
在单独的类中接收活动结果
ComponentActivity
和Fragment
类实现ActivityResultCaller
接口,以允许您使用registerForActivityResult()
API,但您也可以在不实现ActivityResultCaller
的单独类中接收活动结果,方法是直接使用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()
自动使用活动提供的ActivityResultRegistry
。它还提供了一个重载,允许您传入您自己的ActivityResultRegistry
实例,您可以使用它来测试您的活动结果调用,而无需实际启动另一个活动。
当测试应用程序的片段时,您可以使用FragmentFactory
提供一个测试ActivityResultRegistry
,以将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()
方法。您的测试实现可以调用dispatchResult()
(而不是调用startActivityForResult()
),直接提供您想要在测试中使用的确切结果
val testRegistry = object : ActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
dispatchResult(requestCode, expectedResult)
}
}
完整的测试创建预期结果,构建测试ActivityResultRegistry
,将其传递给片段,直接或使用其他测试 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()
、启动其他活动以及使用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)); } }); }