Другое

Обработка условных результатов в onActivityResult в Android

Освойте обработку условных результатов в Android активити. Обрабатывайте случаи, когда вторые активити могут не возвращать результаты из-за проблем с камерой или отмены пользователем.

Как обрабатывать onActivityResult при использовании startActivityForResult в Android с условными возвратами результатов?

Я реализую приложение Android, в котором запускаю вторую активность из основной активности с помощью startActivityForResult. Вторая активность имеет несколько точек выхода:

  1. Она может закрыться без возврата результата, если:

    • У устройства нет камеры
    • Возникают проблемы при подготовке MediaRecorder или MediaPlayer
  2. Она должна вернуть результат (путь к записанному видео) только когда:

    • У устройства есть камера
    • Запись видео завершена успешно
    • Пользователь нажимает кнопку “Готово”

Как правильно проверять и обрабатывать результат в методе onActivityResult моей основной активности, особенно когда вторая активность не всегда возвращает результат?

Использование startActivityForResult в Android с условными возвратами результатов

При использовании startActivityForResult в Android с условными возвратами результатов необходимо правильно обрабатывать как успешные результаты, так и случаи, когда вторая активность не возвращает никакого результата. Ключевым моментом является проверка requestCode, resultCode и данных Intent в методе onActivityResult, при этом необходимо убедиться, что ваша вторая активность правильно вызывает setResult() перед finish(), когда должна вернуть результат.

Содержание


Базовая структура onActivityResult

Метод onActivityResult получает три параметра, которые вместе предоставляют полную информацию о результате:

  • requestCode: Целое число, которое вы указываете при запуске активности, используется для определения, какая активность вернула результат
  • resultCode: Указывает, была ли операция успешной (RESULT_OK) или отменена (RESULT_CANCELED)
  • data: Intent, который содержит любые данные результата от второй активности
java
@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. Нормальный путь успешного выполнения (камера доступна, запись завершена, пользователь нажимает “Готово”)

Во второй активности, когда все условия выполнены:

java
// Во второй активности (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:

java
// Во второй активности, когда камера недоступна
if (!hasCamera()) {
    setResult(Activity.RESULT_CANCELED);
    finish();
    return;
}

Вариант Б: Не вызывать setResult() вообще:

java
// Если setResult() не вызывается перед finish(),
// Android автоматически возвращает RESULT_CANCELED
if (recordingFailed) {
    finish(); // setResult() не вызывается
    return;
}

3. Обработка всех случаев в onActivityResult

java
@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():

java
// Правильный порядок
setResult(Activity.RESULT_OK, resultIntent);
finish();

2. Забыть проверить Intent на null

Проблема: Когда resultCode == RESULT_OK, но data равен null, ваше приложение упадет при попытке получить доступ к extras.

Решение: Всегда проверяйте, что data != null перед доступом к extras:

java
if (resultCode == Activity.RESULT_OK && data != null) {
    // Безопасно получать доступ к данным extras
}

3. Несоответствие кода запроса

Проблема: Использование одного и того же кода запроса для разных активностей может привести к неправильной обработке результатов.

Решение: Используйте уникальные коды запросов:

java
private static final int REQUEST_CODE_VIDEO_RECORDING = 1001;
private static final int REQUEST_CODE_IMAGE_CAPTURE = 1002;

Современный подход с ActivityResultLauncher

Метод startActivityForResult устарел в пользу современного API ActivityResultLauncher, который упрощает обработку условных результатов:

java
// В вашей активности
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 изменяется родительской активностью. Для правильной обработки этого случая:

java
// Во фрагменте
@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:

java
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:

java
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();
    }
}

Лучшие практики

  1. Всегда проверяйте все три параметра в onActivityResult: requestCode, resultCode и data
  2. Обрабатывайте явные и неявные отмены: RESULT_CANCELED и случаи, когда результат не возвращен
  3. Используйте уникальные коды запросов для разных активностей, чтобы избежать путаницы
  4. Проверяйте Intent на null перед доступом к extras, когда resultCode равен RESULT_OK
  5. Вызывайте setResult() перед finish() во второй активности
  6. Рассмотрите возможность использования современного API ActivityResultLauncher для новой разработки
  7. Добавьте правильную обработку ошибок для случаев, когда настройка камеры или записи не удалась
  8. Предоставляйте пользователю обратную связь для разных сценариев сбоя
  9. Тщательно тестируйте на устройствах без камер
  10. Используйте описательные коды результатов, если нужно различать разные типы сбоев

Следуя этим шаблонам и практикам, вы можете создавать надежные Android-приложения, которые корректно обрабатывают условные результаты и обеспечивают хороший пользовательский опыт даже при технических ограничениях или выборе пользователя, предотвращающем успешный результат.


Источники

  1. Get a result from an activity | App architecture | Android Developers
  2. How to manage startActivityForResult on Android - Stack Overflow
  3. How to Manage startActivityForResult on Android? - GeeksforGeeks
  4. Getting a Result from an Activity | Android Developers
  5. android - startActivityForResult() returns result code RESULT_CANCELED - Stack Overflow
  6. How to Use ActivityForResultLuncher as StartActivityForResult() is Deprecated in Android? - GeeksforGeeks
  7. Managing startActivityForResult on Android - Repeato
  8. Android start Activity for result - Mobikul
  9. Android: Using onActivityResult(), startActivityForResult() and setResult() - MasterDaWeb Blog
  10. Android Tutorial => Getting a result from another Activity
Авторы
Проверено модерацией
Модерация