显示对话片段引发"Can not perform this action after onSaveInstanceState"错误



问题

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

我已经编写了原生iOS和Android库,并将它们封装为Titanium模块。在iOS上一切正常,但在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之后",但如果我不创建活动,我不知道在onSaveInstance State之前如何调用它,也不知道为什么当我在本机Android应用程序中使用它时,代码会起作用。

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

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

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

保护自己不受此问题影响的方法是不要直接从回调中执行UI操作。相反,您需要等待,直到启动或恢复ActivityFragment,以便可以安全地显示对话框。

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

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

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

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

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

希望这能帮助

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

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

相关内容

最新更新