Запуск другого действия, будь то в вашем приложении или из другого приложения, не обязательно должен быть односторонней операцией. Вы также можете начать действие и получить результат обратно. Например, ваше приложение может запустить приложение камеры и в результате получить сделанную фотографию. Или вы можете запустить приложение «Контакты», чтобы пользователь мог выбрать контакт, а затем получить в результате контактные данные.
Хотя базовые API-интерфейсы startActivityForResult() и onActivityResult() доступны в классе Activity на всех уровнях API, Google настоятельно рекомендует использовать API-интерфейсы Activity Result, представленные в классах AndroidX Activity и Fragment .
API-интерфейсы результатов действий предоставляют компоненты для регистрации результата, запуска действия, создающего результат, и обработки результата после его отправки системой.
Зарегистрируйте обратный вызов для результата активности
При запуске действия ради результата возможно (а в случае операций с интенсивным использованием памяти, таких как использование камеры, почти наверняка), что ваш процесс и ваша деятельность будут уничтожены из-за нехватки памяти.
По этой причине API результатов действия отделяют обратный вызов результата от места в вашем коде, где вы запускаете другое действие. Поскольку обратный вызов результата должен быть доступен при воссоздании вашего процесса и действия, обратный вызов должен быть безусловно зарегистрирован каждый раз, когда создается ваше действие, даже если логика запуска другого действия происходит только на основе пользовательского ввода или другой бизнес-логики.
В ComponentActivity или Fragment API результатов действия предоставляют API registerForActivityResult() для регистрации обратного вызова результата. registerForActivityResult() принимает ActivityResultContract и ActivityResultCallback и возвращает ActivityResultLauncher , который вы используете для запуска другого действия.
ActivityResultContract определяет тип ввода, необходимый для получения результата, а также тип вывода результата. API-интерфейсы предоставляют контракты по умолчанию для основных действий, таких как съемка изображения, запрос разрешений и т. д. Вы также можете создать собственный контракт .
ActivityResultCallback — это интерфейс с одним методом с методом onActivityResult() , который принимает объект типа вывода, определенного в ActivityResultContract :
Котлин
val getContent = registerForActivityResult(GetContent()) { uri: Uri? -> // Handle the returned Uri }
Ява
// 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() запускает процесс получения результата. Когда пользователь завершает последующее действие и возвращается, выполняется onActivityResult() из ActivityResultCallback , как показано в следующем примере:
Котлин
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/*") } }
Ява
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 , позволяющий использовать API-интерфейсы registerForActivityResult() , вы также можете получить результат действия в отдельном классе, который не реализует ActivityResultCaller напрямую используя ActivityResultRegistry .
Например, вы можете реализовать LifecycleObserver , который обрабатывает регистрацию контракта вместе с запуском средства запуска:
Котлин
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() } } }
Ява
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(); } }); } }
При использовании API ActivityResultRegistry Google настоятельно рекомендует использовать API, которые принимают LifecycleOwner , поскольку LifecycleOwner автоматически удаляет вашу зарегистрированную программу запуска при уничтожении Lifecycle . Однако в тех случаях, когда LifecycleOwner недоступен, каждый класс ActivityResultLauncher позволяет вручную вызвать unregister() в качестве альтернативы.
Тест
По умолчанию registerForActivityResult() автоматически использует ActivityResultRegistry предоставленный действием. Он также предоставляет перегрузку, позволяющую передать собственный экземпляр ActivityResultRegistry , который можно использовать для проверки вызовов результатов действий без фактического запуска другого действия.
При тестировании фрагментов вашего приложения вы предоставляете тестовый ActivityResultRegistry , используя FragmentFactory для передачи ActivityResultRegistry конструктору фрагмента.
Например, фрагмент, использующий контракт TakePicturePreview для получения миниатюры изображения, может быть написан примерно так:
Котлин
class MyFragment( private val registry: ActivityResultRegistry ) : Fragment() { val thumbnailLiveData = MutableLiveData<Bitmap?> val takePicture = registerForActivityResult(TakePicturePreview(), registry) { bitmap: Bitmap? -> thumbnailLiveData.setValue(bitmap) } // ... }
Ява
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 , передает его фрагменту, запускает средство запуска либо напрямую, либо с помощью других тестовых 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 и входные данные и создает Intent которое используется с startActivityForResult() .
Каждый контракт также должен реализовывать parseResult() , который создает выходные данные из данного resultCode , например Activity.RESULT_OK или Activity.RESULT_CANCELED , и Intent .
Контракты могут дополнительно реализовывать getSynchronousResult() если можно определить результат для данного ввода без необходимости вызывать createIntent() , запускать другое действие и использовать parseResult() для построения результата.
В следующем примере показано, как создать ActivityResultContract :
Котлин
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) } }
Ява
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 как часть обратного вызова, как показано в следующем примере:
Котлин
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)) } }
Ява
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)); } }); }