java 如何在 AsyncTask 完成后处理解除 DialogFragment(兼容性库)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10760809/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can you handle dismissing a DialogFragment (compatibility lib) upon completion of an AsyncTask
提问by Chuck Krutsinger
There are numerous posts about how to handle a configuration change during an AsyncTask, but none I have found give a clear solution regarding apps that are in background (onPause()) when an AsyncTask finishes and tries to dismiss a DialogFragment (compatibility library).
有很多关于如何在 AsyncTask 期间处理配置更改的帖子,但我发现没有一篇文章提供了关于当 AsyncTask 完成并尝试关闭 DialogFragment(兼容性库)时后台应用程序 (onPause()) 的明确解决方案。
Here is the problem, if I have an AsyncTask running that should dismiss a DialogFragment in onPostExecute(), I get an IllegalStateException if the app is in the background when it tries to dismiss the DialogFragment.
这就是问题所在,如果我有一个 AsyncTask 正在运行,它应该在 onPostExecute() 中关闭 DialogFragment,如果应用程序在尝试关闭 DialogFragment 时处于后台,我会收到一个 IllegalStateException。
private static class SomeTask extends AsyncTask<Void, Void, Boolean> {
public SomeTask(SomeActivity tActivity)
{
mActivity = tActivity;
}
private SomeActivity mActivity;
/** Set the view during/after config change */
public void setView(Activity tActivity) {
mActivity tActivity;
}
@Override
protected Boolean doInBackground(Void... tParams) {
try {
//simulate some time consuming process
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException ignore) {}
return true;
}
@Override
protected void onPostExecute(Boolean tRouteFound) {
mActivity.dismissSomeDialog();
}
}
The Activity looks like this:
活动如下所示:
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
public class SomeActivity extends FragmentActivity {
public void someMethod() {
...
displaySomeDialog();
new SomeTask(this).execute();
...
}
public void displaySomeDialog() {
DialogFragment someDialog = new SomeDialogFragment();
someDialog.show(getFragmentManager(), "dialog");
}
public void dismissSomeDialog() {
SomeDialogFragment someDialog = (SomeDialogFragment) getFragmentManager().findFragmentByTag("dialog");
someDialog.dismiss();
}
....
}
Works fine UNLESS the app switches to background while SomeTask is still running. In that case, when SomeTask tries to dismissSomeDialog(), I get an IllegalStateException.
除非在 SomeTask 仍在运行时应用程序切换到后台,否则工作正常。在这种情况下,当 SomeTask 尝试关闭 SomeDialog() 时,我会收到一个 IllegalStateException。
05-25 16:36:02.237: E/AndroidRuntime(965): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
All of the posts I've seen seem to point in some kludgy direction with elaborate workarounds. Isn't there some android way of handling this? If it were a Dialog instead of a DialogFragment, then the Activity's dismissDialog() would handle it correctly. If it were a real DialogFragment instead of one from the ACP, then dismissAllowingStateLoss() would handle it. Isn't there something like this for the ACP version of DialogFragment?
我看到的所有帖子似乎都指向了一些带有精心设计的变通方法的笨拙方向。没有一些android方法来处理这个吗?如果它是 Dialog 而不是 DialogFragment,那么 Activity 的dismissDialog() 将正确处理它。如果它是一个真正的 DialogFragment 而不是来自 ACP 的一个,那么dismissAllowingStateLoss() 会处理它。DialogFragment 的 ACP 版本没有类似的东西吗?
回答by Alex Lockwood
Fragments are saved as part of each Activity's state, so performing transactions after onSaveInstanceState()
has been called technically doesn't make sense.
Fragment 被保存为每个 Activity 状态的一部分,因此在onSaveInstanceState()
技术上调用之后执行事务没有意义。
You definitely don't want to use commitAllowingStateLoss()
to avoid the exception in this case. Consider this scenario as an example:
commitAllowingStateLoss()
在这种情况下,您绝对不想使用来避免异常。以这个场景为例:
- The Activity executes an
AsyncTask
. TheAsyncTask
shows aDialogFragment
inonPreExecute()
and starts executing its task on a background thread. - The user clicks "Home" and the
Activity
is stopped and forced into the background. The system decides that the device is pretty low on memory so it decides that it should also destroy theActivity
too. - The
AsyncTask
completes andonPostExecute()
is called. InsideonPostExecute()
you dismiss theDialogFragment
usingcommitAllowingStateLoss()
to avoid the exception. - The user navigates back to the
Activity
. TheFragmentManager
will restore the state of its fragments based on theActivity
's saved state. The saved state doesn't know about anything afteronSaveInstanceState()
has been called, so the request to dismiss theDialogFragment
will not be remembered and theDialogFragment
will be restored even though theAsyncTask
has already completed.
- 活动执行一个
AsyncTask
. 该AsyncTask
节目一DialogFragment
中onPreExecute()
,开始执行在后台线程它的任务。 - 用户单击“主页”,然后
Activity
停止并强制进入后台。系统决定设备的内存非常低,因此它决定它也应该销毁它Activity
。 - 在
AsyncTask
完成和onPostExecute()
被调用。在内部,onPostExecute()
您关闭DialogFragment
usingcommitAllowingStateLoss()
以避免异常。 - 用户导航回
Activity
. 在FragmentManager
将恢复其片段的基础上,国家Activity
的保存状态。保存的状态在onSaveInstanceState()
被调用后不知道任何事情,因此解除请求的请求DialogFragment
将不会被记住,DialogFragment
即使AsyncTask
已经完成,也将恢复。
Because of weird bugs like these that can occasionally happen, it's usually not a good idea to use commitAllowingStateLoss()
to avoid this exception. Because the AsyncTask
callback methods (which are called in response to a background thread finishing its work) have absolutely nothing to do with the Activity
lifecycle methods (which are invoked by the system server process in response to system-wide external events, such as the device falling asleep, or memory running low), handling these situations require you to do a little extra work. Of course, these bugs are extremely rare, and protecting your app against them will often not be the difference between a 1 star rating and a 5 star rating on the play store... but it is still something to be aware of.
由于偶尔会发生此类奇怪的错误,因此commitAllowingStateLoss()
避免这种异常通常不是一个好主意。因为AsyncTask
回调方法(为响应后台线程完成其工作Activity
而调用)与生命周期方法(由系统服务器进程调用以响应系统范围的外部事件,例如设备掉落)完全无关睡着了,或者内存不足),处理这些情况需要你做一些额外的工作。当然,这些错误极其罕见,保护您的应用程序免受它们的侵害通常不是 Play 商店中 1 星评级和 5 星评级之间的区别……但这仍然是需要注意的事情。
Hopefully that made at least some sense. Also, note that Dialog
s also exist as part of the Activity
s state, so although using a plain old Dialog
might avoid the exception, you would essentially have the same problem (i.e. dismissing the Dialog
wouldn't be remembered when the Activity
's state is later restored).
希望这至少有意义。另外,请注意Dialog
s 也作为Activity
s 状态的一部分存在,因此尽管使用普通的 oldDialog
可能会避免异常,但您基本上会遇到相同的问题(即,Dialog
当Activity
's 状态稍后恢复时,不会记住关闭s ) .
To be frank, the best solution would be to avoid showing a dialog throughout the duration of the AsyncTask
. A much more user-friendly solution would be to show a indeterminate progress spinner in the ActionBar
(like the G+ and Gmail apps, for example). Causing major shifts in the user interface in response to asynchronous callbacks is bad for the user experience because it is unexpected and abruptly yanks the user out of what they are doing.
坦率地说,最好的解决方案是避免在整个AsyncTask
. 一个更加用户友好的解决方案是在ActionBar
(例如 G+ 和 Gmail 应用程序)中显示不确定的进度微调器。响应异步回调而导致用户界面发生重大变化对用户体验不利,因为它是出乎意料的,并且突然将用户从他们正在做的事情中拉出来。
See this blog poston the subject for more information.
有关更多信息,请参阅有关该主题的这篇博文。
回答by Zachary Moshansky
To get around the illegal state exception issue and essentially implement a dismissAllowingStateLoss() can be done using the following.
要解决非法状态异常问题并基本上实现dismissAllowingStateLoss() 可以使用以下方法完成。
getFragmentManager().beginTransaction().remove(someDialog).commitAllowingStateLoss();
This should solve the issue without the hacky code. The same can also be applied for show if you have threads communicating through a handler with the UI thread using dialog.show(); Which can cause an illegal state exception as well
这应该可以在没有 hacky 代码的情况下解决问题。如果您的线程通过处理程序与 UI 线程使用 dialog.show() 进行通信,则同样可以应用于 show;这也可能导致非法状态异常
getFragmentManager().beginTransaction().add(someDialog).commitAllowingStateLoss();
考虑到海报问题,@joneswah 是正确的。如果您使用的是支持库,请替换
getFragmentManager()
with
和
getSupportFragmentManager()
对于未来的 Google 员工:@Alex Lockwood 对这个解决方案提出了良好而有效的担忧。该解决方案确实解决了错误并且在大多数情况下都有效,但从用户体验的角度来看,暗示原始问题中的方法存在问题。
The Activity should assume that the async task may not complete and that it will not perform onPostExecute(). Whatever UI action (ie, spinner, ideally not a dialog) is started to notify the user of the async operation, should have provisions to stop automatically either on a timeout or by tracking state and checking in onRestore/onResume type lifecycle events to ensure the UI is updated properly. Services may also be worth investigating.
Activity 应该假设异步任务可能无法完成并且不会执行 onPostExecute()。无论 UI 操作(即微调器,理想情况下不是对话框)启动以通知用户异步操作,都应提供在超时或通过跟踪状态和检查 onRestore/onResume 类型生命周期事件自动停止的规定,以确保UI 已正确更新。服务也可能值得研究。
回答by Zambotron
You should cancel your AsyncTask in onPause() if the onPostExecute() is going to update the UI. You shouldn't try to update the UI while your activity has been paused.
如果 onPostExecute() 将更新 UI,您应该在 onPause() 中取消您的 AsyncTask。当您的活动已暂停时,您不应尝试更新 UI。
Eg. in your onPause():
例如。在您的 onPause() 中:
if (task != null) task.cancel(true);
If you want the changes from the task to persist to the next time, then store the data/changes in doInBackground() and then update the UI when your activity/fragment/dialog gets resumed.
如果您希望任务的更改持续到下一次,请将数据/更改存储在 doInBackground() 中,然后在您的活动/片段/对话框恢复时更新 UI。
If you don't want the changes from the task to persist, then don't store the changes until onPostExecute()
如果您不希望任务中的更改持续存在,则在 onPostExecute() 之前不要存储更改
回答by Chuck Krutsinger
After numerous redesigns I finally settled on an approach that seems to work. However, I haven't been able to find certain aspects of my design documented elsewhere in discussions of Android development. So I'd be interested in any feedback.
经过无数次重新设计后,我最终确定了一种似乎有效的方法。但是,在 Android 开发讨论的其他地方,我无法找到我设计的某些方面。所以我会对任何反馈感兴趣。
In summary, what I had to do is this:
总之,我必须做的是:
- onSaveInstanceState() - if SomeTask is running, I use a simple lock to block SomeTask from exiting doInBackground() during pause.
- onResume() - I had to put in a switch statement to handle different resume situations. If launching app I do nothing as nothing is paused, if restarting after being hidden or after config change I release the lock so that the preserved SomeTask instance can resume where it left off, etc.
- onDestroy() - I cancel SomeTask if it is running.
- onSaveInstanceState() - 如果 SomeTask 正在运行,我使用一个简单的锁来阻止 SomeTask 在暂停期间退出 doInBackground()。
- onResume() - 我不得不放入一个 switch 语句来处理不同的恢复情况。如果启动应用程序我什么都不做,因为没有暂停,如果在隐藏或配置更改后重新启动,我会释放锁定,以便保留的 SomeTask 实例可以从它停止的地方恢复,等等。
- onDestroy() - 如果 SomeTask 正在运行,我会取消它。
I'll put the code fragments for this solution in my original post.
我会将这个解决方案的代码片段放在我原来的帖子中。
回答by Glaucus
When Android stops your app because the user hit the back or home button, your dialogs are closed for you. Usually the trick is to preserve the dialogs between onStop()/onStart(). So unless you need to do more than just close the dialog, I'd say don't worry about it.
当 Android 因为用户点击后退或主页按钮而停止您的应用程序时,您的对话框将为您关闭。通常的技巧是保留 onStop()/onStart() 之间的对话。所以除非你需要做的不仅仅是关闭对话框,否则我会说不要担心。
EDIT: On your activity that hosts the dialog, you may still want to close the dialog if it's still open inside onStop(). This helps prevent memory leaks. But this doesn't need to be triggered from AsyncTask.
编辑:在您承载对话框的活动中,如果对话框仍在 onStop() 内打开,您可能仍希望关闭该对话框。这有助于防止内存泄漏。但这不需要从 AsyncTask 触发。
Like i said above, the problem is what happens when you hit onStart() again and your AsyncTask is NOT finished yet. You'll need to figure out a way to determine that and re-open that dialog if needed.
就像我上面说的,问题是当你再次点击 onStart() 并且你的 AsyncTask 还没有完成时会发生什么。您需要找出一种方法来确定并在需要时重新打开该对话框。
回答by Eli
getActivity().finish();
in the DialogFragment
worked for me.
getActivity().finish();
在DialogFragment
对我来说有效。