Безопасно ли совершать FragmentTransaction внутри onActivityResult()?

Несколько лет назад у меня возникла проблема в одном из моих приложений, когда я попытался зафиксировать FragmentTransaction внутри моего обратного вызова onActivityResult(). Погуляв вокруг, я нашел этот вопрос и ответ, в котором говорится

В момент вызова onActivityResult() состояние активности/фрагмента может быть еще не восстановлено, и поэтому любые транзакции, которые происходят в течение этого времени, будут потеряны в результате.

Я закончил адаптировать решение, рекомендованное в том же ответе для моего приложения, и жизнь была хорошей. Однако недавние эксперименты показывают, что, возможно, все изменилось, и теперь может безопасно совершать FragmentTransaction изнутри onActivityResult().

Документация для (поддержка v4) FragmentManager.beginTransaction() определяет безопасное окно для транзакций как:

Примечание: транзакция фрагмента может быть создана/зафиксирована только до того, как операция сохранит свое состояние. Если вы попытаетесь совершить транзакцию после FragmentActivity.onSaveInstanceState() (и до следующего FragmentActivity.onStart или FragmentActivity.onResume(), вы получите сообщение об ошибке.

Чтение документации для onActivityResult(), я вижу

Вы получите этот вызов непосредственно перед onResume() при повторном запуске вашей активности.

Это заставляет меня думать, что безопасно выполнять эти транзакции в onActivityResult(), поскольку onStart() уже вызван, помещая меня в безопасное окно.

Я сделал приложение, чтобы проверить это, и я успешно вижу фрагменты диалога, которые я создаю и фиксирую внутри onActivityResult(). У меня было одно и то же приложение, которое также регистрировало обратные вызовы жизненного цикла активности, чтобы я мог проверить их порядок, и я вижу onStart(), затем onRestoreInstanceState(), а затем onActivityResult() каждый раз.

Я что-то упустил? Или изменилась структура и onActivityResult() теперь гарантированно станет безопасным местом для транзакций с фрагментами? Различается ли это поведение по уровню API?

Я нашел еще один вопрос и ответ, который, похоже, читает документацию так же, как и у меня, но оба они старше года, и ни один из них не относится к onActivityResult() как безопасное место для транзакций.

Ответ 1

Небольшое погружение в источники

В FragmentManager существует логическая переменная, называемая mStatedSaved. Эта переменная отслеживает сохраненное состояние в зависимости от обратных вызовов жизненного цикла активности. Вот метод, который генерирует известное исключение:


    private void checkStateLoss() {
        if (mStateSaved) {
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        }
        ...
    }

Это означает, что как только эта переменная будет изменена на false, транзакции фрагмента могут быть выполнены. Эта переменная станет true, когда состояние активности будет сохранено, т.е. onSaveInstanceState().


Вернуться к вопросу

Вы сказали, что ранее у вас были проблемы с транзакцией из onActivityResult(). Это должно означать, что ранее mStateSaved не назначался false, и в настоящее время он есть. На деле это так.

Здесь onActivityResult() реализация из выпуска O:


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        mFragments.noteStateNotSaved();
        ...
    }

Где noteStateNotSaved() будет делать следующее:


    public void noteStateNotSaved() {
        ...
        mStateSaved = false;
        ...
    }

Напротив, вы можете увидеть реализацию onActivityResult() версии Jelly Bean:


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        int index = requestCode>>16;
        if (index != 0) {
            index--;
            if (mFragments.mActive == null || index = mFragments.mActive.size()) {
                Log.w(TAG, "Activity result fragment index out of range: 0x"
                        + Integer.toHexString(requestCode));
                return;
            }
            Fragment frag = mFragments.mActive.get(index);
            if (frag == null) {
                Log.w(TAG, "Activity result no fragment exists for index: 0x"
                        + Integer.toHexString(requestCode));
            } else {
                frag.onActivityResult(requestCode&0xffff, resultCode, data);
            }
            return;
        }

        super.onActivityResult(requestCode, resultCode, data);
    }

Ничто не изменило бы значение поля mStateSaved, которое создало бы исключение, если транзакция будет совершена.

Фактически в выпуске Kit-Kat была представлена ​​строка mFragments.noteStateNotSaved() . Как вы можете видеть комментарий коммита, сделанный Dianne Hackborn:

ActivityFragment должен очистить флаг, что состояние сохраняется, когда оно получает onNewIntent(). Это может произойти до того, как возобновлено, так что мы, возможно, еще не очистили его. Также необходимо сделать то же самое вещь для onActivityResult().

Сумма

Является onActivityResult() теперь гарантированным безопасным местом для транзакций фрагментов?

Предполагая, что вы используете источники, которые включают commit 4ccc001, который был сделан в октябре 2012 года - да, это безопасное место для транзакций фрагментов.