显示DialogFragment抛出“无法执行此操作后onSaveInstanceState”错误
问题
嗨,我正在为Titanium创建一个Android和iOS模块,它有一个sendLog方法,它将一些任意的JSON数据发送到一个服务器,并返回一个URL,如果它匹配一些预定义的过滤器。 该网址应该以带有webview的模式对话框打开。
我编写了原生的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
由于dialogfragment没有commitAllowingStateLoss选项,所以我使用的最简单的解决方案是在调用onSaveInstance时设置一个标志,并将其重置在onCreate
和onRestoreInstance
。 然后在做一个片段事务之前,检查这个标志以确保它是错误的。 顺便说一句,这通常发生在一个异步回调。 当后台工作完成并且回调触发时,活动已经超越了保存实例。
这不是一个微不足道的问题,所以没有简单快速的解决方法,您可以从答案中复制/粘贴。 底线是,你将不得不重构你的一些代码。
您正尝试显示DialogFragment
以响应异步操作 – 如果在onSaveInstanceState
之后该操作完成,则回调将尝试显示对话框并引发IllegalStateException
。
保护自己免受这个问题的方法是不直接从回调做UI的东西。 相反,您需要等到您启动或恢复Activity
或Fragment
,才能安全地显示对话框。
一个简单的方法是使用粘性事件,即从回调中发布粘性事件,并在UI组件的onResume
方法中订阅该类型的粘性事件。
如果您不想使用事件总线库,则可以改为从非UI组件中调用异步方法,该组件在回调中更新其内部状态,然后使UI组件在onResume
检查该状态。 如果您使用这种方法,则需要小心管理您的全局状态。
我知道这个问题已经有一个正确的答案,但我想分享我的解决方案,以显示DialogFragment你应该重写它的show()
方法,并调用commitAllowingStateLoss()
对象。 这里是Kotlin的例子:
override fun show(manager: FragmentManager?, tag: String?) { try { val ft = manager?.beginTransaction() ft?.add(this, tag) ft?.commitAllowingStateLoss() } catch (ignored: IllegalStateException) { } }
参考@丹尼斯评论,我已经覆盖了方法显示,它的工作原理。
@Override public void show(FragmentManager manager, String tag) { FragmentTransaction fragmentTransaction = manager.beginTransaction(); fragmentTransaction.add(this, TAG); fragmentTransaction.commitAllowingStateLoss(); }
希望这可以帮助