Запуск другого действия, будь то в вашем приложении или из другого приложения, не обязательно должен быть односторонней операцией. Вы также можете начать действие и получить результат обратно. Например, ваше приложение может запустить приложение камеры и в результате получить сделанную фотографию. Или вы можете запустить приложение «Контакты», чтобы пользователь мог выбрать контакт, а затем получить в результате контактные данные.
Хотя базовые API-интерфейсы startActivityForResult()
и onActivityResult()
доступны в классе Activity
на всех уровнях API, Google настоятельно рекомендует использовать API-интерфейсы результатов действий, представленные в классах 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)); } }); }