【问题标题】:Showing a DialogFragment throws "Can not perform this action after onSaveInstanceState" error显示 DialogFragment 会引发“onSaveInstanceState 后无法执行此操作”错误
【发布时间】:2016-09-22 15:12:48
【问题描述】:

问题

您好,我正在为 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 是否不允许使用 Fragments/或要求您在特定点调用 Dialog.show()?该错误涉及“...在 onSaveInstanceState 之后”,但如果我没有创建活动,我不知道如何在 onSaveInstanceState 之前调用它,以及为什么在本机 Android 应用程序中使用代码时代码有效。

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

【问题讨论】:

    标签: android module appcelerator appcelerator-titanium dialogfragment


    【解决方案1】:

    这不是一个微不足道的问题,因此没有快速简便的解决方法,您只需从答案中复制/粘贴即可。底线是你将不得不重构你的一些代码。

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

    保护自己免受此问题影响的方法是不要直接通过回调处理 UI。相反,您需要等到开始或恢复ActivityFragment 之后,才能安全地显示对话框。

    一种简单的方法是使用粘滞事件,即从回调中发布粘滞事件并在 UI 组件的 onResume 方法中订阅该类型的粘滞事件。

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

    【讨论】:

    • 这确实是我迄今为止找到的解决方案,但它们对我不起作用。出现 2 个问题: 1) 我无法直接访问 onResume 方法,因为该库用于 Appcelerator Titanium,它有自己的 Activity 子类,并且您使用 Javascript 来编写视图。 2)当我直接调用此方法而不是从回调中调用时,它会显示相同的错误。
    • 我已经为 DialogFragment 提供了一个替代解决方案,可以避免这个异常:github.com/AndroidDeveloperLB/DialogShard
    【解决方案2】:

    我知道这个问题已经有了正确的答案,但我想分享我的解决方案,以显示 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) {
    
            }
    
        }
    

    【讨论】:

    • 内部标志 mDismissed = false;mShownByMe = true; 似乎没有被调用?
    【解决方案3】:

    参考@Dennis 评论,我已经覆盖了方法 show 并且它有效。

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

    希望对你有帮助

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        我遇到了同样的问题,并通过在 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);
                }
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2014-03-05
          • 1970-01-01
          • 1970-01-01
          • 2017-06-21
          • 2015-11-26
          • 1970-01-01
          • 2012-12-25
          • 2016-09-06
          • 2014-10-18
          相关资源
          最近更新 更多