Ошибка доставки результата — onActivityForResult

У меня есть LoginActivity (вход пользователя). По сути, это собственный Activity, оформленный в виде диалога (чтобы он выглядел как диалог). Он появляется над SherlockFragmentActivity. Я хочу: если есть успешный вход в систему, должно быть два FragmentTransaction для обновления представления. Вот код:

В LoginActivity, в случае успешного входа,

setResult(1, new Intent());

In SherlockFragmentActivity:

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

    if (resultCode == 1) {
        LoggedStatus = PrefActivity.getUserLoggedInStatus(this);
        FragmentTransaction t = MainFragmentActivity.this.getSupportFragmentManager().beginTransaction();
        SherlockListFragment mFrag = new MasterFragment();
        t.replace(R.id.menu_frame, mFrag);
        t.commit();

        // Set up Main Screen
        FragmentTransaction t2 = MainFragmentActivity.this.getSupportFragmentManager().beginTransaction();
        SherlockListFragment mainFrag = new FeaturedFragment();
        t2.replace(R.id.main_frag, mainFrag);
        t2.commit();
    }
}

Он падает при первом коммите с этим LogCat:

E/AndroidRuntime(32072): Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
E/AndroidRuntime(32072):    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1299)
E/AndroidRuntime(32072):    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1310)
E/AndroidRuntime(32072):    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:541)
E/AndroidRuntime(32072):    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:525)
E/AndroidRuntime(32072):    at com.kickinglettuce.rate_this.MainFragmentActivity.onActivityResult(MainFragmentActivity.java:243)
E/AndroidRuntime(32072):    at android.app.Activity.dispatchActivityResult(Activity.java:5293)
E/AndroidRuntime(32072):    at android.app.ActivityThread.deliverResults(ActivityThread.java:3315)

person TheLettuceMaster    schedule 28.04.2013    source источник
comment
как вы вызвали startActivityForResult()   -  person Rohit    schedule 12.09.2018


Ответы (5)


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

Звонок commitAllowingStateLoss() больше похож на взлом, чем на исправление. Потеря состояния — это плохо, и ее следует избегать любой ценой. На момент вызова onActivityResult() состояние активности/фрагмента может быть еще не восстановлено, и поэтому любые транзакции, происходящие в это время, будут потеряны. Это очень важная ошибка, которую необходимо устранить! (Обратите внимание, что ошибка возникает только тогда, когда ваш Activity возвращается после того, как система его убила... что, в зависимости от объема памяти устройства, иногда может быть редким... так что этот вид ошибки не является чем-то, что очень легко поймать во время тестирования).

Вместо этого попробуйте переместить свои транзакции в onPostResume() (обратите внимание, что onPostResume() всегда вызывается после onResume(), а onResume() всегда вызывается после onActivityResult()):

private boolean mReturningWithResult = false;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    mReturningWithResult = true;
}

@Override
protected void onPostResume() {
    super.onPostResume();
    if (mReturningWithResult) {
        // Commit your transactions here.
    }
    // Reset the boolean flag back to false for next time.
    mReturningWithResult = false;
}

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

