繁体   English   中英

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

[英]IllegalStateException: Can not perform this action after onSaveInstanceState with 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 有关。 stacktrace 没有显示我自己的任何类,所以我不知道这个异常发生在哪里以及如何防止它。

记录一下:我有一个 tabhost,在每个选项卡中都有一个 ActivityGroup 在活动之间切换。

在此处查看我的回答。 基本上我只需要:

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

不要在saveInstanceState方法上调用super() 这是把事情搞砸了......

这是支持包中的一个已知错误

如果您需要保存实例并向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();

添加或执行导致ExceptionFragmentTransaction

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

如果会话状态已经被保存,这个方法调用,就像popBackStack一样,总是会以IllegalStateException失败。 检查来源。 您无法阻止抛出此异常。

  • 删除对super.onSaveInstanceState的调用将无济于事。
  • 使用commitAllowingStateLoss创建 Fragment 将无济于事。

这是我观察问题的方式:

  • 有一个带有提交按钮的表单。
  • 单击按钮时,将创建一个对话框并启动一个异步过程。
  • 用户在该过程完成之前单击主页键 - 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 年 10 月,Google 使用名为 Lifecycle 组件的新事物制作了 Android 支持库。 它为“在 onSaveInstanceState 后无法执行此操作”问题提供了一些新思路。

简而言之:

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

更长的版本解释:

  • 为什么会出现这个问题?

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

    通常这个问题不应该发生——我们总是在一开始就尝试将片段加载到活动中,就像onCreate()方法是一个完美的地方。 但有时确实会发生这种情况,尤其是当您无法决定将哪个片段加载到该活动时,或者您正在尝试从AsyncTask块加载片段时(或任何事情都需要一点时间)。 在片段事务真正发生之前,但在活动的onCreate()方法之后,用户可以做任何事情。 如果用户按下主页按钮,触发 Activity 的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类中维护一个列表,以存储那些没有机会完成事务操作的片段。 当用户从主屏幕返回时,发现仍有片段等待启动时​​,它将转到@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)注释下的resume()方法。 现在我认为它应该像我预期的那样工作。

这是针对此问题的不同解决方案。

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

简短而有效的解决方案:

遵循简单的步骤

脚步

第 1 步:覆盖相应片段中的onSaveInstanceState状态。 并从中删除超级方法。

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

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

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

当心,使用transaction.commitAllowingStateLoss()可能会给用户带来糟糕的体验。 有关引发此异常的原因的更多信息,请参阅此帖子

我为这种问题找到了一个肮脏的解决方案。 如果你仍然想保留你的ActivityGroups出于任何原因(我有时间限制原因),你只需实现

public void onBackPressed() {}

在您的Activity并在其中执行一些back代码。 即使旧设备上没有这样的方法,这个方法也会被新设备调用。

不要使用 commitAllowingStateLoss(),它应该只用于用户界面状态可以意外更改的情况。

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

我有一个类似的问题,场景是这样的:

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

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

如果从FragmentActivity继承,则必须在onActivityResult()调用超类:

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

如果您不这样做并尝试在该方法中显示片段对话框,您可能会收到 OP 的IllegalStateException (老实说,我不太明白为什么super 调用解决了这个问题。 onActivityResult()onResume()之前被调用,所以它仍然不允许显示片段对话框。)

当我按下后退按钮取消我的地图片段活动上的意图选择器时,我遇到了这个异常。 我通过将 onResume(我在其中初始化片段)的代码替换为 onstart() 解决了这个问题,并且该应用程序运行良好。希望它有所帮助。

在我的案例中,我发现的最流畅和最简单的解决方案可能是避免从堆栈中弹出有问题的片段以响应活动结果。 因此,在我的onActivityResult()更改此调用:

popMyFragmentAndMoveOn();

对此:

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

在我的情况下有帮助。

我认为使用transaction.commitAllowingStateLoss(); 不是最好的解决方案。 当活动的配置更改并且片段onSavedInstanceState()被调用时,将抛出此异常,此后您的异步回调方法尝试提交片段。

