代码之家  ›  专栏  ›  技术社区  ›  Ventis

显示DialogFragment会引发“Can not perform this action after onSaveInstanceState”错误

  •  2
  • Ventis  · 技术社区  · 9 年前

    问题

    嗨,我正在为Titanium创建一个Android和iOS模块,它有一个sendLog方法,可以将一些任意的JSON数据发送到服务器,如果它匹配一些预定义的过滤器,则返回一个URL。URL应在带有Web视图的模式对话框中打开。

    我编写了本地iOS和Android库,并将它们包装为Titanium模块。在iOS上一切正常,但在Android上我无法打开对话框(请参阅下面的错误堆栈跟踪)。现在有一条日志消息总是触发同一个网页进行测试。在Android上,它只是无声无息地失败了。

    测试用例

    var mupets = require("be.iminds.mupets");
    mupets.initialize("wappr", "http://tocker.iminds.be:3000/log/report.json", 1, 100, 3);
    var esmLog = { 
        bar: "foo"
    };
    mupets.sendLog("es-test-01",JSON.stringify(esmLog));
    

    在这段代码后(最多10秒后),模块应显示一个本地对话框,其中包含以下网页: http://tocker.iminds.be:3000/es/sheets/test-01/index.html

    相反,这是我经常犯的错误:

    日志

    java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1411)
    at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1429)
    at android.app.BackStackRecord.commitInternal(BackStackRecord.java:687)
    at android.app.BackStackRecord.commit(BackStackRecord.java:663)
    at android.app.DialogFragment.show(DialogFragment.java:256)
    at be.iminds.mupets_client_android.logging.plugins.OutHttp.getEsm(OutHttp.java:122)
    at be.iminds.mupets_client_android.logging.plugins.OutHttp$1.success(OutHttp.java:78)
    at be.iminds.mupets_client_android.HttpClient$1$1.onResponse(HttpClient.java:76)
    at okhttp3.RealCall$AsyncCall.execute(RealCall.java:133)
    at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
    at java.lang.Thread.run(Thread.java:818)
    

    以下是导致错误的Android代码:

    Activity activity = (Activity) context;
                    EsmDialogFragment esmDialogFragment = EsmDialogFragment.newInstance(new EsmDialogListener() {
                        @Override
                        public void submit(String type, JsonObject result) {
                            Mupets.sendLog(type, result);
                            esmShown = false;
                        }
    
                        @Override
                        public void onCancel(JsonObject cancelled) {
                            super.onCancel(cancelled);
                            Mupets.sendLog("ESM_cancelled", cancelled);
                            esmShown = false;
                        }
                    }, url, true);
                    FragmentTransaction transaction = activity.getFragmentManager().beginTransaction();
                    Fragment prev = activity.getFragmentManager().findFragmentByTag(EsmDialogFragment.ESM_DIALOG_FRAGMENT);
                    if (prev != null) {
                        transaction.remove(prev);
                    }
                    transaction.addToBackStack(null);
                    Log.v(TAG, "Pre-show fragment");
                    esmDialogFragment.show(transaction, EsmDialogFragment.ESM_DIALOG_FRAGMENT);
                    Log.v(TAG, "Post-show fragment");
    

    Titanium是否不允许使用碎片/或要求您呼叫Dialog。在特定点显示()?该错误涉及“…onSaveInstanceState之后”,但我不知道如果我没有创建活动,我会如何在onSaveInstanceState之前调用它,以及当我在原生Android应用程序中使用它时,为什么代码会工作。

    这是一个Titanium示例项目,其中包含打开后应显示对话框的模块: https://www.dropbox.com/s/0v77xd5gllv6kb3/testModule.zip?dl=1

    5 回复  |  直到 9 年前
        1
  •  6
  •   npace    9 年前

    这不是一个微不足道的问题,因此没有简单快捷的修复方法,您只需从答案中复制/粘贴即可。归根结底,您必须重构一些代码。

    您正在尝试显示 DialogFragment 响应异步操作-如果该操作在之后完成 onSaveInstanceState ,回调将尝试显示对话框并诱导 IllegalStateException .

    保护自己不受此问题影响的方法是不要直接从回调中执行UI操作。相反,你需要推迟,直到你开始或恢复 Activity Fragment ,以便可以安全地显示对话框。

    一种简单的方法是使用粘性事件,即从回调中发布粘性事件,并在UI组件的 onResume 方法

    如果不想使用事件总线库,可以改为从非UI组件调用异步方法,该组件在回调中更新其内部状态,然后让UI组件检查该状态 onResume(继续) 。如果使用此方法,则需要小心管理全局状态。

        2
  •  2
  •   Dennis Zinkovski    8 年前

    我知道这个问题已经有了正确的答案,但我想分享我的解决方案,以显示您应该重写的DialogFragment show() 方法和调用 commitAllowingStateLoss() 在…上 Transaction 对象以下是科特林的例子:

    override fun show(manager: FragmentManager?, tag: String?) {
            try {
                val ft = manager?.beginTransaction()
                ft?.add(this, tag)
                ft?.commitAllowingStateLoss()
            } catch (ignored: IllegalStateException) {
    
            }
    
        }
    
        3
  •  2
  •   twelvester    8 年前

    关于@Dennis注释,我已经覆盖了show方法,它可以工作了。

    @Override
    public void show(FragmentManager manager, String tag) {
        FragmentTransaction fragmentTransaction = manager.beginTransaction();
        fragmentTransaction.add(this, TAG);
        fragmentTransaction.commitAllowingStateLoss();
    }
    

    希望这有帮助

        4
  •  0
  •   Thupten    8 年前

    因为dialogfragment没有选项 commitAllowingStateLoss ,我使用的最简单的解决方案是在调用onSaveInstance时设置一个标志并将其重置为 onCreate onRestoreInstance 然后,在进行片段事务之前,请检查标志以确保其为false。顺便说一句,这通常发生在异步回调中。后台工作完成并触发回调时,该活动已超出onSaveInstance。

        5
  •  0
  •   Parth Jadav    4 年前

    我遇到了同样的问题,并通过在DialogFragment扩展类中重写show()解决了这个问题。

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