person Alex Lockwood    schedule 20.08.2013
comment
Во фрагментах я использую onResume в отсутствие onPostResume. Я не видел проблему снова, но, возможно, вы хотите прокомментировать это. PS. Большое спасибо за ваши содержательные посты! - person Maragues; 18.09.2013
comment
Да, делать это в Fragment#onResume() можно. Это связано с тем, что FragmentActivity#onPostResume() вызывает FragmentActivity#onResumeFragments(), который вызывает FragmentManager#dispatchResume(), который вызывает Fragment#onResume() для каждого из фрагментов действия. Поэтому Fragment#onResume() вызывается после FragmentActivity#onPostResume(), так что проблем не будет (вы можете проверить [исходный код](goo. gl/Lo1Z1T ) для каждого соответствующего класса, чтобы убедиться в этом самостоятельно... или вы можете просто я :P). И спасибо! Рад, что вы сочли их проницательными. :) - person Alex Lockwood; 19.09.2013
comment
@Alex, если фрагмент диалога выполняет сетевой вызов и кнопка «Домой» нажата до того, как фрагмент диалога получит асинхронный ответ, вызов this.dismiss() в асинхронном ответе вызовет исключение потери состояния. В этом сценарии, когда следует вызывать метод reject(), чтобы не было потери состояния? Обратите внимание, что запрос и ответ находятся внутри диалогового фрагмента, а не внутри действия. - person crazy horse; 29.10.2013
comment
@crazyhorse В этом случае может быть хорошей идеей отменить асинхронную задачу и / или поднять флаг, чтобы гарантировать, что dismiss() не вызывается внутри AsyncTask#onPostExecute() (диалоговое окно будет автоматически закрыто FragmentManager в случае, если действие переходит в фоновом режиме, поэтому вам не нужно самостоятельно закрывать диалоговое окно после того, как активность исчезла). - person Alex Lockwood; 19.12.2013
comment
@AlexLockwood, да, сейчас я использую флаг - я хотел избежать использования здесь логического значения, но в некоторых случаях это кажется неизбежным. - person crazy horse; 27.12.2013
comment
Не знал о onPostResume(). Исправлена ​​моя проблема (я создавал фрагмент диалога в onResume() после отмены системного диалога из ожидающего намерения, что приводило к сбою моего приложения). - person EpicPandaForce; 25.03.2015
comment
Прежде чем внедрять это решение, прочитайте это, чтобы понять, почему это взлом (наряду со многими другими). Реальное решение - это исправление в одну строку. - person twig; 25.05.2015
comment
@twig Я действительно называю super.onActivityResult() в своем ответе (и ОП также делает это в вопросе), поэтому я не уверен, как размещенная вами ссылка решает проблему. - person Alex Lockwood; 25.05.2015
comment
@AlexLockwood Извините за это. Я пропустил эту строку при просмотре вашего решения и смешал ваше исправление с другими хаками. Обновил пост соответственно - person twig; 25.05.2015
comment
@AlexLockwood Спасибо за подробное объяснение. Я все время использовал commitAllowingStateLoss для приложения, которому более 2 лет. Но я никогда не наблюдаю ничего странного. Могу ли я узнать, если происходит потеря состояния, происходит ли это в следующей последовательности? фрагмент диаграммы истории фиксируется в onActivityResult -> происходит восстановление состояния, и диаграмма истории извлекается из диспетчера фрагментов -> вызывается onResume. - person Cheok Yan Cheng; 17.06.2015
comment
@AlexLockwood Кроме того, могу ли я узнать, что состояние восстановления, о котором вы упомянули в своей статье, имеет ли оно какое-либо отношение к onSaveInstanceState(Bundle)? Так как я обычно сохраняю свои переменные в onSaveInstanceState(Bundle) и восстанавливаю их обратно в onCreate(Bundle). Относится ли восстановление состояния к тому, что я делаю в onCreate(Bundle), или это что-то другое? - person Cheok Yan Cheng; 17.06.2015
comment
Означает ли это, что вы никогда не должны делать что-либо (по крайней мере, ссылаться на что-либо) в onActivityResult, кроме как установить resultCode и data в глобальную переменную, а затем выполнять операции в onResume? - person ono; 15.09.2015
comment
@ono Нет, есть еще много способов переопределить onActivityResult(). Вам просто нужно знать, что onActivityResult() вызывается до того, как активность/фрагменты восстановят свое сохраненное состояние. Так, например, если вы начали действие для результата, а затем изменили текст текстового представления по возвращении в onActivityResult(), возможно, что обновленный текст будет перезаписан впоследствии, когда состояние представления действия/фрагмента будет восстановлено. - person Alex Lockwood; 15.09.2015
comment
@AlexLockwood моя проблема в том, что я пытаюсь установить текст в TextView из данных в намерении из onActivityResult, но мое представление равно нулю. Только если андроид удалил мою активность - person ono; 15.09.2015
comment
@AlexLockwood У меня такая же проблема с onRequestPermissionsResult code.google.com/p/ android/issues/detail?id=190966 - person android_dev; 20.10.2015
comment
Начиная с библиотеки поддержки 22.2.1, onActivityResult в Fragment вызывается после onStart. Поэтому я думаю, что если вы хотите показать фрагмент диалога в Fragment.onActivityResult, безопасно вызывать show() напрямую без каких-либо обходных путей. - person Thuy Trinh; 11.01.2016
comment
@ThuyTrinh Есть ли известная вам ошибка, подтверждающая это? В последнее время я не знал о каких-либо изменениях в жизненном цикле фрагмента. - person Alex Lockwood; 11.01.2016
comment
@AlexLockwood спасибо за этот пост. В своем блоге вы говорите, что транзакции должны совершаться после Activity#onPostResume. Но в то же время вы говорите, что их можно закоммитить в Activity#onCreate (который запускается еще до onResume). Я уверен, что я что-то неправильно понял, так что вы можете объяснить. - person nutella_eater; 30.01.2017
comment
пожалуйста, проверьте это stackoverflow.com/questions/43617600/ - person vm345; 28.04.2017
comment
Только что получил эту ошибку на Android O (8.0) с последней библиотекой поддержки 26.0.2. Баг все еще присутствует. Обходной путь в этом ответе устраняет проблему. Я получил эту ошибку после вызова show() на DialogFragment. - person vovahost; 22.09.2017
comment
@alexeypolusov Насколько я понимаю, совершать транзакции из Activity#onCreate безопасно, потому что это гарантированно произойдет до любого восстановления состояния фрагмента, и структура диктует, что это подходящее место для переопределения восстановления фрагмента по умолчанию, если вы выберете. Таким образом, между onCreate и onPostResume есть окно, в котором вам нужно дождаться завершения восстановления состояния, прежде чем совершать транзакции. - person hmac; 12.06.2019
comment
@AlexLockwood: А как насчет этого метода? ``` @OnLifecycleEvent(Lifecycle.Event.ON_START) ``` Я буду использовать это для обнаружения возобновления работы приложения (а не только одного действия) - person Huy Nguyen; 08.01.2020
comment
developer.android.com/reference/android/app/ говорит, что приложения обычно не реализуют этот метод; он предназначен для системных классов, чтобы выполнить окончательную настройку после запуска кода возобновления работы приложения. - Вы уверены, что я должен использовать это? Мое приложение должно открывать разные фрагменты при запуске при определенных условиях. Я использую onResume() основного действия уже несколько лет. Нет проблем, я только что нашел эту тему, потому что я ищу редко встречающееся недопустимое состояние в onSaveInstanceState() этой активности... - person The incredible Jan; 22.01.2020
comment
developer.android.com/reference/android/ support/v4/app/ void onResumeFragments () Это версия onResume(), ориентированная на фрагменты, которую вы можете переопределить для выполнения операций в Activity в той же точке, где возобновляются ее фрагменты. Обязательно всегда звоните суперклассу. - person The incredible Jan; 22.01.2020