简单的解决方案可以是检查活动是否正在改变配置

例如检查isChangingConfigurations()

IE

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

也可以查看链接

此处抛出异常(在 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);
    }
}

我只是通过使用一个标志来标记 Activity 的当前状态来解决这个问题。 这是我的解决方案:

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

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

您可以使用 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
}

如果你在 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;
    }
}

礼貌: IllegalStateException 的解决方案

这个问题已经让我烦恼了很多时间,但幸运的是我为它提供了一个具体的解决方案。 对它的详细解释是here

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

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

如果您使用 popBackStack() 或 popBackStackImmediate() 方法崩溃,请尝试使用 fixt:

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

这对我也有用。

在我的例子中,我在一个名为 onActivityResult 的覆盖方法中得到了这个错误。 挖掘之后,我才发现也许我之前需要调用“ super ”。
我添加了它并且它起作用了

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

}

也许您只需要在代码之前执行的任何覆盖上添加一个“超级”。

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

从支持库版本 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) 我的修复是在fragmentTransaction中使用commitAllowingStateLoss()而不是 commit()。

这篇文章对 commitAllowingStateLoss() 很有帮助,并且再也没有出现片段问题。

总而言之,这里接受的答案可能适用于牛轧糖之前的 android 版本。

这可能会为某人节省几个小时的搜索时间。 快乐的编码。 <3 欢呼

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

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)

将此添加到您的活动中

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

我也遇到过这个问题,每次当您的FragmentActivity上下文发生变化(例如屏幕方向发生变化等)时都会出现问题。 所以最好的解决方法是从你的FragmentActivity更新上下文。

我最终创建了一个基本片段并使我的应用程序中的所有片段扩展它

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

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

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

我有同样的问题。 它的发生是由于先前活动的破坏。 当ı支持先前的活动时,它被销毁了。 我把它作为基本活动(错误)

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    SpinnerCustom2.setFragmentManager(getSupportFragmentManager());
    onCreateDrawerActivity(savedInstanceState);
}

我把它放在onStart它是正确的

@Override
protected void onStart() {
    super.onStart();
    SpinnerCustom2.setFragmentManager(getSupportFragmentManager());

}

@Gian Gomen以我为例,SUPER解决了问题。 似乎比commitAllowingStateLoss()更正确的解决方案,因为它可以解决问题,而不是隐藏问题。

@Override
public void onRequestPermissionsResult(
     final int requestCode,
     @NonNull final String[] permissions, 
     @NonNull final int[] grantResults
) {
        super.onRequestPermissionsResult(requestCode,permissions, grantResults); //<--- Without this line crash 
        switch (requestCode) {
            case Constants.REQUEST_CODE_PERMISSION_STORAGE:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    onPermissionGranted(Constants.REQUEST_CODE_PERMISSION_STORAGE);
                }
                break;
        }

如果状态保存,请使用 remove() 而不是 popup() 。

   private void removeFragment() {
    FragmentManager fragmentManager = getSupportFragmentManager();
    if (fragmentManager.isStateSaved()) {
        List<Fragment> fragments = fragmentManager.getFragments();
        if (fragments != null && !fragments.isEmpty()) {
            fragmentManager.beginTransaction().remove(fragments.get(fragments.size() - 1)).commitAllowingStateLoss();
        }
    }
}

将 getFragmentManager() 更改为 getChildFragmentManager() 。 不要使用父 FragmentManager,尝试使用 self.

正如您在崩溃报告中看到的那样,引发异常的最后一行是

checkStateLoss(FragmentManager.java:1109)

如果你看一下 checkStateLoss 的实现

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

所以对我来说一个简单的解决方案是找到您在应用程序中调用的 Fragment Manager 的哪种方法最终导致调用该方法,并在调用该方法之前简单地检查 isStateSaved() 是否为 false。 对我来说,这是 show() 方法。 我确实喜欢这个

if (!isStateSaved()) {
  myDialog.show(fragmentManager, Tag)
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM