Android AsyncTask 上下文行为
Posted
技术标签:
【中文标题】Android AsyncTask 上下文行为【英文标题】:Android AsyncTask context behavior 【发布时间】:2011-01-08 15:26:23 【问题描述】:我一直在使用 android 中的 AsyncTasks,我正在处理一个问题。
举一个简单的例子,一个带有一个 AsyncTask 的 Activity。后台的任务并没有做任何壮观的事情,它只是休眠了 8 秒。
在 onPostExecute() 方法中的 AsyncTask 结束时,我只是将按钮可见性状态设置为 View.VISIBLE,只是为了验证我的结果。
现在,这非常有效,直到用户决定在 AsyncTask 工作时(在 8 秒睡眠窗口内)改变他的手机方向。
我了解 Android Activity 生命周期,并且我知道该 Activity 会被销毁并重新创建。
这就是问题所在。AsyncTask 指的是一个按钮,并且显然包含对首先启动 AsyncTask 的上下文的引用。
我希望,这个旧上下文(因为用户导致方向更改)要么变为 null,并且 AsyncTask 抛出 NPE 以引用它试图使其可见的按钮。
相反,没有抛出 NPE,AsyncTask 认为按钮引用不为空,将其设置为可见。结果?屏幕上什么都没有发生!
更新:我已经通过在活动中保留WeakReference
并在配置更改发生时切换来解决这个问题。这很麻烦。
代码如下:
public class Main extends Activity
private Button mButton = null;
private Button mTestButton = null;
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mButton = (Button) findViewById(R.id.btnStart);
mButton.setOnClickListener(new OnClickListener ()
@Override
public void onClick(View v)
new taskDoSomething().execute(0l);
);
mTestButton = (Button) findViewById(R.id.btnTest);
private class TaskDoSomething extends AsyncTask<Long, Integer, Integer>
@Override
protected Integer doInBackground(Long... params)
Log.i("LOGGER", "Starting...");
try
Thread.sleep(8000);
catch (InterruptedException e)
e.printStackTrace();
return 0;
@Override
protected void onPostExecute(Integer result)
Log.i("LOGGER", "...Done");
mTestButton.setVisibility(View.VISIBLE);
尝试执行它并在 AsyncTask 工作时更改您的手机方向。
【问题讨论】:
写“保持对活动的弱引用”而不是在代码中这样做有点误导。更糟糕的是,您进一步说“并在发生配置更改时切换”,这实际上没有任何意义......切换什么? 【参考方案1】:AsyncTask 并非设计为在 Activity 被拆除并重新启动后重复使用。正如您所说,内部 Handler 对象变得陈旧。在 Romain Guy 的 Shelves 示例中,他简单地取消了任何当前正在运行的 AsyncTask,然后在方向更改后重新启动新的。
可以将您的 Thread 移交给新的 Activity,但它会增加很多管道。没有普遍同意的方法,但你可以在这里阅读我的方法:http://foo.jasonhudgins.com/2010/03/simple-progressbar-tutorial.html
【讨论】:
【参考方案2】:如果您只需要一个上下文而不将其用于 ui 内容,您可以简单地将 ApplicationContext 传递给您的 AsyncTask。例如,您通常需要系统资源的上下文。
不要尝试从 AsyncTask 更新 UI,并尽量避免自己处理配置更改,因为它可能会变得混乱。为了更新 UI,您可以注册一个广播接收器并发送一个广播。
您还应该将 AsyncTask 作为与上述活动分开的公共类,它使测试变得更加容易。不幸的是,Android 编程经常会强化不良做法,而官方示例也无济于事。
【讨论】:
【参考方案3】:这是导致我始终防止我的 Activity 在方向更改时被销毁/重新创建的类型。
为此,请将其添加到清单文件中的 <Activity>
标记中:
android:configChanges="orientation|keyboardHidden"
并在您的 Activity 类中覆盖 onConfigurationChanged:
@Override
public void onConfigurationChanged(final Configuration newConfig)
// Ignore orientation change to keep activity from restarting
super.onConfigurationChanged(newConfig);
【讨论】:
您的方法有效,但您对这种方法有很好的反驳意见吗?为什么默认情况下操作系统会破坏并重新创建活动呢?换句话说,按照您的建议,我会“失去”什么?顺便说一句,它按预期工作。感谢您的回复。 据我所知,您将失去的主要是横向和纵向模式具有不同 Activity 布局的能力。您可能会“失去”其他一些东西,但我不知道它们是什么。 除非您知道自己在做什么,否则 Google 通常不建议使用这种方法。 我了解活动也可能因来电等原因而结束。如果是这样的话,你还是会有问题的,对吧? 我建议避免使用此解决方案。问题 #1 - 可以在配置更改时重新启动活动。除了方向变化之外,这还可以是字体大小、语言环境等的变化。问题#2 - AsyncTask 有对 Activity 的引用。如果您完成() Activity,它将保留在内存中,直到 AsyncTask 完成。如果用户启动 Activity、按 BACK、再次启动等,内存中可能存在同一个 Activity 的多个实例,并可能导致 OOM 错误。问题 #3 - 您不能将不同的资源用于纵向/横向。【参考方案4】:为避免这种情况,您可以在此处使用答案:https://***.com/a/2124731/327011
但是,如果您需要销毁活动(纵向和横向的不同布局),您可以将 AsyncTask 设为公共类(在此处阅读为什么它不应该是私有的 Android: AsyncTask recommendations: private class or public class?),然后创建一个方法 setActivity 来设置在销毁/创建时引用当前活动。
您可以在此处查看示例:Android AsyncTask in external class
【讨论】:
以上是关于Android AsyncTask 上下文行为的主要内容,如果未能解决你的问题,请参考以下文章
Android:AsyncTask 需要一个上下文来在离开包含活动后显示警报
在上下文不工作的情况下从 AsyncTask onPostExecute 调用 Android Intent