Это похоже на ответ @Alex Lockwood, но с использованием Runnable:

private Runnable mOnActivityResultTask;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    mOnActivityResultTask = new Runnable() {
        @Override
        public void run() {
            // Your code here
        }
    }
}

@Override
protected void onPostResume() {
    super.onPostResume();
    if (mOnActivityResultTask != null) {
        mOnActivityResultTask.run();
        mOnActivityResultTask = null;
    }
}

Если вы используете Android 3.0 и выше с лямбда-выражениями, используйте это:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    mOnActivityResultTask = () -> {
        // Your code here
    }
}
person vovahost    schedule 22.09.2017
comment
Я бы предпочел runnable, так как это позволяет вам писать логику близко к тому месту, где это происходит. В моем случае это исходит от абонента rx. - person Fabio; 23.09.2017

Вы можете использовать ft.commitAllowingStateLoss() для решения этой проблемы.

Причина: ваш метод ft.commit() был переключен после onSaveInstanceState.

person muyiou    schedule 16.05.2013

ваш логарифм ясно говорит: "Невозможно выполнить это действие после onSaveInstanceState" -- ваш Activity уже мертв в этот момент и не может вернуть никаких результатов.

просто двигайся:

Intent in = new Intent();
setResult(1, in);

в то место в твоем Activity, где он еще жив, и все будет хорошо. и не забудьте finish() свой Activity доставить результат.

person lenik    schedule 28.04.2013
comment
Спасибо. finish() это последнее, что я делаю. У меня был код намерения прямо над ним. Я переместил его вверх и ту же ошибку. Я добавляю код для ответа... - person TheLettuceMaster; 28.04.2013
comment
Еще одно замечание к последнему комментарию. Я на самом деле не вижу тостов. Но когда я повторно захожу в приложение после сбоя, я вхожу в систему. - person TheLettuceMaster; 28.04.2013

В моем случае я столкнулся с теми же проблемами из-за следующего

public void onBackPressed() {
    super.onBackPressed();
    setIntents();

}


private void setIntents(){
    Intent searchConstaints=new Intent();
    searchConstaints.putExtra("min",20);
    searchConstaints.putExtra("max",80);
    setResult(101,searchConstaints);
    finish();
}

Решено путем реорганизации вызовов функций в onBackPressed().

public void onBackPressed() {

    setIntents();
    super.onBackPressed();

}
person RAJESH KUMAR ARUMUGAM    schedule 04.04.2017