从活动获取结果

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

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

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

为活动结果注册回调

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

因此,活动结果 API 将结果回调与您在代码中启动其他活动的位置分离。因为结果回调需要在重新创建您的进程和活动时可用,所以每次创建活动时都必须无条件地注册回调,即使启动其他活动的逻辑仅基于用户输入或其他业务逻辑发生。

ComponentActivityFragment 中,活动结果 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()开始生成结果的过程。当用户完成后续活动并返回时,将执行来自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

在单独的类中接收活动结果

ComponentActivityFragment类实现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_OKActivity.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,允许您提取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));
        }
    });
}