【问题标题】:On showing dialog I get "Can not perform this action after onSaveInstanceState"在显示对话框时,我得到“onSaveInstanceState 后无法执行此操作”
【发布时间】:2013-03-21 16:23:32
【问题描述】:

有些用户在举报,如果他们使用通知栏中的快速操作,他们会被强制关闭。

我在调用 "TestDialog" 类的通知中显示了一个快速操作。 在 TestDialog 类中按下“贪睡”按钮后,我将显示贪睡对话框。

private View.OnClickListener btnSnoozeOnClick() {
    return new View.OnClickListener() {

        public void onClick(View v) {
            showSnoozeDialog();
        }
    };
}

private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    snoozeDialog.show(fm, "snooze_dialog");
}

错误是*IllegalStateException: Can not perform this action after onSaveInstanceState*.

触发 IllegarStateException 的代码行是:

snoozeDialog.show(fm, "snooze_dialog");

该类正在扩展“FragmentActivity”,而“SnoozeDialog”类正在扩展“DialogFragment”。

这是错误的完整堆栈跟踪:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1338)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:127)
at com.test.testing.TestDialog.f(TestDialog.java:538)
at com.test.testing.TestDialog.e(TestDialog.java:524)
at com.test.testing.TestDialog.d(TestDialog.java:519)
at com.test.testing.g.onClick(TestDialog.java:648)
at android.view.View.performClick(View.java:3620)
at android.view.View$PerformClick.run(View.java:14292)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4507)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
at dalvik.system.NativeStart.main(Native Method)

我无法重现此错误,但我收到了很多错误报告。

谁能帮我解决这个错误?

【问题讨论】:

  • 您找到解决方案了吗?我和你有同样的问题。我在这里问过:stackoverflow.com/questions/15730878/… 请检查我的问题并查看不适用于我的情况的可能解决方案。也许它会为你工作。
  • 尚无解决方案 :-( 您的建议已添加到我的课程中。
  • 从这里检查接受的答案。这解决了我的问题:stackoverflow.com/questions/14177781/…
  • 触发此对话框时您的活动是否可见?听起来这可能是由于您的应用试图显示附加到已暂停/停止的 Activity 的对话框。
  • 我通过更改一些设计以使用 childFragmentManager 而不是 supportFragmentManager 来解决我的相同问题。当我尝试显示对话框时,在方向更改后发生此错误。

标签: android dialog fragment


【解决方案1】:

这是常见的issue。 我们通过在 DialogFragment 扩展类中重写 show() 和处理异常解决了这个问题

public class CustomDialogFragment extends DialogFragment {

    @Override
    public void show(FragmentManager manager, String tag) {
        try {
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commit();
        } catch (IllegalStateException e) {
            Log.d("ABSDIALOGFRAG", "Exception", e);
        }
    }
}

请注意,应用此方法不会改变 DialogFragment.class 的内部字段:

boolean mDismissed;
boolean mShownByMe;

在某些情况下,这可能会导致意外结果。最好使用 commitAllowingStateLoss() 而不是 commit()

【讨论】:

  • 但是为什么会出现这个问题呢?可以忽略错误吗?当你这样做时会发生什么?毕竟,当点击时,这意味着活动正在进行中并且很好......无论如何,我已经在这里报告了这个,因为我认为这是一个错误:code.google.com/p/android/issues/detail?id=207269
  • 那么你能在那儿加注星标和/或评论吗?
  • 最好在 try-catch 子句中调用 super.show(manager, tag)。 DialogFragment 拥有的标志可以通过这种方式保持安全
  • 此时你可以调用commitAllowingStateLoss()而不是commit()。不会引发异常。
  • 你刚刚添加了一个 try-catch 块,这不是问题的解决方案,你只是在掩盖一个错误。
【解决方案2】:

这意味着onSaveInstanceState() 之后的commit()(如果是 DialogFragment 则为show())片段。

Android 会将您的片段状态保存在onSaveInstanceState()。所以,如果你在commit() 片段之后onSaveInstanceState() 片段状态将会丢失。

因此,如果 Activity 被杀死并稍后重新创建,该 Fragment 将不会添加到 Activity 中,这是不好的用户体验。这就是 Android 不允许不惜一切代价进行状态丢失的原因。

简单的解决方案是检查状态是否已经保存。

boolean mIsStateAlreadySaved = false;
boolean mPendingShowDialog = false;

@Override
public void onResumeFragments(){
    super.onResumeFragments();
    mIsStateAlreadySaved = false;
    if(mPendingShowDialog){
        mPendingShowDialog = false;
        showSnoozeDialog();
    }
}

@Override
public void onPause() {
    super.onPause();
    mIsStateAlreadySaved = true;
}

private void showSnoozeDialog() {
    if(mIsStateAlreadySaved){
        mPendingShowDialog = true;
    }else{
        FragmentManager fm = getSupportFragmentManager();
        SnoozeDialog snoozeDialog = new SnoozeDialog();
        snoozeDialog.show(fm, "snooze_dialog");
    }
}

注意:onResumeFragments() 将在片段恢复时调用。

【讨论】:

  • 如果我想在另一个片段中显示 DialogFragment 怎么办?
  • 我们的解决方案是创建activity和fragment基类并将onResumeFragments委托给fragment(我们在fragment基类中创建onResumeFragments)。这不是很好的解决方案,但它确实有效。如果您有更好的解决方案,请告诉我:)
  • 好吧,我认为在“onStart”中显示对话框应该可以正常工作,因为片段肯定会显示,但我仍然看到一些关于它的崩溃报告。我被指示尝试将其放在“onResume”上。关于替代品,我看到了这个:twigstechtips.blogspot.co.il/2014/01/…,但这很奇怪。
  • 我认为twigstechtips.blogspot.co.il/2014/01/… 工作的原因是因为它启动了新线程,因此在代码 runOnUiThread 运行之前调用了所有生命周期代码,即 onStart、onResume 等。这意味着在调用 runOnUiThread 之前状态已经恢复。
  • 我使用单次调用来发布(可运行)。关于 getFragmentManager,这取决于。如果您想与另一个活动共享该对话框,则应使用 getFragmentManager,但是,如果该对话框仅与片段一起存在,则 getChildFragmentManager 似乎是更好的选择。
