IllegalStateException:使用ViewPager在onSaveInstanceState之后无法执行此操作


496

我从市场上的应用程序中获取用户报告,但出现以下异常:

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有关,我不使用。stacktrace不显示我自己的任何类,因此我不知道发生此异常的位置以及如何防止它。

作为记录:我有一个tabhost,在每个选项卡中都有一个ActivityGroup在Activity之间切换。


2
我发现这个问题讨论同样的问题,但没有解决办法有两种.. stackoverflow.com/questions/7469082/...
nhaarman

3
当您不使用时FragmentManager,Honeycomb当然是。这是在真正的蜂窝片上发生的吗?还是有人在手机上运行了被入侵的Honeycomb之类的东西,或者是被入侵的版本出了问题?
CommonsWare

1
我不知道。这是笔者在市场开发者控制台中得到的唯一信息,用户信息不包含任何有用的信息要么..
nhaarman

我正在使用Flurry,它向我显示了11个使用Android 3.0.1的会话,并且我有11个有关此异常的报告。可能是巧合。Android 3.1和3.2分别具有56和38个会话。
nhaarman 2011年

市场错误报告的“平台”部分,有时包含设备的Android版本。
Nikolay Elenkov 2011年

Answers:


720

在这里检查我的答案。基本上我只需要:

@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


370
您应该使用commitAllowingStateLoss()而不是commit()
2012年

18
关于commitAllowingStateLoss()的评论本身就是一个答案-您应该这样发布。
里沙迪尼亚

20
关于'commitAllowingStateLoss'-/>“这很危险,因为如果以后需要将其状态从活动中恢复,则提交可能会丢失,因此,仅在UI状态意外发生变化的情况下才可以使用该提交。用户。”
2013年

10
如果我查看v4源,popBackStackImmediate如果状态已保存,则立即失败。之前添加的片段commitAllowingStateLoss没有任何作用。我的测试表明这是正确的。它对此特定异常没有影响。我们需要的是一种popBackStackImmediateAllowingStateLoss方法。
Synesso 2015年

3
@DanieleB是的,我在这里发布了答案。但实际上,我已经通过使用Otto消息总线找到了一个更好的解决方案:将片段注册为订户,并监听总线的异步结果。暂停时注销,然后在恢复时重新注册。异步还需要在完成和片段暂停时使用Produce方法。当我有时间的时候,我将更详细地更新我的答案。
Synesso

130

有许多类似的错误消息相关的问题。检查此特定堆栈跟踪的第二行。此异常与对的调用特别相关FragmentManagerImpl.popBackStackImmediate

如果已保存会话状态popBackStack,则此方法调用(如)将始终失败IllegalStateException。检查源。您无法阻止该异常被抛出。

  • 删除对的呼叫super.onSaveInstanceState不会有帮助。
  • 使用创建片段commitAllowingStateLoss将无济于事。

这是我观察问题的方式:

  • 有一个带有提交按钮的表格。
  • 单击该按钮后,将创建一个对话框并开始异步过程。
  • 用户在处理完成之前单击Home键-会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;
    }
}

5
在这里有趣的阅读:androiddesignpatterns.com/2013/08/…–
Pascal

如果您使用DialogFragment,我在这里做了另一种选择:github.com/AndroidDeveloperLB/DialogShard
android开发人员

如果popBackStackImmediate本身被Android调用怎么办?
Kimi Chiu

1
绝对好 这个应该是公认的答案。非常感谢你!也许我会添加SubmitPressed = false; 在popBackStackInmediate之后。
Neonigma

我没有使用public void onSaveInstanceState(Bundle outState)方法。我是否需要为public void onSaveInstanceState(Bundle outState)设置空方法?
Faxriddin Abdullayev

55

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

1
!isFinishing()和&!isDestroyed()对我不起作用。
艾伦·沃克

!isFinishing()和&!isDestroyed()对我有用,但它需要API17。但是它根本不显示DialogFragment。见stackoverflow.com/questions/15729138/...其他好的解决办法,stackoverflow.com/a/41813953/2914140帮助了我。
CoolMind

29

现在是2017年10月,Google推出了名为Lifecycle组件的新产品,从而打造了Android支持库。它为“ onSaveInstanceState之后无法执行此操作”问题提供了一些新思路。

