从活动获取结果

启动另一个活动,无论是在您的应用内还是来自其他应用,都不需要是一次性操作。您还可以启动一个活动并接收返回的结果。例如,您的应用可以启动相机应用,并将捕获的照片作为结果接收。或者,您可能会启动联系人应用,让用户选择一个联系人,然后将联系人详细信息作为结果接收。

虽然底层 startActivityForResult()onActivityResult() API 在所有 API 级别上的 Activity 类中可用,但 Google 强烈建议使用 AndroidX ActivityFragment 类中引入的 Activity Result API。

Activity Result API 提供了用于注册结果、启动产生结果的活动以及在系统调度结果后处理结果的组件。

注册活动结果的回调

在启动活动以获取结果时,您的进程和您的活动可能会由于内存不足而被销毁,这在相机使用等内存密集型操作的情况下是可能的,并且几乎是肯定的。

出于这个原因,ActivityResult API 将结果回调与启动其他 Activity 的代码位置解耦。由于结果回调需要在您的进程和 Activity 重新创建时可用,因此回调必须在每次创建 Activity 时都无条件地注册,即使启动其他 Activity 的逻辑仅基于用户输入或其他业务逻辑。

ComponentActivityFragment 中,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 并返回时,将执行来自 ActivityResultCallbackonActivityResult(),如下例所示

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 结果

ComponentActivityFragment 类实现 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_OKActivity.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,允许您在回调中提取 resultCodeIntent,如下例所示

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));
        }
    });
}