【问题标题】:Calling DialogFragment's show() from within onRequestPermissionsResult() causes IllegalStateException in Marshmallow从 onRequestPermissionsResult() 中调用 DialogFragment 的 show() 会导致 Marshmallow 中出现 IllegalStateException
【发布时间】:2016-01-20 17:29:26
【问题描述】:

步骤:

  1. FragmentActivity 请求许可
  2. onRequestPermissionsResult() 中显示一个DialogFragment
  3. java.lang.IllegalStateException 被抛出:onSaveInstanceState 之后无法执行此操作

当我在延迟一段时间后显示对话框(使用 postDelayed)时,不会发生这种情况。 根据http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html) 在后蜂窝设备上,我们可以在onPause()onStop() 之间commit(),而不会出现任何状态丢失或异常。 这是示例项目源、日志文件和记录的问题的链接。 https://drive.google.com/folderview?id=0BwvvuYbQTUl6STVSZF9TX2VUeHM&usp=sharing

我还打开了一个问题https://code.google.com/p/android/issues/detail?id=190966,但它被标记为 WorkingAsIntended,他们建议只捕获异常。但这并不能解决问题。我知道其他解决方法,但这不是android bug吗?

更新 该错误的状态再次被“分配”。希望它会尽快修复。 我的临时解决方案是

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        // do your fragment transaction here
    }
}, 200);

【问题讨论】:

  • 你在使用支持对话框片段吗??你在使用片段活动吗?
  • @dex 是的,支持对话框片段和appcompatactivity
  • 那么已经记录了相同的检查:code.google.com/p/android/issues/detail?id=23761
  • @dex 这不一样。如果 onActivityResult() 调用活动停止并调用 onSaveInstanceState()。然后当你想显示对话框时,抛出异常并且没关系。但在这种情况下,调用活动并没有停止,它只是暂停..
  • 这也给我带来了很多麻烦。看起来有一个官方错误和一群人抱怨它仍然没有“修复”,因为它被标记:code.google.com/p/android-developer-preview/issues/…

标签: android android-fragments android-dialogfragment android-permissions android-6.0-marshmallow


【解决方案1】:

该错误已被接受并将被修复,但是,我强烈反对 postDelayed 和 timer 解决方案。最好的方法是在 Activity 中引入一个状态标志,在回调中设置,并在 onResume 或类似中使用。例如:

private boolean someFlag;

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// some code checking status
    someFlag = true;
}

然后在onResume:

protected void onResume() {
    if(someFlag == true) {
        doSomething();
        someFlag = false;
    }
}

【讨论】:

  • 请参阅我在“Scott Key”答案下的评论。但如果 postDelayed 工作正常,为什么不呢?
  • 是的,你在上面。 postDelayed 方法有效,但不是很可靠。在这种情况下,我们确实知道释放对线程的控制就足够了,但一般来说,如果发生延迟你正在等待的事情怎么办?您可能没有设置足够的延迟,并且您的代码中断。你永远不应该依赖魔法延迟。
  • 是的,我知道。但就我而言,这没关系,因为我知道可能不会发生会造成延迟的事情。我不想让我的代码不可读,因为我希望 android 团队能尽快解决这个问题。
  • 我同意,我宁愿在 onResume() 中使用一个标志,而不是依赖某个时间间隔——不管它发生/不发生的可能性有多大
  • 虽然接受的答案通常可以解决问题,但应该接受这个答案,因为它始终可靠。感谢您的解决方案,您为这个问题节省了 2 多天的时间;)
【解决方案2】:

我也认为这是 android 错误。我不敢相信他们标记了您的问题WorkingAsIntended。目前唯一的解决方案是延迟执行 onRequestPermissionsResult() 中的代码,直到 android 人员正确修复此问题。

如果有人想知道如何延迟执行,这是我解决此问题的方法:

@Override public void onRequestPermissionsResult(int requestCode, String[] 权限, int[] grantResults) { if (requestCode == PERMISSION_CODE) { if (/* 允许/拒绝权限 */) { 新 Timer().schedule(新 TimerTask() { @Override public void run() { // 执行操作(如片段交易等) } }, 0); } }

这实际上会延迟执行直到onRequestPermissionsResult() 完成,所以我们不会得到java.lang.IllegalStateException。这适用于我的应用程序。

【讨论】:

  • 我现在有类似的临时解决方案。但我使用 postDelayed 和 Handler
  • 看来bug又被分配了。希望很快就会解决
  • 它再次被标记为按预期工作 - 真是个笑话!
【解决方案3】:

试试这样的:

// ...
private Runnable mRunnable;

@Override
public void onResume() {
   super.onResume();

   if (mRunnable != null) {
       mRunnable.run();
       mRunnable = null;
   }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
   super.onRequestPermissionsResult(requestCode, permissions, grantResults);

   if (/* PERMISSION_DENIED */) {
      mRunnable = /* new Runnable which show dialogFragment*/;
   }
}

【讨论】:

  • 解决方案是在 onPostResume 中保留一个标志并调用 show dialog ..这不是问题..这似乎是一个错误..
【解决方案4】:

这是 Android https://code.google.com/p/android/issues/detail?id=190966&q=label%3AReportedBy-Developer&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars 中的一个错误

因此,就目前而言,解决方法是必要的。您可以使用@ldulcic 的解决方案。或者你可以使用 Handler 的 postDelay 方法。首选第二个选项。

【讨论】:

  • 谢谢帽子!这个错误是我打开的:D
  • Oups) 但这应该对其他人有所帮助。我花了一些时间研究发生了什么。而且我的发现可能会阻止其他开发人员将时间浪费在尚未解决的问题上。
  • 你不能花时间研究,因为问题中已经提供了临时解决方案:D
  • 为了帮助其他开发者,请star issuecode.google.com/p/android/issues/detail?id=190966
  • 是的,你是对的,但永远不要盲目相信 Wikipedia,StackOverflow 更是如此。)
【解决方案5】:

由于您使用的是DialogFragment,因此您应该保留一个标志或状态,而不是在onPostResume 中显示您的对话框。您应该在onPostResume 而不是onResume 或其他生命周期方法中执行此操作。

正如documentation 明确指出的,如果您在onPostResumeonResumeFragments(对于FragmentActivity)以外的Activity 生命周期方法中提交事务,在某些情况下,可以在Activity 的状态完全恢复之前调用该方法.

所以,如果你在 PostResume 上显示你的对话框,你会没事的。

【讨论】:

  • 你是对的 :) 请在“Scott Key”答案下查看我的评论。希望这将很快得到解决,我们将不必使用自定义解决方案
【解决方案6】:

我以一种我认为更具确定性的方式解决了这个问题。我基本上没有使用计时器,而是将结果排队,直到 Activity.onResumeFragments() (或者如果你不使用片段,你可以在 Activity.onResume() 中进行)。我执行 onResumeFragments() 是因为我还将结果路由到请求权限的特定片段,因此我需要确保片段已准备好。

这是 onRequestPermissionsResult():

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    // This is super ugly, but google has a bug that they are calling this method BEFORE
    // onResume... which screws up fragment lifecycle big time.  So work around it.  But also
    // be robust enough to still work if/when they fix the bug.
    if (mFragmentsResumed) {
        routePermissionsResult(requestCode, permissions, grantResults);
    } else {
        mQueuedPermissionGrantResults = grantResults;
        mQueuedPermissionRequestCode = requestCode;
        mQueuedPermissions = permissions;
    }
}

然后这里是 onResumeFragments():

@Override
protected void onResumeFragments() {
    super.onResumeFragments();

    mFragmentsResumed = true;

    if (mQueuedPermissionGrantResults != null) {
        routePermissionsResult(mQueuedPermissionRequestCode, mQueuedPermissions,
                mQueuedPermissionGrantResults);
    }

为了完整起见,这里是 onPause(),它会清除 mFragmentsResumed 标志:

@Override
protected void onPause() {
    super.onPause();

    mFragmentsResumed = false;
}

我使用了 mFragmentsResumed 标志,因为我不希望这段代码在谷歌修复错误时停止工作(如果我只是在 onRequestPermissionsResult 中设置排队的变量,那么更改生命周期调用的顺序会使代码不起作用但在此之前调用了 onResumeFragments)。

【讨论】:

  • 实现了这一点,它看起来就像一个魅力。一个丑陋问题的干净解决方案。很高兴您可以像最初调用它时一样处理 routePermissionsResult 方法中的 onRequestPermissionsResult。
【解决方案7】:

正如issue 中的一个 cmets 中所解释的,这仅在使用支持库的DialogFragment(即android.support.v4.app.DialogFragment)时发生。您可以更改您的代码以使用“本机”DialogFragment (android.app.DialogFragment),它会正常工作。

我知道在某些情况下使用本机 DialogFragment 是不可能的,但在这种情况下,鉴于您指的是运行时权限(该功能仅在最新版本的 Android 中可用),您很可能能够做到这一点。我有这个确切的问题,我可以用这种方式解决它。

【讨论】:

    【解决方案8】:

    我也使用 Handler 解决了这个问题,但我能够避免使用计时器将其 Looper 设置为 MainLooper。所以它会在视图渲染后运行:

    new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            // do your fragment transaction here
        }
    });
    

    【讨论】:

      猜你喜欢
      • 2014-10-07
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 2016-08-28
      • 2019-06-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多