【解决方案3】:

使用 Activity-KTX 的新生命周期范围就像下面的代码示例一样简单:

lifecycleScope.launchWhenResumed {
   showErrorDialog(...)
}

该方法可以在onStop()之后直接调用,返回时调用onResume()会成功显示对话框。

【讨论】:

【解决方案4】:
private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    // snoozeDialog.show(fm, "snooze_dialog");
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.add(snoozeDialog, "snooze_dialog");
    ft.commitAllowingStateLoss();
}

参考:link

【讨论】:

    【解决方案5】:

    几天后我想分享我的解决方案我是如何修复它的,为了显示 DialogFragment 你应该覆盖它的show() 方法并在Transaction 对象上调用commitAllowingStateLoss()。这是 Kotlin 中的示例:

    override fun show(manager: FragmentManager?, tag: String?) {
            try {
                val ft = manager?.beginTransaction()
                ft?.add(this, tag)
                ft?.commitAllowingStateLoss()
            } catch (ignored: IllegalStateException) {
    
            }
    
        }
    

    【讨论】:

    • 为了让开发人员不必继承 DialogFragment,您可以将其更改为具有以下签名的 Kotlin 扩展函数:fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String)。此外,try-catch 不是必需的,因为您调用的是 commitAllowingStateLoss() 方法而不是 commit() 方法。
    【解决方案6】:

    如果对话框不是很重要(可以在应用关闭/不再显示时不显示它),请使用:

    boolean running = false;
    
    @Override
    public void onStart() {
        running = true;
        super.onStart();
    }
    
    @Override
    public void onStop() {
        running = false;
        super.onStop();
    }
    

    只有在我们运行时才打开你的对话框(片段):

    if (running) {
        yourDialog.show(...);
    }
    

    编辑,可能是更好的解决方案:

    在生命周期中调用 onSaveInstanceState 的位置是不可预测的,我认为更好的解决方案是像这样检查 isSavedInstanceStateDone():

    /**
     * True if SavedInstanceState was done, and activity was not restarted or resumed yet.
     */
    private boolean savedInstanceStateDone;
    
    @Override
    protected void onResume() {
        super.onResume();
    
        savedInstanceStateDone = false;
    }
    
    @Override
    protected void onStart() {
        super.onStart();
    
        savedInstanceStateDone = false;
    }
    
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        savedInstanceStateDone = true;
    }
    
    
    /**
     * Returns true if SavedInstanceState was done, and activity was not restarted or resumed yet.
     */
    public boolean isSavedInstanceStateDone() {
        return savedInstanceStateDone;
    }
    

    【讨论】:

    • 这似乎不起作用,因为我在“onStart”方法调用中遇到了这个异常(试图在那里显示 DialogFragment)。
    • 你拯救了我的一天。谢谢弗兰克。
    • 这很好用,我需要将它添加到 show() 和 hide() 中。谢谢
    【解决方案7】:

    我多年来一直遇到这个问题。
    互联网上充斥着数十个(数十万个?数千个?)关于此的讨论,其中的混乱和虚假信息似乎很多。
    更糟糕的是,本着 xkcd“14 标准”漫画的精神,我将把我的答案扔给戒指。

    cancelPendingInputEvents()commitAllowingStateLoss()catch (IllegalStateException e) 和类似的解决方案看起来都很糟糕。

    希望以下内容可以轻松展示如何重现和解决问题:

    private static final Handler sHandler = new Handler();
    private boolean mIsAfterOnSaveInstanceState = true;
    
    @Override
    protected void onSaveInstanceState(Bundle outState)
    {
        super.onSaveInstanceState(outState);
        mIsAfterOnSaveInstanceState = true; // <- To repro, comment out this line
    }
    
    @Override
    protected void onPostResume()
    {
        super.onPostResume();
        mIsAfterOnSaveInstanceState = false;
    }
    
    @Override
    protected void onResume()
    {
        super.onResume();
        sHandler.removeCallbacks(test);
    }
    
    @Override
    protected void onPause()
    {
        super.onPause();
        sHandler.postDelayed(test, 5000);
    }
    
    Runnable test = new Runnable()
    {
        @Override
        public void run()
        {
            if (mIsAfterOnSaveInstanceState)
            {
                // TODO: Consider saving state so that during or after onPostResume a dialog can be shown with the latest text
                return;
            }
    
            FragmentManager fm = getSupportFragmentManager();
            DialogFragment dialogFragment = (DialogFragment) fm.findFragmentByTag("foo");
            if (dialogFragment != null)
            {
                dialogFragment.dismiss();
            }
    
            dialogFragment = GenericPromptSingleButtonDialogFragment.newInstance("title", "message", "button");
            dialogFragment.show(fm, "foo");
    
            sHandler.postDelayed(test, 5000);
        }
    };
    

    【讨论】:

    • 我喜欢那些不加解释地投反对票的人。与其只是投票,不如让他们解释我的解决方案有什么缺陷会更好?我可以否决投票者的反对票吗?
    • 是的,这是SO的问题,我每次都在建议中写这个问题,但他们不想解决。
    • 我认为反对票可能是嵌入式 XKCD 的结果,答案真的不是社交媒体的地方,(无论多么有趣和/或真实)。
    • 应该在 BaseFragment(BaseActivity) 或 CustomDialogFragment 或扩展 BaseFragment 的 myFragment 上?
    【解决方案8】:

    请尝试使用 FragmentTransaction 而不是 FragmentManager。我认为下面的代码将解决您的问题。如果没有,请告诉我。

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    snoozeDialog.show(ft, "snooze_dialog");
    

    编辑:

    Fragment Transaction

    请检查此链接。我想它会解决你的疑问。

    【讨论】:

    • 任何关于为什么使用 FragmentTransaction 解决问题的解释都会很棒。
    • Dialog#show(FragmentManager, tag) 做同样的事情。这不是解决方案。
    • 这个答案不是解决方案。 DialogFragment#show(ft) 和 show(fm) 做同样的事情。
    • @danijoo 你说得对,两者都做同样的工作。但是在少数手机中,如果您使用的是 fragmentmanager 而不是 fragmenttransaction,则会出现类似的问题。所以就我而言,这解决了我的问题。
    【解决方案9】:

    使您的对话框片段对象全局化并在 onPause() 方法中调用 dismissAllowingStateLoss()

    @Override
    protected void onPause() {
        super.onPause();
    
        if (dialogFragment != null) {
            dialogFragment.dismissAllowingStateLoss();
        }
    }
    

    不要忘记在片段中分配值并在按钮单击或任何地方调用 show()。

    【讨论】:

      【解决方案10】:

      许多视图将高级事件(例如单击处理程序)发布到事件队列以延迟运行。所以问题是已经为活动调用了“onSaveInstanceState”,但事件队列包含延迟的“点击事件”。因此,当这个事件被分派给你的处理程序时

      at android.os.Handler.handleCallback(Handler.java:605)
      at android.os.Handler.dispatchMessage(Handler.java:92)
      at android.os.Looper.loop(Looper.java:137)
      

      并且您的代码确实show 抛出了 IllegalStateException。

      最简单的解决方案是清理事件队列,在onSaveInstanceState

      protected void onSaveInstanceState(Bundle outState) {
              super.onSaveInstanceState(outState);
              // ..... do some work
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                  findViewById(android.R.id.content).cancelPendingInputEvents();
              }
      }
      

      【讨论】:

      • 您是否真的确认这可以解决问题?
      • Google 已将此添加到下一个版本的 androidx 库中,目前处于测试阶段(activityfragment)。
      • @mhsmith 我记得这个解决方案用 IllegalStateException 解决了我的代码中的问题
      【解决方案11】:

      使用此代码

      FragmentTransaction ft = fm.beginTransaction();
              ft.add(yourFragment, "fragment_tag");
              ft.commitAllowingStateLoss();
      

      而不是

      yourFragment.show(fm, "fragment_tag");
      

      【讨论】:

        【解决方案12】:

        我通过使用反射找到了一个优雅的解决方案。 以上所有解决方案的问题是字段 mDismissedmShownByMe 不会改变它们的状态。

        只需在您自己的自定义底部工作表对话框片段中覆盖方法“show”,如下面的示例 (Kotlin)

        override fun show(manager: FragmentManager, tag: String?) {
                val mDismissedField = DialogFragment::class.java.getDeclaredField("mDismissed")
                mDismissedField.isAccessible = true
                mDismissedField.setBoolean(this, false)
        
                val mShownByMeField = DialogFragment::class.java.getDeclaredField("mShownByMe")
                mShownByMeField.isAccessible = true
                mShownByMeField.setBoolean(this, true)
        
                manager.beginTransaction()
                        .add(this, tag)
                        .commitAllowingStateLoss()
            }
        

        【讨论】:

        • “我找到了一个优雅的解决方案,通过使用反射来解决这个问题。”怎么优雅?
        • 优雅、时尚、别致、聪明、漂亮、优雅
        • 这是唯一对我有用的解决方案。我认为它很优雅
        【解决方案13】:

        如果您覆盖 show() 函数,请不要这样做:

        override fun show(manager: FragmentManager, tag: String?) {
            // mDismissed = false; is removed -> lead to wrong state
            // mShownByMe = true; is removed -> lead to wrong state
            val ft = manager.beginTransaction()
            ft.add(this, tag)
            ft.commitAllowingStateLoss()
        }
        

        可能会导致对话框的错误状态

        只要做:

        override fun show(manager: FragmentManager, tag: String?) {
            try {
                super.show(manager, tag)
            } catch (e: Exception) {
                val ft = manager.beginTransaction()
                ft.add(this, tag)
                ft.commitAllowingStateLoss()
            }
        }
        

        【讨论】:

        • 无论如何我只使用 fragmentTransaction ;)
        【解决方案14】:

        虽然它没有在任何地方正式提及,但我多次遇到这个问题。根据我的经验,在旧平台上支持片段的兼容性库存在问题,这会导致此问题。您可以使用普通的片段管理器 API 对此进行测试。如果没有任何效果,那么您可以使用普通对话框而不是对话框片段。

        【讨论】:

          【解决方案15】:
          1. 将此类添加到您的项目中:(必须在 android.support.v4.app 包中)
          包android.support.v4.app; /** * 由 Gil 于 2017 年 8 月 16 日创建。 */ 公共类 StatelessDialogFragment 扩展 DialogFragment { /** * 显示对话框,使用现有事务添加片段,然后提交 * 在允许状态丢失的同时进行交易。
          * * 我建议你大部分时间使用 {@link #show(FragmentTransaction, String)} 但是 * 这适用于您真的不关心的对话。 (调试/跟踪/广告等) * * @param 交易 * 在其中添加片段的现有事务。 * @param 标记 * 此片段的标签,根据 * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}。 * @return 返回已提交事务的标识符,按照 * {@link FragmentTransaction#commit() FragmentTransaction.commit()}。 * @see StatelessDialogFragment#showAllowingStateLoss(FragmentManager, String) */ public int showAllowingStateLoss(FragmentTransaction 交易,字符串标签) { mDismissed = 假; mShownByMe = 真; transaction.add(this, tag); mViewDestroyed = 假; mBackStackId = transaction.commitAllowingStateLoss(); 返回 mBackStackId; } /** * 显示对话框,将片段添加到给定的 FragmentManager。这是一个方便 * 用于显式创建事务,使用给定标签将片段添加到其中,以及 * 在不关心状态的情况下提交它。这将交易添加到 * 回栈。当片段被解除时,将执行一个新的事务来移除它 * 来自活动。
          * * 我建议你大部分时间使用 {@link #show(FragmentManager, String)} 但这是 * 对于你真的不关心的对话。 (调试/跟踪/广告等) * * * @参数管理器 * 这个片段将被添加到的 FragmentManager。 * @param 标记 * 此片段的标签,根据 * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}。 * @see StatelessDialogFragment#showAllowingStateLoss(FragmentTransaction, String) */ public void showAllowingStateLoss(FragmentManager 管理器,字符串标签) { mDismissed = 假; mShownByMe = 真; FragmentTransaction ft = manager.beginTransaction(); ft.add(这个,标签); ft.commitAllowingStateLoss(); } }
          1. 扩展 StatelessDialogFragment 而不是 DialogFragment
          2. 使用方法showAllowingStateLoss代替show

          3. 享受 ;)

          【讨论】:

          • 这些布尔字段是干什么用的?为什么不将它们声明为类成员?
          • 布尔字段是 DialogFragment 的受保护成员,它们的名称显然暗示了它们的用途,我们需要更新它们以免干扰 DialogFragment 的逻辑。请注意,在原始 DialogFragment 类中,此函数存在但没有公共访问权限
          • Ough 这些成员没有受到保护,它们是内部的。当我将 StatelessDialogFragment 放入我的一个包中时出现编译错误。谢谢老兄。我会尽快在生产中测试它。跨度>
          【解决方案16】:

          以下实现可用于解决在Activity 生命周期期间安全执行状态更改的问题,特别是用于显示对话框:如果实例状态已经保存(例如由于配置更改),则推迟直到执行恢复状态。

          public abstract class XAppCompatActivity extends AppCompatActivity {
          
              private String TAG = this.getClass().getSimpleName();
          
              /** The retained fragment for this activity */
              private ActivityRetainFragment retainFragment;
          
              /** If true the instance state has been saved and we are going to die... */
              private boolean instanceStateSaved;
          
              @Override
              protected void onPostCreate(Bundle savedInstanceState) {
                  super.onPostCreate(savedInstanceState);
          
                  // get hold of retain Fragment we'll be using
                  retainFragment = ActivityRetainFragment.get(this, "Fragment-" + this.getClass().getName());
              }
          
              @Override
              protected void onPostResume() {
                  super.onPostResume();
          
                  // reset instance saved state
                  instanceStateSaved = false;
          
                  // execute all the posted tasks
                  for (ActivityTask task : retainFragment.tasks) task.exec(this);
                  retainFragment.tasks.clear();
              }
          
              @Override
              protected void onSaveInstanceState(Bundle outState) {
                  super.onSaveInstanceState(outState);
                  instanceStateSaved = true;
              }
          
              /**
               * Checks if the activity state has been already saved.
               * After that event we are no longer allowed to commit fragment transactions.
               * @return true if the instance state has been saved
               */
              public boolean isInstanceStateSaved() {
                  return instanceStateSaved;
              }
          
              /**
               * Posts a task to be executed when the activity state has not yet been saved
               * @param task The task to be executed
               * @return true if the task executed immediately, false if it has been queued
               */
              public final boolean post(ActivityTask task)
              {
                  // execute it immediately if we have not been saved
                  if (!isInstanceStateSaved()) {
                      task.exec(this);
                      return true;
                  }
          
                  // save it for better times
                  retainFragment.tasks.add(task);
                  return false;
              }
          
              /** Fragment used to retain activity data among re-instantiations */
              public static class ActivityRetainFragment extends Fragment {
          
                  /**
                   * Returns the single instance of this fragment, creating it if necessary
                   * @param activity The Activity performing the request
                   * @param name The name to be given to the Fragment
                   * @return The Fragment
                   */
                  public static ActivityRetainFragment get(XAppCompatActivity activity, String name) {
          
                      // find the retained fragment on activity restarts
                      FragmentManager fm = activity.getSupportFragmentManager();
                      ActivityRetainFragment fragment = (ActivityRetainFragment) fm.findFragmentByTag(name);
          
                      // create the fragment and data the first time
                      if (fragment == null) {
                          // add the fragment
                          fragment = new ActivityRetainFragment();
                          fm.beginTransaction().add(fragment, name).commit();
                      }
          
                      return fragment;
                  }
          
                  /** The queued tasks */
                  private LinkedList<ActivityTask> tasks = new LinkedList<>();
          
                  @Override
                  public void onCreate(Bundle savedInstanceState)
                  {
                      super.onCreate(savedInstanceState);
          
                      // retain this fragment
                      setRetainInstance(true);
                  }
          
              }
          
              /** A task which needs to be performed by the activity when it is "fully operational" */
              public interface ActivityTask {
          
                  /**
                   * Executed this task on the specified activity
                   * @param activity The activity
                   */
                  void exec(XAppCompatActivity activity);
              }
          }
          

          然后使用这样的类:

          /** AppCompatDialogFragment implementing additional compatibility checks */
          public abstract class XAppCompatDialogFragment extends AppCompatDialogFragment {
          
              /**
               * Shows this dialog as soon as possible
               * @param activity The activity to which this dialog belongs to
               * @param tag The dialog fragment tag
               * @return true if the dialog has been shown immediately, false if the activity state has been saved
               *         and it is not possible to show it immediately
               */
              public boolean showRequest(XAppCompatActivity activity, final String tag) {
                  return showRequest(activity, tag, null);
              }
          
              /**
               * Shows this dialog as soon as possible
               * @param activity The activity to which this dialog belongs to
               * @param tag The dialog fragment tag
               * @param args The dialog arguments
               * @return true if the dialog has been shown immediately, false if the activity state has been saved
               *         and it is not possible to show it immediately
               */
              public boolean showRequest(XAppCompatActivity activity, final String tag, final Bundle args)
              {
                  return activity.post(new XAppCompatActivity.ActivityTask() {
                      @Override
                      public void exec(XAppCompatActivity activity) {
                          if (args!= null) setArguments(args);
                          show(activity.getSupportFragmentManager(), tag);
                      }
                  });
              }
          
              /**
               * Dismiss this dialog as soon as possible
               * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
               *         and it is not possible to dismissed it immediately
               */
              public boolean dismissRequest()
              {
                  return dismissRequest(null);
              }
          
              /**
               * Dismiss this dialog as soon as possible
               * @param runnable Actions to be performed before dialog dismissal
               * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
               *         and it is not possible to dismissed it immediately
               */
              public boolean dismissRequest(final Runnable runnable)
              {
                  // workaround as in rare cases the activity could be null
                  XAppCompatActivity activity = (XAppCompatActivity)getActivity();
                  if (activity == null) return false;
          
                  // post the dialog dismissal
                  return activity.post(new XAppCompatActivity.ActivityTask() {
                      @Override
                      public void exec(XAppCompatActivity activity) {
                          if (runnable != null) runnable.run();
                          dismiss();
                      }
                  });
              }
          }
          

          您可以安全地显示对话框,而不必担心应用状态:

          public class TestDialog extends XAppCompatDialogFragment {
          
              private final static String TEST_DIALOG = "TEST_DIALOG";
          
              public static void show(XAppCompatActivity activity) {
                  new TestDialog().showRequest(activity, TEST_DIALOG);
              }
          
              public TestDialog() {}
          
              @NonNull
              @Override
              public Dialog onCreateDialog(Bundle savedInstanceState)
              {
                  return new AlertDialog.Builder(getActivity(), R.style.DialogFragmentTheme /* or null as you prefer */)
                          .setTitle(R.string.title)
                          // set all the other parameters you need, e.g. Message, Icon, etc.
                          ).create();
              }
          }
          

          然后从您的XAppCompatActivity 中调用TestDialog.show(this)

          如果您想创建一个更通用的带有参数的对话框类,您可以将它们保存在 Bundle 中,并在 show() 方法中使用参数,然后在 onCreateDialog() 中使用 getArguments() 检索它们。

          整个方法可能看起来有点复杂,但是一旦您为活动和对话框创建了两个基类,它就非常易于使用并且运行良好。它可用于其他可能受相同问题影响的基于Fragment 的操作。

          【讨论】:

            【解决方案17】:

            出现此错误的原因似乎是在调用 onSaveInstanceState 之后传递了输入事件(例如按键或点击事件)。

            解决方案是在您的 Activity 中覆盖 onSaveInstanceState 并取消所有未决事件。

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

            【讨论】:

              猜你喜欢
              • 2017-05-25
              • 2017-06-21
              • 1970-01-01
              • 1970-01-01
              • 2014-10-18
              • 2014-03-05
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多