如何从保留片段托管的异步任务中传递和保存对 UI 的更改?
How to deliver and persist changes to the UI from an asynchronous task hosted by a retained fragment?
使用保留的片段来托管异步任务并不是一个新想法(请参阅 Alex Lockwood 关于该主题的优秀 blog post)
但是在使用它之后,我在将内容从 AsyncTask 回调传送回我的 activity 时遇到了问题。具体来说,我发现尝试关闭对话框可能会导致 IllegalStateException。同样,可以在 Alex Lockwood 的 another blog post 中找到对此的解释。具体来说,本节解释发生了什么:
Avoid performing transactions inside asynchronous callback methods.
This includes commonly used methods such as AsyncTask#onPostExecute()
and LoaderManager.LoaderCallbacks#onLoadFinished(). The problem with
performing transactions in these methods is that they have no
knowledge of the current state of the Activity lifecycle when they are
called. For example, consider the following sequence of events:
- An activity executes an AsyncTask.
- The user presses the "Home" key,
causing the activity's onSaveInstanceState() and onStop() methods to
be called.
- The AsyncTask completes and onPostExecute() is called,
unaware that the Activity has since been stopped.
- A FragmentTransaction is committed inside the onPostExecute() method,
causing an exception to be thrown.
然而,在我看来,这是一个更广泛问题的一部分,只是碰巧片段管理器抛出一个异常让你意识到这一点。通常,您在 onSaveInstanceState()
之后对 UI 所做的任何更改都将丢失。所以建议
Avoid performing transactions inside asynchronous callback methods.
实际应该是:
Avoid performing UI updates inside asynchronous callback methods.
问题:
- 如果使用此模式,您是否应该因此取消您的任务,以防止
onSaveInstanceState()
中的回调(如果不轮换)?
像这样:
@Override
public void onSaveInstanceState(Bundle outState)
{
if (!isChangingConfigurations())
{
//if we aren't rotating, we need to lose interest in the ongoing task and cancel it
mRetainedFragment.cancelOnGoingTask();
}
super.onSaveInstanceState(outState);
}
您是否应该费心使用保留片段来保留正在进行的任务?总是在模型中标记一些关于正在进行的请求的东西会更有效吗?或者做一些像 RoboSpice 这样的事情,你可以重新连接到一个正在进行的任务,如果它是挂起的。要获得与保留片段类似的行为,如果您因配置更改以外的原因停止任务,则必须取消任务。
从第一个问题继续:即使在配置更改期间,您也不应该在 onSaveInstanceState()
之后进行任何 UI 更新,所以您实际上应该这样做:
粗略代码:
@Override
public void onSaveInstanceState(Bundle outState)
{
if (!isChangingConfigurations())
{
//if we aren't rotating, we need to lose interest in the ongoing task and cancel it
mRetainedFragment.cancelOnGoingTask();
}
else
{
mRetainedFragment.beginCachingAsyncResponses();
}
super.onSaveInstanceState(outState);
}
@Override
public void onRestoreInstanceState(Bundle inState)
{
super.onRestoreInstanceState(inState);
if (inState != null)
{
mRetainedFragment.stopCachingAndDeliverAsyncResponses();
}
}
beginCachingAsyncResponses()
会做一些类似 PauseHandler 的事情 here
从开发人员的角度来看,避免实时应用程序中的 NPE 是首要任务。对于 AsyncTask
的 onPostExecute()
和 Volley Request
中的 onResume()
& onError()
等方法,添加:
Activity = getActivity();
if(activity != null && if(isAdded())){
// proceed ...
}
在Activity
里面应该是
if(this != null){
// proceed ...
}
这很不雅。而且效率低下,因为其他线程上的工作有增无减。但这将使应用程序躲避 NPE。除此之外,还有onPause()
、onStop()
、onDestroy()
.
中各种cancel()
方法的调用
现在来谈谈更普遍的配置更改和应用程序退出问题。我读过 AsyncTask
s 和 Volley Request
s 应该只从 Service
s 而不是 Activity
s 执行,因为 Service
s 继续 运行 即使用户 "exits" 应用
所以我自己对此进行了深入研究,得出了一个很好的答案。
虽然没有记录这样做,但 activity 状态更改是在同步块中执行的。也就是说,一旦配置更改开始,UI 线程将从 onPause
一直忙到 onResume
。因此,没有必要像我在问题中那样使用 beginCachingAsyncResponses
之类的东西,因为在配置更改开始后不可能跳转到主线程。
你可以通过扫描源代码看到这是真的:http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.0.2_r1/android/app/ActivityThread.java#3886 看看这个,看起来 onSaveInstancestate
是用 handleDestroyActivity 顺序完成的......所以不可能更新UI 它在配置更改期间丢失了。
所以这应该足够了:
@Override
public void onSaveInstanceState(Bundle outState)
{
if (!isChangingConfigurations())
{
//if we aren't rotating, we need to lose interest in the ongoing task and cancel it
mRetainedFragment.cancelOnGoingTask();
}
super.onSaveInstanceState(outState);
}
从保留的片段中,从主线程访问 activity 至关重要:
public void onSomeAsyncNetworkIOResult(Result r)
{
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = new Runnable()
{
//If we were to call getActivity here, it might be destroyed by the time we hit the main thread
@Override
public void run()
{
//Now we are running on the UI thread, we cannot be part-way through a config change
// It's crucial to call getActivity from the main thread as it might change otherwise
((MyActivity)getActivity()).handleResultInTheUI(r);
}
};
mainHandler.post(myRunnable);
return;
}
使用保留的片段来托管异步任务并不是一个新想法(请参阅 Alex Lockwood 关于该主题的优秀 blog post)
但是在使用它之后,我在将内容从 AsyncTask 回调传送回我的 activity 时遇到了问题。具体来说,我发现尝试关闭对话框可能会导致 IllegalStateException。同样,可以在 Alex Lockwood 的 another blog post 中找到对此的解释。具体来说,本节解释发生了什么:
Avoid performing transactions inside asynchronous callback methods.
This includes commonly used methods such as AsyncTask#onPostExecute() and LoaderManager.LoaderCallbacks#onLoadFinished(). The problem with performing transactions in these methods is that they have no knowledge of the current state of the Activity lifecycle when they are called. For example, consider the following sequence of events:
- An activity executes an AsyncTask.
- The user presses the "Home" key, causing the activity's onSaveInstanceState() and onStop() methods to be called.
- The AsyncTask completes and onPostExecute() is called, unaware that the Activity has since been stopped.
- A FragmentTransaction is committed inside the onPostExecute() method, causing an exception to be thrown.
然而,在我看来,这是一个更广泛问题的一部分,只是碰巧片段管理器抛出一个异常让你意识到这一点。通常,您在 onSaveInstanceState()
之后对 UI 所做的任何更改都将丢失。所以建议
Avoid performing transactions inside asynchronous callback methods.
实际应该是:
Avoid performing UI updates inside asynchronous callback methods.
问题:
- 如果使用此模式,您是否应该因此取消您的任务,以防止
onSaveInstanceState()
中的回调(如果不轮换)?
像这样:
@Override
public void onSaveInstanceState(Bundle outState)
{
if (!isChangingConfigurations())
{
//if we aren't rotating, we need to lose interest in the ongoing task and cancel it
mRetainedFragment.cancelOnGoingTask();
}
super.onSaveInstanceState(outState);
}
您是否应该费心使用保留片段来保留正在进行的任务?总是在模型中标记一些关于正在进行的请求的东西会更有效吗?或者做一些像 RoboSpice 这样的事情,你可以重新连接到一个正在进行的任务,如果它是挂起的。要获得与保留片段类似的行为,如果您因配置更改以外的原因停止任务,则必须取消任务。
从第一个问题继续:即使在配置更改期间,您也不应该在
onSaveInstanceState()
之后进行任何 UI 更新,所以您实际上应该这样做:
粗略代码:
@Override
public void onSaveInstanceState(Bundle outState)
{
if (!isChangingConfigurations())
{
//if we aren't rotating, we need to lose interest in the ongoing task and cancel it
mRetainedFragment.cancelOnGoingTask();
}
else
{
mRetainedFragment.beginCachingAsyncResponses();
}
super.onSaveInstanceState(outState);
}
@Override
public void onRestoreInstanceState(Bundle inState)
{
super.onRestoreInstanceState(inState);
if (inState != null)
{
mRetainedFragment.stopCachingAndDeliverAsyncResponses();
}
}
beginCachingAsyncResponses()
会做一些类似 PauseHandler 的事情 here
从开发人员的角度来看,避免实时应用程序中的 NPE 是首要任务。对于 AsyncTask
的 onPostExecute()
和 Volley Request
中的 onResume()
& onError()
等方法,添加:
Activity = getActivity();
if(activity != null && if(isAdded())){
// proceed ...
}
在Activity
里面应该是
if(this != null){
// proceed ...
}
这很不雅。而且效率低下,因为其他线程上的工作有增无减。但这将使应用程序躲避 NPE。除此之外,还有onPause()
、onStop()
、onDestroy()
.
cancel()
方法的调用
现在来谈谈更普遍的配置更改和应用程序退出问题。我读过 AsyncTask
s 和 Volley Request
s 应该只从 Service
s 而不是 Activity
s 执行,因为 Service
s 继续 运行 即使用户 "exits" 应用
所以我自己对此进行了深入研究,得出了一个很好的答案。
虽然没有记录这样做,但 activity 状态更改是在同步块中执行的。也就是说,一旦配置更改开始,UI 线程将从 onPause
一直忙到 onResume
。因此,没有必要像我在问题中那样使用 beginCachingAsyncResponses
之类的东西,因为在配置更改开始后不可能跳转到主线程。
你可以通过扫描源代码看到这是真的:http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.0.2_r1/android/app/ActivityThread.java#3886 看看这个,看起来 onSaveInstancestate
是用 handleDestroyActivity 顺序完成的......所以不可能更新UI 它在配置更改期间丢失了。
所以这应该足够了:
@Override
public void onSaveInstanceState(Bundle outState)
{
if (!isChangingConfigurations())
{
//if we aren't rotating, we need to lose interest in the ongoing task and cancel it
mRetainedFragment.cancelOnGoingTask();
}
super.onSaveInstanceState(outState);
}
从保留的片段中,从主线程访问 activity 至关重要:
public void onSomeAsyncNetworkIOResult(Result r)
{
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = new Runnable()
{
//If we were to call getActivity here, it might be destroyed by the time we hit the main thread
@Override
public void run()
{
//Now we are running on the UI thread, we cannot be part-way through a config change
// It's crucial to call getActivity from the main thread as it might change otherwise
((MyActivity)getActivity()).handleResultInTheUI(r);
}
};
mainHandler.post(myRunnable);
return;
}