简而言之:

  • 使用生命周期组件来确定是否是弹出片段的正确时间。

较长版本的说明:

  • 为什么这个问题出来?

    这是因为您正在尝试使用FragmentManager您的活动(我想这将保留您的片段?)来为您的片段提交事务。通常这看起来像是您正在尝试为即将发生的片段做一些事务,与此同时主机活动已经调用了savedInstanceState方法(用户可能碰巧触摸了home按钮,因此活动调用onStop(),在我的情况下,这就是原因)

    通常不应该发生此问题-我们总是从一开始就尝试将片段加载到活动中,就像该onCreate()方法是实现此目的的理想之地。但是有时确实会发生这种情况,特别是当您无法确定要向该活动加载哪个片段,或者您试图从一个AsyncTask块加载片段时(否则任何事情都会花费一些时间)。片段事务真正发生之前的时间,但是在活动onCreate()方法执行之后,用户可以执行任何操作。如果用户按下主页按钮,这会触发活动的onSavedInstanceState()方法,则可能会can not perform this action导致崩溃。

    如果有人想深入了解此问题,建议他们阅读此博客文章。它在源代码层内部看起来很深,并对此做了很多解释。另外,它给出了您不应使用该commitAllowingStateLoss()方法来解决此崩溃的原因(相信我,它对您的代码没有任何帮助)

  • 如何解决这个问题?

    • 我应该使用commitAllowingStateLoss()方法加载片段吗?不,你不应该

    • 我应该重写onSaveInstanceState方法,忽略其中的super方法吗?不,你不应该

    • 我是否应该使用神奇的isFinishing内部活动来检查主机活动是否在正确的时间进行片段事务?是的,这看起来像是正确的方法。

  • 看一下Lifecycle组件可以做什么。

    基本上,Google在AppCompatActivity类(以及您应在项目中使用的其他几个基类)中进行一些实现,这使确定当前生命周期状态变得更加容易。回头看看我们的问题:为什么会发生这个问题?这是因为我们在错误的时间做某事。因此,我们尝试不执行此操作,此问题将消失。

    我为自己的项目编写了一些代码,这是我使用的代码LifeCycle。我用Kotlin编写代码。

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

如上所示。我将检查主机活动的生命周期状态。通过支持库中的Lifecycle组件,这可能更加具体。该代码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)。现在,我认为它应该可以按预期运行。


8
使用Java代替Kotlin很好
Shchvova 18-4-27的

1
FragmentDispatcher如果仅还原一个片段,为什么使用列表实现存储未决片段?
fraherm

21

这是解决此问题的另一种方法。

使用私有成员变量,您可以将返回的数据设置为intent,然后可以在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;
    }
}

7
根据不允许您执行的操作,这可能需要移到比onResume()还要晚的时刻。对于insatcne,如果出现FragmentTransaction.commit()是问题,则需要改为onPostResume()。
pjv 2012年

1
这是对我这个问题的答案。由于我需要将收到的NFC标签转发到上一个活动,因此这就是我所做的。
Janis Peisenieks

7
对我来说,这是发生的,因为我没有打电话super.onActivityResult()
苏菲安2015年

20

简短而有效的解决方案:

遵循简单的步骤

脚步

步骤1:onSaveInstanceState在各个片段中覆盖状态。并从中删除超级方法。

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

第2步:使用 fragmentTransaction.commitAllowingStateLoss( );

而不是 fragmentTransaction.commit( ); while片段操作。


答案未复制或通过其他方式提交。我的工作解决方案是通过几次试验和失败而张贴的,以帮助人们
Vinayak

12

注意,使用transaction.commitAllowingStateLoss()可能会给用户带来糟糕的体验。关于为什么这个异常被抛出的更多信息,请参阅这篇文章


6
这不提供问题的答案,您必须提供问题的有效答案
Umar Ata

10

我找到了解决此类问题的解决方案。如果您仍然ActivityGroups出于某种原因(我有时间限制的原因)仍要保留您的身份,则只需实施

public void onBackPressed() {}

在你那里Activity做一些back代码。即使较旧的设备上没有这种方法,该方法也会被较新的设备调用。


