从 Activity 获取结果

启动另一个 activity(无论是您应用内的 activity 还是来自另一个应用的 activity)不一定是一项单向操作。您也可以启动一个 activity 并接收返回的结果。例如,您的应用可以启动一个相机应用并接收捕获的照片作为结果。或者,您可以启动“联系人”应用,让用户选择一个联系人,然后接收联系人详细信息作为结果。

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

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

注册 activity 结果回调

当启动一个 activity 以获取结果时,您的进程和 activity 可能会因内存不足而被销毁,在内存密集型操作(例如相机使用)的情况下,这几乎是必然的。

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

ComponentActivityFragment 中,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 结果

尽管 ComponentActivityFragment 类实现了 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_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));
        }
    });
}