IllegalStateException: не может выполнить это действие после onSaveInstanceState с ViewPager
Я получаю пользовательские отчеты из моего приложения на рынке, за исключением следующего:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyUp(Activity.java:2044)
at android.view.KeyEvent.dispatch(KeyEvent.java:2529)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.widget.TabHost.dispatchKeyEvent(TabHost.java:297)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2880)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2853)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2028)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4028)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
at dalvik.system.NativeStart.main(Native Method)
Очевидно, это как-то связано с FragmentManager, которым я не пользуюсь. В трассировке стека нет ни одного из моих собственных классов, поэтому я понятия не имею, где происходит это исключение и как его предотвратить.
Для записи: у меня есть вкладка, и на каждой вкладке есть группа действий, переключающаяся между действиями.
37 ответов
Пожалуйста, проверьте мой ответ здесь. В основном мне просто нужно было:
@Override
protected void onSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
Не звони super()
на saveInstanceState
метод. Это все испортило...
Это известная ошибка в пакете поддержки.
Если вам нужно сохранить экземпляр и добавить что-то в свой outState
Bundle
Вы можете использовать следующее:
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
super.onSaveInstanceState(outState);
}
В конце концов, правильное решение было (как видно из комментариев) использовать:
transaction.commitAllowingStateLoss();
при добавлении или выполнении FragmentTransaction
это было причиной Exception
,
Есть много проблем, связанных с похожим сообщением об ошибке. Проверьте вторую строку этой конкретной трассировки стека. Это исключение конкретно связано с призывом к FragmentManagerImpl.popBackStackImmediate
,
Этот вызов метода, как popBackStack
, всегда потерпит неудачу с IllegalArgumentException
если состояние сеанса уже было сохранено. Проверьте источник. Вы ничего не можете сделать, чтобы остановить это исключение.
- Удаление звонка
super.onSaveInstanceState
не поможет - Создание фрагмента с
commitAllowingStateLoss
не поможет
Вот как я заметил проблему:
- Есть форма с кнопкой отправки.
- При нажатии кнопки создается диалоговое окно и запускается асинхронный процесс.
- Пользователь нажимает клавишу дома до завершения процесса -
onSaveInstanceState
называется. - Процесс завершается, выполняется обратный вызов и
popBackStackImmediate
попытка IllegalStateException
брошен
Вот что я сделал, чтобы решить это:
Поскольку невозможно избежать IllegalStateException
в обратном вызове лови и игнорируй это.
try {
activity.getSupportFragmentManager().popBackStackImmediate(name);
} catch (IllegalStateException ignored) {
// There's no way to avoid getting this if saveInstanceState has already been called.
}
Этого достаточно, чтобы остановить сбой приложения. Но теперь пользователь восстановит приложение и увидит, что кнопка, которую, как они думали, нажали, вообще не была нажата (они думают). Фрагмент формы все еще показывает!
Чтобы исправить это, когда диалоговое окно создано, создайте некоторое состояние, чтобы указать, что процесс запущен.
progressDialog.show(fragmentManager, TAG);
submitPressed = true;
И сохраните это состояние в связке.
@Override
public void onSaveInstanceState(Bundle outState) {
...
outState.putBoolean(SUBMIT_PRESSED, submitPressed);
}
Не забудьте загрузить его снова в onViewCreated
Затем при возобновлении отката фрагменты, если ранее была предпринята попытка отправки. Это предотвращает возвращение пользователя к тому, что выглядит как неподтвержденная форма.
@Override
public void onResume() {
super.onResume();
if (submitPressed) {
// no need to try-catch this, because we are not in a callback
activity.getSupportFragmentManager().popBackStackImmediate(name);
submitPressed = false;
}
}
Проверьте, если деятельность isFinishing()
перед показом фрагмента и обратите внимание на commitAllowingStateLoss()
,
Пример:
if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
DummyFragment dummyFragment = DummyFragment.newInstance();
ft.add(R.id.dummy_fragment_layout, dummyFragment);
ft.commitAllowingStateLoss();
}
Это октябрь 2017 года, и Google создает библиотеку поддержки Android с новым компонентом Lifecycle. Это дает новую идею для этой проблемы "Не удается выполнить это действие после onSaveInstanceState".
Короче:
- Используйте компонент жизненного цикла, чтобы определить, является ли это правильным временем для появления вашего фрагмента.
Более длинная версия с объяснением:
почему эта проблема выходит?
Это потому, что вы пытаетесь использовать
FragmentManager
из вашей деятельности (которая будет содержать ваш фрагмент, я полагаю?), чтобы совершить транзакцию для вас фрагмент. Обычно это выглядит так, как будто вы пытаетесь выполнить некоторую транзакцию для следующего фрагмента, в то время как действия хоста уже вызываютsavedInstanceState
метод (пользователь может коснуться кнопки "Домой", так что активность вызываетonStop()
в моем случае это и есть причина)Обычно этой проблемы не должно быть - мы всегда пытаемся загрузить фрагмент в действие в самом начале, например,
onCreate()
метод является идеальным местом для этого. Но иногда это случается, особенно когда вы не можете решить, какой фрагмент вы будете загружать для этого действия, или вы пытаетесь загрузить фрагмент изAsyncTask
блок (или что-нибудь займет немного времени). Время до того, как транзакция фрагмента действительно произойдет, но послеonCreate()
метод, пользователь может сделать что угодно. Если пользователь нажимает кнопку "Домой", которая вызывает активностьonSavedInstanceState()
метод, было быcan not perform this action
авария.Если кто-то хочет глубже в этом вопросе, я предлагаю им взглянуть на этот пост в блоге. Он смотрит глубоко в слой исходного кода и многое объясняет по этому поводу. Кроме того, это дает причину того, что вы не должны использовать
commitAllowingStateLoss()
способ обойти эту ошибку (поверьте мне, она не предлагает ничего хорошего для вашего кода)Как это исправить?
Должен ли я использовать
commitAllowingStateLoss()
способ загрузить фрагмент? Нет, ты не должен;Должен ли я переопределить
onSaveInstanceState
метод, игнорируйsuper
метод внутри него? Нет, ты не должен;Должен ли я использовать магический
isFinishing
внутри активности, чтобы проверить, находится ли активность хоста в нужный момент для транзакции фрагмента? Да, это похоже на правильный способ сделать.
Посмотрите, что может сделать компонент жизненного цикла.
По сути, Google делает некоторые реализации внутри
AppCompatActivity
класс (и несколько других базовых классов, которые вы должны использовать в своем проекте), что упрощает определение текущего состояния жизненного цикла. Вспомните нашу проблему: почему эта проблема возникла? Это потому, что мы делаем что-то не в то время. Поэтому мы стараемся этого не делать, и эта проблема исчезнет.Я немного пишу для своего проекта, вот что я делаю, используя
LifeCycle
, Я пишу код в Котлине.
val hostActivity: AppCompatActivity? = null // the activity to host fragments. It's value should be properly initialized.
fun dispatchFragment(frag: Fragment) {
hostActivity?.let {
if(it.lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)){
showFragment(frag)
}
}
}
private fun showFragment(frag: Fragment) {
hostActivity?.let {
Transaction.begin(it, R.id.frag_container)
.show(frag)
.commit()
}
Как я покажу выше. Я проверю состояние жизненного цикла активности хоста. С компонентом жизненного цикла в библиотеке поддержки это может быть более конкретным. Код lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)
означает, что если текущее состояние хотя бы onResume
не позже чем это? Что гарантирует, что мой метод не будет выполняться во время какого-либо другого состояния жизни (например, onStop
).
Это все сделано?
Конечно, нет. Код, который я показал, говорит о каком-то новом способе предотвращения сбоя приложения. Но если это пойти в состояние
onStop
, эта строка кода не будет делать что-то и, следовательно, ничего не показывает на вашем экране. Когда пользователи возвращаются в приложение, они видят пустой экран, это пустое действие хоста, на котором нет фрагментов вообще. Это плохой опыт (да, немного лучше, чем крушение).Так что здесь я хотел бы, чтобы могло быть что-то более приятное: приложение не зависнет, если оно вернется в состояние позже
onResume
метод транзакции осведомлен о состоянии жизни; кроме того, после того, как пользователь вернется в наше приложение, действие будет пытаться продолжить выполнение этого фрагмента транзакции.Я добавлю кое-что еще к этому методу:
class FragmentDispatcher(_host: FragmentActivity) : LifecycleObserver {
private val hostActivity: FragmentActivity? = _host
private val lifeCycle: Lifecycle? = _host.lifecycle
private val profilePendingList = mutableListOf<BaseFragment>()
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun resume() {
if (profilePendingList.isNotEmpty()) {
showFragment(profilePendingList.last())
}
}
fun dispatcherFragment(frag: BaseFragment) {
if (lifeCycle?.currentState?.isAtLeast(Lifecycle.State.RESUMED) == true) {
showFragment(frag)
} else {
profilePendingList.clear()
profilePendingList.add(frag)
}
}
private fun showFragment(frag: BaseFragment) {
hostActivity?.let {
Transaction.begin(it, R.id.frag_container)
.show(frag)
.commit()
}
}
}
Я веду список в этом dispatcher
класс, чтобы сохранить эти фрагменты не имеют возможности завершить действие транзакции. И когда пользователь возвращается с домашнего экрана и обнаруживает, что еще есть фрагмент, ожидающий запуска, он перейдет к resume()
метод под @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
аннотаций. Теперь я думаю, что это должно работать так, как я ожидал.
Краткое и рабочее решение:
Следуйте простым шагам
меры
Шаг 1: переопределить onSaveInstanceState
состояние в соответствующем фрагменте. И удалите супер метод из него.
@Override
public void onSaveInstanceState( Bundle outState ) {
}
Шаг 2: Используйте fragmentTransaction.commitAllowingStateLoss( );
вместо fragmentTransaction.commit( );
пока фрагмент операции.
Вот другое решение этой проблемы.
Используя закрытую переменную-член, вы можете установить возвращаемые данные как намерение, которое затем может быть обработано после super.onResume();
Вот так:
private Intent mOnActivityResultIntent = null;
@Override
protected void onResume() {
super.onResume();
if(mOnActivityResultIntent != null){
... do things ...
mOnActivityResultIntent = null;
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
if(data != null){
mOnActivityResultIntent = data;
}
}
ВНИМАНИЕ, используя transaction.commitAllowingStateLoss()
может привести к плохому опыту для пользователя. Для получения дополнительной информации о том, почему это исключение выдается, смотрите этот пост.
Я нашел грязное решение для такой проблемы. Если вы все еще хотите сохранить свой ActivityGroups
по какой-либо причине (у меня были причины ограничения времени), вы просто реализуете
public void onBackPressed() {}
в вашем Activity
и сделать некоторые back
код там. даже если на старых устройствах такого метода нет, этот метод вызывается более новыми.
Не используйте commitAllowingStateLoss(), его следует использовать только в тех случаях, когда это нормально, когда состояние пользовательского интерфейса неожиданно меняется для пользователя.
https://developer.android.com/reference/android/app/FragmentTransaction.html
Если транзакция происходит в ChildFragmentManager of parentFragment, используйте для проверки parentFragment.isResume() снаружи.
if (parentFragment.isResume()) {
DummyFragment dummyFragment = DummyFragment.newInstance();
transaction = childFragmentManager.BeginTransaction();
trans.Replace(Resource.Id.fragmentContainer, startFragment);
}
У меня была похожая проблема, сценарий был такой:
- Моя деятельность - добавление / замена фрагментов списка.
- Каждый фрагмент списка имеет ссылку на действие, чтобы уведомить действие при щелчке элемента списка (шаблон наблюдателя).
- Каждый фрагмент списка вызывает setRetainInstance(true); в его методе onCreate.
Метод действия onCreate был таким:
mMainFragment = (SelectionFragment) getSupportFragmentManager()
.findFragmentByTag(MAIN_FRAGMENT_TAG);
if (mMainFragment == null) {
mMainFragment = new SelectionFragment();
mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
R.layout.item_main_menu, getResources().getStringArray(
R.array.main_menu)));
mMainFragment.setOnSelectionChangedListener(this);
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
transaction.commit();
}
Исключение было вызвано тем, что при изменении конфигурации (устройство поворачивается) создается действие, основной фрагмент извлекается из истории диспетчера фрагментов, и в то же время фрагмент уже имеет ссылку OLD на уничтоженное действие.
Изменение реализации для этого решило проблему:
mMainFragment = (SelectionFragment) getSupportFragmentManager()
.findFragmentByTag(MAIN_FRAGMENT_TAG);
if (mMainFragment == null) {
mMainFragment = new SelectionFragment();
mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
R.layout.item_main_menu, getResources().getStringArray(
R.array.main_menu)));
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
transaction.commit();
}
mMainFragment.setOnSelectionChangedListener(this);
Вы должны устанавливать своих слушателей каждый раз, когда создается действие, чтобы избежать ситуации, когда фрагменты имеют ссылки на старые уничтоженные экземпляры действия.
Фрагментные транзакции не должны выполняться после Activity.onStop()
! Убедитесь, что у вас нет обратных вызовов, которые могли бы выполнить транзакцию послеonStop()
. Лучше устранить причину, чем пытаться обходить проблему подходами вроде.commitAllowingStateLoss()
Если вы наследуете от FragmentActivity
Вы должны вызвать суперкласс в onActivityResult()
:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
...
}
Если вы не сделаете этого и попытаетесь показать диалоговое окно фрагмента в этом методе, вы можете получить OP IllegalStateException
, (Если честно, я не совсем понимаю, почему супер-вызов решает проблему. onActivityResult()
называется раньше onResume()
поэтому нельзя показывать диалоговое окно фрагмента.)
Возможно, самое гладкое и простое решение, которое я нашел в моем случае, состояло в том, чтобы избежать выталкивания поврежденного фрагмента из стека в ответ на результат активности. Так что меняю этот звонок по моему onActivityResult()
:
popMyFragmentAndMoveOn();
к этому:
new Handler(Looper.getMainLooper()).post(new Runnable() {
public void run() {
popMyFragmentAndMoveOn();
}
}
помогло в моем случае.
как вы можете видеть в своем отчете о сбое, последняя строка, выдающая исключение,
checkStateLoss(FragmentManager.java:1109)
если посмотреть на реализацию checkStateLoss
private void checkStateLoss() {
if (isStateSaved()) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
}
поэтому простое решение для меня состоит в том, чтобы найти какой-либо метод диспетчера фрагментов, который вы вызываете в своем приложении, который в конечном итоге приводит к вызову этого метода, и просто проверить, является ли isStateSaved() ложным перед вызовом этого метода. Для меня это был метод show(). мне понравилось
if (!isStateSaved()) {
myDialog.show(fragmentManager, Tag)
}
Я получал это исключение, когда нажимал кнопку "Назад", чтобы отменить выбор намерения на фрагменте карты. Я решил это, заменив код onResume(где я инициализировал фрагмент) на onstart(), и приложение работает нормально. Надеюсь, это поможет.
Вежливость: решение для IllegalStateException
Эта проблема раздражала меня много времени, но, к счастью, я нашел для нее конкретное решение. Подробное объяснение этого здесь.
Использование commitAllowStateloss() может предотвратить это исключение, но приведет к нарушениям пользовательского интерфейса. До сих пор мы понимали, что IllegalStateException встречается, когда мы пытаемся зафиксировать фрагмент после потери состояния Activity, поэтому мы должны просто отложить транзакцию до восстановления состояния. Это можно сделать просто так
Объявите две приватные логические переменные
public class MainActivity extends AppCompatActivity {
//Boolean variable to mark if the transaction is safe
private boolean isTransactionSafe;
//Boolean variable to mark if there is any transaction pending
private boolean isTransactionPending;
Теперь в onPostResume() и onPause мы устанавливаем и удаляем нашу логическую переменную isTransactionSafe. Идея состоит в том, чтобы помечать транзакции как безопасные только тогда, когда активность находится на переднем плане, поэтому нет шансов потерять состояние.
/*
onPostResume is called only when the activity's state is completely restored. In this we will
set our boolean variable to true. Indicating that transaction is safe now
*/
public void onPostResume(){
super.onPostResume();
isTransactionSafe=true;
}
/*
onPause is called just before the activity moves to background and also before onSaveInstanceState. In this
we will mark the transaction as unsafe
*/
public void onPause(){
super.onPause();
isTransactionSafe=false;
}
private void commitFragment(){
if(isTransactionSafe) {
MyFragment myFragment = new MyFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.frame, myFragment);
fragmentTransaction.commit();
}
}
-То, что мы сделали до сих пор, спасет от IllegalStateException, но наши транзакции будут потеряны, если они будут выполнены после того, как действие переместится в фоновый режим, что-то вроде commitAllowStateloss(). Чтобы помочь с этим, у нас есть логическая переменная isTransactionPending
public void onPostResume(){
super.onPostResume();
isTransactionSafe=true;
/* Here after the activity is restored we check if there is any transaction pending from
the last restoration
*/
if (isTransactionPending) {
commitFragment();
}
}
private void commitFragment(){
if(isTransactionSafe) {
MyFragment myFragment = new MyFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.frame, myFragment);
fragmentTransaction.commit();
isTransactionPending=false;
}else {
/*
If any transaction is not done because the activity is in background. We set the
isTransactionPending variable to true so that we can pick this up when we come back to
foreground
*/
isTransactionPending=true;
}
}
Всякий раз, когда вы пытаетесь загрузить фрагмент в вашей активности, убедитесь, что активность возобновлена и она не будет находиться в состоянии паузы. В состоянии паузы вы можете в конечном итоге потерять выполненную операцию фиксации.
Вы можете использовать транзакцию mit.comlitelStateLoss() вместо транзакции.com () для загрузки фрагмента.
или же
Создайте логическое значение и проверьте, не прекращается ли активность
@Override
public void onResume() {
super.onResume();
mIsResumed = true;
}
@Override
public void onPause() {
mIsResumed = false;
super.onPause();
}
тогда при загрузке проверяйте фрагмент
if(mIsResumed){
//load the your fragment
}
Что касается @Anthonyeef отличный ответ, вот пример кода на Java:
private boolean shouldShowFragmentInOnResume;
private void someMethodThatShowsTheFragment() {
if (this.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
showFragment();
} else {
shouldShowFragmentInOnResume = true;
}
}
private void showFragment() {
//Your code here
}
@Override
protected void onResume() {
super.onResume();
if (shouldShowFragmentInOnResume) {
shouldShowFragmentInOnResume = false;
showFragment();
}
}
Исключение выдается здесь (в FragmentActivity):
@Override
public void onBackPressed() {
if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
super.onBackPressed();
}
}
В FragmentManager.popBackStatckImmediate()
,FragmentManager.checkStateLoss()
называется во-первых. Это причина IllegalStateException
, Смотрите реализацию ниже:
private void checkStateLoss() {
if (mStateSaved) { // Boom!
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException(
"Can not perform this action inside of " + mNoTransactionsBecause);
}
}
Я решаю эту проблему, просто используя флаг, чтобы отметить текущий статус деятельности. Вот мое решение:
public class MainActivity extends AppCompatActivity {
/**
* A flag that marks whether current Activity has saved its instance state
*/
private boolean mHasSaveInstanceState;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
mHasSaveInstanceState = true;
super.onSaveInstanceState(outState);
}
@Override
protected void onResume() {
super.onResume();
mHasSaveInstanceState = false;
}
@Override
public void onBackPressed() {
if (!mHasSaveInstanceState) {
// avoid FragmentManager.checkStateLoss()'s throwing IllegalStateException
super.onBackPressed();
}
}
}
Расширение Kotlin
fun FragmentManager?.replaceAndAddToBackStack(
@IdRes containerViewId: Int,
fragment: () -> Fragment,
tag: String
) {
// Find and synchronously remove a fragment with the same tag.
// The second transaction must start after the first has finished.
this?.findFragmentByTag(tag)?.let {
beginTransaction().remove(it).commitNow()
}
// Add a fragment.
this?.beginTransaction()?.run {
replace(containerViewId, fragment, tag)
// The next line will add the fragment to a back stack.
// Remove if not needed.
// You can use null instead of tag, but tag is needed for popBackStack(),
// see https://stackru.com/a/59158254/2914140
addToBackStack(tag)
}?.commitAllowingStateLoss()
}
Применение:
val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)
Если у вас произошел сбой с помощью метода popBackStack() или popBackStackImmediate(), попробуйте исправить с помощью:
if (!fragmentManager.isStateSaved()) {
fragmentManager.popBackStackImmediate();
}
Это работает и для меня.
Этот сбой происходит из-за того, что FragmentTransaction фиксируется после того, как жизненный цикл принадлежащего ему Activity уже запущен на SaveInstanceState. Это часто вызвано фиксацией FragmentTransaction из асинхронного обратного вызова. Посетите связанный ресурс для получения более подробной информации.
Фрагментные транзакции и потеря состояния активности
http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
Если вы выполняете какую-либо FragmentTransaction в onActivityResult, то, что вы можете сделать, вы можете установить некоторое логическое значение внутри onActivityResult, тогда в onResume вы можете выполнить FragmentTransaction на основе логического значения. Пожалуйста, ознакомьтесь с кодом ниже.
@Override
protected void onResume() {
super.onResume;
if(isSwitchFragment){
isSwitchFragment=false;
bottomNavigationView.getTabAt(POS_FEED).select();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == FilterActivity.FILTER_REQUEST_EVENT && data != null) {
isSwitchFragment=true;
}
}
Я думаю, используя transaction.commitAllowingStateLoss();
не лучшее решение Это исключение будет сгенерировано при изменении конфигурации действия и фрагментации onSavedInstanceState()
вызывается и после этого ваш метод асинхронного обратного вызова пытается зафиксировать фрагмент.
Простое решение может быть проверить, меняет ли деятельность конфигурацию или нет
например проверить isChangingConfigurations()
т.е.
if(!isChangingConfigurations()) {
//commit transaction.
}
Проверьте эту ссылку, а также
В моем случае я получил эту ошибку в методе переопределения onActivityResult. После копания я просто думаю, что мне нужно было позвонить "супер" раньше.
Я добавил это, и это просто сработало
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data); //<--- THIS IS THE SUPPER CALL
if (resultCode == Activity.RESULT_OK && requestCode == 0) {
mostrarFragment(FiltroFragment.newInstance())
}
}
Может быть, вам просто нужно добавить super в любое переопределение, которое вы делаете перед вашим кодом.
измените getFragmentManager() на getChildFragmentManager() . Не используйте родительский FragmentManager, попробуйте использовать себя.
Чтобы обойти эту проблему, мы можем использовать Компонент архитектуры навигации, который был представлен в Google I/O 2018. Компонент архитектуры навигации упрощает реализацию навигации в приложении Android.
Начиная с версии библиотеки поддержки 24.0.0 вы можете позвонить FragmentTransaction.commitNow()
метод, который фиксирует эту транзакцию синхронно вместо вызова commit()
с последующим executePendingTransactions()
, Как сказано в документации, этот подход еще лучше:
Вызов commitNow предпочтительнее вызова commit(), за которым следует executePendingTransactions(), поскольку последний будет иметь побочный эффект при попытке зафиксировать все ожидающие транзакции, независимо от того, является ли это желаемым поведением или нет.
Я знаю, что @Ovidiu Latcu принял приемлемый ответ, но через некоторое время ошибка все еще сохраняется.
@Override
protected void onSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
Crashlytics все еще посылает мне это странное сообщение об ошибке.
Однако теперь ошибка возникает только в версии 7+ (Nougat). Мое исправление заключалось в том, чтобы использовать commitAllowingStateLoss() вместо commit() для фрагмента транзакции.
Этот пост полезен для commitAllowingStateLoss() и никогда больше не сталкивался с проблемой фрагментов.
Подводя итог, можно сказать, что принятый ответ может работать на версиях для Android до Nougat.
Это может сэкономить кому-то несколько часов поиска. счастливых кодировок. <3 ура
Другой возможный обходной путь, который я не уверен, помогает ли во всех случаях (происхождение здесь):
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
final View rootView = findViewById(android.R.id.content);
if (rootView != null) {
rootView.cancelPendingInputEvents();
}
}
}