6

不要使用commitAllowingStateLoss(),仅应将UI状态可以在用户上意外更改的情况下使用。

https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss()

如果交易发生在parentFragment的ChildFragmentManager中,请改用 parentFragment.isResume()进行检查。

if (parentFragment.isResume()) {
    DummyFragment dummyFragment = DummyFragment.newInstance();
    transaction = childFragmentManager.BeginTransaction();
    trans.Replace(Resource.Id.fragmentContainer, startFragment);
}

5

我有一个类似的问题,情况是这样的:

  • 我的活动是添加/替换列表片段。
  • 每个列表片段都有对活动的引用,以便在单击列表项时(观察者模式)通知活动。
  • 每个列表片段都调用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);

您需要在每次创建活动时都设置您的侦听器,以避免碎片引用活动的旧破坏实例的情况。


5

如果您继承自FragmentActivity,则必须在中调用超类onActivityResult()

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

如果不这样做,并尝试在该方法中显示一个片段对话框,则可能会得到OP的IllegalStateException。(说实话,我不太理解为什么超级调用解决了这个问题。onActivityResult()在之前被调用了onResume(),因此仍然不允许显示片段对话框。)


1
很想知道为什么这可以解决问题。
大麦克拉格

3

当我按“后退”按钮取消地图片段活动中的意图选择器时,出现此异常。我通过将onResume(我在其中初始化片段的代码)替换为onstart()来解决此问题,该应用程序运行正常。希望它会有所帮助。


2

我认为使用transaction.commitAllowingStateLoss();不是最佳解决方案。当活动的配置发生更改并onSavedInstanceState()调用fragment ,然后您的异步回调方法尝试提交fragment 时,将引发此异常。

一个简单的解决方案是检查活动是否正在更改配置

例如检查 isChangingConfigurations()

if(!isChangingConfigurations()) { //commit transaction. }

同时检查链接


当用户单击某项时(单击是执行transaction-commit的触发器),我就以某种方式得到了此异常。怎么会这样 您的解决方案在这里吗?
Android开发人员

@androiddeveloper您在用户点击时还要做些什么。在您提交事务之前,片段以某种方式保存了它的状态
Amol Desai

在事务提交的确切行上引发了异常。另外,我还有一个奇怪的错字:不是“在这里工作”,而是“在这里工作”。
Android开发人员

@androiddeveloper你是对的!但是在提交事务之前,您是否生成了任何后台线程之类的东西?
Amol Desai

我不这么认为(对不起,我不在办公室),但是为什么会这么重要?这就是UI的所有内容...如果我曾经在后台线程上做某事,那我就会有例外,此外,我也不会将与UI相关的东西放在后台线程上,因为这样做太冒险了。
Android开发人员

2

在我的案例中,找到的最流畅,最简单的解决方案可能是避免响应活动结果而将有问题的片段从堆栈中弹出。所以在我的改变这个电话onActivityResult()

popMyFragmentAndMoveOn();

对此:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    public void run() {
        popMyFragmentAndMoveOn();
    }
}

对我来说有帮助。


2

如果要在onActivityResult中执行一些FragmentTransaction,可以在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;
    }
}

请不要将代码发布为图像,而应使用代码格式。
米瑟

1
是的,它帮助了我..,这是棉花糖设备的确切和正确的解决方案,我得到了这个例外,并用这个简单的技巧解决了..!因此,投票赞成。
sandhya sasane 18/09/16

2

礼貌:IllegalStateException的解决方案

这个问题困扰了我很多时间,但是幸运的是我提供了一个具体的解决方案。它的详细解释在这里

使用commitAllowStateloss()可能会防止此异常,但会导致UI不正常。到目前为止,我们已经了解到,当我们在Activity状态丢失后尝试提交片段时会遇到IllegalStateException-因此我们应该将事务延迟到状态恢复之前可以像这样简单地完成

声明两个私有布尔变量

 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;
 }
}

2

片段事务之后不应该执行Activity.onStop() 检查之后没有任何可以执行事务的回调onStop()。最好解决原因,而不是尝试使用类似的方法解决问题.commitAllowingStateLoss()


1

