Обработка условных результатов в onActivityResult в Android
Освойте обработку условных результатов в Android активити. Обрабатывайте случаи, когда вторые активити могут не возвращать результаты из-за проблем с камерой или отмены пользователем.
Как обрабатывать onActivityResult при использовании startActivityForResult в Android с условными возвратами результатов?
Я реализую приложение Android, в котором запускаю вторую активность из основной активности с помощью startActivityForResult. Вторая активность имеет несколько точек выхода:
-
Она может закрыться без возврата результата, если:
- У устройства нет камеры
- Возникают проблемы при подготовке MediaRecorder или MediaPlayer
-
Она должна вернуть результат (путь к записанному видео) только когда:
- У устройства есть камера
- Запись видео завершена успешно
- Пользователь нажимает кнопку “Готово”
Как правильно проверять и обрабатывать результат в методе onActivityResult моей основной активности, особенно когда вторая активность не всегда возвращает результат?
Использование startActivityForResult в Android с условными возвратами результатов
При использовании startActivityForResult в Android с условными возвратами результатов необходимо правильно обрабатывать как успешные результаты, так и случаи, когда вторая активность не возвращает никакого результата. Ключевым моментом является проверка requestCode, resultCode и данных Intent в методе onActivityResult, при этом необходимо убедиться, что ваша вторая активность правильно вызывает setResult() перед finish(), когда должна вернуть результат.
Содержание
- Базовая структура onActivityResult
- Шаблоны обработки условных результатов
- Распространенные проблемы и решения
- Современный подход с ActivityResultLauncher
- Особенности работы с фрагментами
- Практические примеры реализации
- Лучшие практики
Базовая структура onActivityResult
Метод onActivityResult получает три параметра, которые вместе предоставляют полную информацию о результате:
requestCode: Целое число, которое вы указываете при запуске активности, используется для определения, какая активность вернула результатresultCode: Указывает, была ли операция успешной (RESULT_OK) или отменена (RESULT_CANCELED)data: Intent, который содержит любые данные результата от второй активности
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Сначала проверяем, соответствует ли запрос ожидаемому
if (requestCode == REQUEST_CODE_VIDEO_RECORDING) {
// Затем проверяем код результата
if (resultCode == Activity.RESULT_OK) {
// Обработка успешного результата
if (data != null) {
String videoPath = data.getStringExtra("video_path");
// Обработка пути к видео
}
} else if (resultCode == Activity.RESULT_CANCELED) {
// Обработка отмены - что может быть вызвано:
// - Нажатием пользователем кнопки "назад"
// - Отсутствием камеры на устройстве
// - Сбоем подготовки к записи
// - Или любым другим условием, когда результат не возвращается
}
}
}
Шаблоны обработки условных результатов
При работе с условными результатами необходимо обрабатывать несколько сценариев:
1. Нормальный путь успешного выполнения (камера доступна, запись завершена, пользователь нажимает “Готово”)
Во второй активности, когда все условия выполнены:
// Во второй активности (VideoRecorderActivity)
public void onDoneClicked(View view) {
if (videoRecordingSuccessful && videoPath != null) {
Intent resultIntent = new Intent();
resultIntent.putExtra("video_path", videoPath);
setResult(Activity.RESULT_OK, resultIntent);
finish();
}
}
2. Условные сценарии сбоя
Когда условия не выполнены, у вас есть варианты:
Вариант А: Явно установить RESULT_CANCELED:
// Во второй активности, когда камера недоступна
if (!hasCamera()) {
setResult(Activity.RESULT_CANCELED);
finish();
return;
}
Вариант Б: Не вызывать setResult() вообще:
// Если setResult() не вызывается перед finish(),
// Android автоматически возвращает RESULT_CANCELED
if (recordingFailed) {
finish(); // setResult() не вызывается
return;
}
3. Обработка всех случаев в onActivityResult
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_VIDEO_RECORDING) {
if (resultCode == Activity.RESULT_OK && data != null) {
// Успех: камера доступна, запись завершена, пользователь нажал "готово"
String videoPath = data.getStringExtra("video_path");
processVideo(videoPath);
} else {
// Отмена: либо RESULT_CANCELED, либо результат не возвращен
if (resultCode == Activity.RESULT_CANCELED) {
// Явная отмена (пользователь нажал "назад" или вызван setResult(RESULT_CANCELED))
showUserCancelledMessage();
} else {
// Результат не возвращен (setResult никогда не вызывался)
showRecordingFailedMessage();
}
}
}
}
Распространенные проблемы и решения
1. setResult вызывается после finish()
Проблема: Если вы вызываете setResult() после finish(), результат не будет доставлен.
Решение: Всегда вызывайте setResult() перед finish():
// Правильный порядок
setResult(Activity.RESULT_OK, resultIntent);
finish();
2. Забыть проверить Intent на null
Проблема: Когда resultCode == RESULT_OK, но data равен null, ваше приложение упадет при попытке получить доступ к extras.
Решение: Всегда проверяйте, что data != null перед доступом к extras:
if (resultCode == Activity.RESULT_OK && data != null) {
// Безопасно получать доступ к данным extras
}
3. Несоответствие кода запроса
Проблема: Использование одного и того же кода запроса для разных активностей может привести к неправильной обработке результатов.
Решение: Используйте уникальные коды запросов:
private static final int REQUEST_CODE_VIDEO_RECORDING = 1001;
private static final int REQUEST_CODE_IMAGE_CAPTURE = 1002;
Современный подход с ActivityResultLauncher
Метод startActivityForResult устарел в пользу современного API ActivityResultLauncher, который упрощает обработку условных результатов:
// В вашей активности
private ActivityResultLauncher<Intent> videoResultLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
videoResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
if (data != null) {
String videoPath = data.getStringExtra("video_path");
processVideo(videoPath);
}
} else {
// Обработка отмены или сбоя
showRecordingFailedMessage();
}
}
);
}
// Запуск активности
private void startVideoRecording() {
Intent intent = new Intent(this, VideoRecorderActivity.class);
videoResultLauncher.launch(intent);
}
Особенности работы с фрагментами
При вызове startActivityForResult из фрагмента, requestCode изменяется родительской активностью. Для правильной обработки этого случая:
// Во фрагменте
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// requestCode будет отличаться от того, что вы передали
// Используйте уникальный базовый код для идентификации ваших запросов
if ((requestCode & 0xFFFF0000) == ((MY_REQUEST_CODE << 16) & 0xFFFF0000)) {
int originalRequestCode = requestCode >> 16;
if (originalRequestCode == VIDEO_RECORDING_REQUEST) {
// Обработка результата
}
}
super.onActivityResult(requestCode, resultCode, data);
}
// Для запуска из фрагмента
private void launchVideoRecording() {
Intent intent = new Intent(getActivity(), VideoRecorderActivity.class);
startActivityForResult(intent, (VIDEO_RECORDING_REQUEST << 16) | getActivity().hashCode());
}
Практические примеры реализации
Полный процесс записи видео
MainActivity:
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE_VIDEO_RECORDING = 1001;
private TextView resultTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
resultTextView = findViewById(R.id.resultTextView);
findViewById(R.id.startRecordingBtn).setOnClickListener(v -> {
if (checkCameraPermission()) {
Intent intent = new Intent(this, VideoRecorderActivity.class);
startActivityForResult(intent, REQUEST_CODE_VIDEO_RECORDING);
} else {
requestCameraPermission();
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_VIDEO_RECORDING) {
if (resultCode == Activity.RESULT_OK && data != null) {
String videoPath = data.getStringExtra("video_path");
resultTextView.setText("Видео записано: " + videoPath);
// Продолжение обработки видео
} else {
if (resultCode == Activity.RESULT_CANCELED) {
resultTextView.setText("Запись отменена пользователем");
} else {
resultTextView.setText("Ошибка записи - нет камеры или технические проблемы");
}
}
}
}
}
VideoRecorderActivity:
public class VideoRecorderActivity extends AppCompatActivity {
private MediaRecorder mediaRecorder;
private String videoPath;
private boolean recordingFailed = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_recorder);
if (!hasCamera()) {
Toast.makeText(this, "Камера недоступна", Toast.LENGTH_SHORT).show();
setResult(Activity.RESULT_CANCELED);
finish();
return;
}
try {
setupMediaRecorder();
findViewById(R.id.recordBtn).setOnClickListener(v -> startRecording());
findViewById(R.id.doneBtn).setOnClickListener(v -> finishWithResult());
findViewById(R.id.cancelBtn).setOnClickListener(v -> cancelRecording());
} catch (Exception e) {
recordingFailed = true;
Toast.makeText(this, "Не удалось подготовить рекордер: " + e.getMessage(),
Toast.LENGTH_SHORT).show();
setResult(Activity.RESULT_CANCELED);
finish();
}
}
private boolean hasCamera() {
return getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
}
private void setupMediaRecorder() throws Exception {
// Код настройки MediaRecorder
// Это может не сработать по разным причинам
}
private void finishWithResult() {
if (videoPath != null && !recordingFailed) {
Intent resultIntent = new Intent();
resultIntent.putExtra("video_path", videoPath);
setResult(Activity.RESULT_OK, resultIntent);
} else {
setResult(Activity.RESULT_CANCELED);
}
finish();
}
private void cancelRecording() {
setResult(Activity.RESULT_CANCELED);
finish();
}
}
Лучшие практики
- Всегда проверяйте все три параметра в
onActivityResult: requestCode, resultCode и data - Обрабатывайте явные и неявные отмены:
RESULT_CANCELEDи случаи, когда результат не возвращен - Используйте уникальные коды запросов для разных активностей, чтобы избежать путаницы
- Проверяйте Intent на null перед доступом к extras, когда resultCode равен
RESULT_OK - Вызывайте setResult() перед finish() во второй активности
- Рассмотрите возможность использования современного API ActivityResultLauncher для новой разработки
- Добавьте правильную обработку ошибок для случаев, когда настройка камеры или записи не удалась
- Предоставляйте пользователю обратную связь для разных сценариев сбоя
- Тщательно тестируйте на устройствах без камер
- Используйте описательные коды результатов, если нужно различать разные типы сбоев
Следуя этим шаблонам и практикам, вы можете создавать надежные Android-приложения, которые корректно обрабатывают условные результаты и обеспечивают хороший пользовательский опыт даже при технических ограничениях или выборе пользователя, предотвращающем успешный результат.
Источники
- Get a result from an activity | App architecture | Android Developers
- How to manage startActivityForResult on Android - Stack Overflow
- How to Manage startActivityForResult on Android? - GeeksforGeeks
- Getting a Result from an Activity | Android Developers
- android - startActivityForResult() returns result code RESULT_CANCELED - Stack Overflow
- How to Use ActivityForResultLuncher as StartActivityForResult() is Deprecated in Android? - GeeksforGeeks
- Managing startActivityForResult on Android - Repeato
- Android start Activity for result - Mobikul
- Android: Using onActivityResult(), startActivityForResult() and setResult() - MasterDaWeb Blog
- Android Tutorial => Getting a result from another Activity