从onRequestPermissionsResult()中调用DialogFragment的show()会导致Marshmallow中出现IllegalStateException

时间:2015-10-21 16:01:20

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

步骤:

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

    当我在延迟一段时间后显示对话框时(使用postDelayed),这不会发生。 根据{{​​3}})关于后蜂窝设备,我们commit()onPause()之间onStop()可以没有任何状态损失或异常。 这是一个示例项目源,日志文件和记录问题的链接。 http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html

    此外,我已经打开了一个问题https://drive.google.com/folderview?id=0BwvvuYbQTUl6STVSZF9TX2VUeHM&usp=sharing,但它被标记为WorkingAsIntended,他们建议只捕获异常。但这并没有解决问题。我知道解决它的其他方法,但不是这个安卓漏洞吗?

    更新 该错误的状态再次被分配"。希望很快就能解决。 我的临时解决方案是

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

8 个答案:

答案 0 :(得分:24)

该错误已被接受并将被修复,但是,我强烈反对postDelayed和计时器解决方案。执行此操作的最佳方法是在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;
    }
}

答案 1 :(得分:6)

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

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

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

    if (requestCode == PERMISSION_CODE) {
      if (/* PERMISSION ALLOWED/DENIED */) {
          new Timer().schedule(new TimerTask() {
              @Override public void run() {
                  // EXECUTE ACTIONS (LIKE FRAGMENT TRANSACTION ETC.)
              }
          }, 0);
      }
  }

这实质上会延迟执行,直到onRequestPermissionsResult()完成,因此我们无法获得java.lang.IllegalStateException。这适用于我的应用程序。

答案 2 :(得分:4)

尝试这样的事情:

// ...
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*/;
   }
}

答案 3 :(得分: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方法。第二种选择是首选。

答案 4 :(得分:4)

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

正如documentation明确指出的那样,如果您在除onPostResumeonResumeFragments之外的Activity生命周期方法中提交事务(对于FragmentActivity),在某些情况下,可以在活动的状态已经完全恢复。

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

答案 5 :(得分:2)

我认为这是一种更确定的方式。我没有使用计时器,而是将结果排队,直到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。

答案 6 :(得分:0)

正如issue中的一条评论所解释的那样,只有在使用支持库with open("file", "rb") as infile, open('output', 'wb') as outfile: buf = bytearray() for n in randoms: infile.seek(lsize * n) buf.extend(infile.read(lsize)) outfile.write(buf) (即DialogFragment)时才会发生这种情况。您可以更改代码以使用" native" android.support.v4.app.DialogFragmentDialogFragment),它会正常工作。

我知道在某些情况下使用原生android.app.DialogFragment是不可能的,但在这种情况下,鉴于您指的是运行时权限(仅在最新版本的Android中可用的功能),最有可能做到这一点。我有这个问题,我可以这样解决。

答案 7 :(得分:0)

我也使用Handler解决了这个问题,但是我避免使用计时器将其Looper设置为MainLooper。因此它将在呈现视图后运行:

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