从支持库版本开始24.0.0可以调用FragmentTransaction.commitNow()方法,同步犯,而不是调用这个交易commit()之后executePendingTransactions()。正如文档所述,这种方法更好:

调用commitNow比调用commit()再调用executePendingTransactions()更可取,因为后者会产生尝试提交所有当前未决事务的副作用,无论这是否是所需行为。


1

每当您尝试在活动中加载片段时,请确保活动处于恢复状态且不会进入暂停状态。在暂停状态下,您可能最终会丢失已完成的提交操作。

您可以使用transaction.commitAllowingStateLoss()而不是transaction.commit()来加载片段

要么

创建一个布尔值,并检查活动是否不会暂停

@Override
public void onResume() {
    super.onResume();
    mIsResumed = true;
}

@Override
public void onPause() {
    mIsResumed = false;
    super.onPause();
}

然后在加载片段检查时

if(mIsResumed){
//load the your fragment
}

1

为了绕过这个问题,我们可以使用导航建筑构件,这是在谷歌I / O 2018的导航建筑构件简化了导航的Android应用中的实施过程中引入。


这还不够好,而且存在错误(错误的深层链接处理,无法保存状态显示/隐藏片段以及一些仍悬而未决的重要问题)
do01年

1

关于@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();
    }
}

1

如果您使用popBackStack()或popBackStackImmediate()方法崩溃,请尝试通过以下方法修复:

        if (!fragmentManager.isStateSaved()) {
            fragmentManager.popBackStackImmediate();
        }

这也为我工作。


请注意,它需要API 26及更高版本
Itay Feldman

1

就我而言,我在名为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”即可。


1

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://stackoverflow.com/a/59158254/2914140
        addToBackStack(tag)
    }?.commitAllowingStateLoss()
}

用法:

val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)

您可以在Fragment
Claire

@克莱尔,谢谢!你是说要变fragment: Fragment吗?是的,我尝试了这种变体,但是在这种情况下,在所有情况下都将创建一个片段(即使fragmentManager == null,但我没有遇到这种情况)。我更新了答案,并将null更改为标记addToBackStack()
CoolMind


0

在您的活动中添加

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (outState.isEmpty()) {
        // Work-around for a pre-Android 4.2 bug
        outState.putBoolean("bug:fix", true);
    }
}

0

我也遇到了这个问题,每次您FragmentActivity更改上下文时都会发生问题(例如,更改屏幕方向等)。因此,最好的解决方法是从中更新上下文FragmentActivity


0

我最终创建了一个基本片段,并让我的应用程序中的所有片段都对其进行了扩展

public class BaseFragment extends Fragment {

    private boolean mStateSaved;

    @CallSuper
    @Override
    public void onSaveInstanceState(Bundle outState) {
        mStateSaved = true;
        super.onSaveInstanceState(outState);
    }

    /**
     * Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
     * would otherwise occur.
     */
    public void showAllowingStateLoss(FragmentManager manager, String tag) {
        // API 26 added this convenient method
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (manager.isStateSaved()) {
                return;
            }
        }

        if (mStateSaved) {
            return;
        }

        show(manager, tag);
    }
}

然后当我尝试显示片段时 showAllowingStateLoss不是show

像这样:

MyFragment.newInstance()
.showAllowingStateLoss(getFragmentManager(), MY_FRAGMENT.TAG);

我从这个PR提出了这个解决方案:https : //github.com/googlesamples/easypermissions/pull/170/files


0

另一种可能的解决方法,我不确定在所有情况下是否都可以帮助(源自此处):

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

0

我知道@Ovidiu Latcu接受了一个答案,但是一段时间后,错误仍然存​​在。

@Override
protected void onSaveInstanceState(Bundle outState) {
     //No call for super(). Bug on API Level > 11.
}

Crashlytics仍然向我发送此奇怪的错误消息。

但是,现在仅在版本7+(牛轧糖)上出现错误。我的解决方法是使用commitAllowingStateLoss()而不是commit()。

这个帖子对commitAllowingStateLoss()很有帮助,并且再也没有片段问题。

综上所述,此处接受的答案可能适用于牛轧糖之前的android版本。

这样可以节省某人几个小时的搜索时间。快乐的编码。<3个欢呼声

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.