在Android中的WeakReference/AsyncTask模式

60
我有一个关于在Android中经常出现的简单情况的问题。
我们有一个主活动,我们调用一个AsyncTask并带有MainActivity的引用,以便AsyncTask可以更新MainActivity上的视图。
我将事件分解为以下步骤:
- MainActivity创建AyncTask,传递其引用。 - AysncTask开始工作,例如下载十个文件。 - 用户更改了设备的方向。这会导致AsyncTask中的孤立指针。 - 当AsyncTask完成并尝试访问活动以更新状态时,由于空指针而崩溃。
以上问题的解决方法是在AsyncTask中保留弱引用,正如《Pro Android 4》书籍推荐的那样。
WeakReference<Activity> weakActivity;

in method onPostExecute

Activity activity = weakActivity.get();
if (activity != null) {
   // do your stuff with activity here
}

这种情况如何解决?

我的问题是,如果我的异步任务正在下载十个文件,在完成其中五个后活动会重新启动(因为方向发生了变化),那么我的FileDownloadingTask会再次被调用吗?之前最初调用的AsyncTask会发生什么?非常感谢您,对于问题的长度我感到很抱歉。


9
谢谢您发布这样一个措辞清晰得体的问题。 - Travis
3个回答

36
这如何解决这种情况? WeakReference 允许 Activity 被垃圾收集,因此避免了内存泄漏。
空引用意味着 AsyncTask 无法盲目尝试更新已经不再连接的用户界面,否则会抛出异常(例如,视图未附加到窗口管理器)。当然,你必须检查 null 值以避免 NPE。
如果我的 AsyncTask 在下载十个文件时,在完成前五个文件时重新启动活动(因为方向发生改变),那么我的文件下载任务是否会再次被调用?
这取决于你的实现,但可能是,除非你有意做一些事情使重复下载变得不必要,例如在某个地方缓存结果。
上一个最初被调用的 AsyncTask 会发生什么?
在早期的 Android 版本中,它将运行到完成,下载所有的文件只是为了扔掉它们(或者根据你的实现进行缓存)。
在较新的 Android 版本中,我怀疑 AsyncTask 与启动它们的 Activity 一同终止,但我怀疑的基础仅限于 RoboSpice 的内存泄漏演示在我的 JellyBean 设备上实际上没有泄漏。
如果我可以提供一些建议: AsyncTask 不适合执行可能运行时间较长的任务,例如网络操作。如果你可以接受单个工作线程,则 IntentService 是更好(且仍相对简单)的方法。如果你想要控制线程池,请使用(本地)Service - 并小心不要在主线程上执行任务!如果你正在寻找一种可靠地在后台进行网络操作的方式,RoboSpice 似乎是一个不错的选择(声明:我没有尝试过;我也没有关联)。Play商店中有一个RoboSpice Motivations演示应用,可以通过演示所有可能出现的问题(包括使用弱引用的解决方法)来说明为什么应该使用它。
另请参考这个主题:Is AsyncTask really conceptually flawed or am I just missing something? 更新:
我创建了一个GitHub项目,其中包含一个使用 IntentService 进行下载的示例,以回答另一个SO问题(如何解决android.os.NetworkOnMainThreadException?),但我认为它在这里也是相关的。它的另一个优点是通过onActivityResult返回结果,当您旋转设备时,正在进行下载的内容将提交给重新启动的Activity

2
小更新:现在RxJava是在后台运行任务、脱离主线程并将结果传递到主线程的好方法(这样您就可以更新UI等)。另一个选择是使用EventBus。 - AgentKnopf

7
WeakReference类基本上只是防止JRE增加给定实例的引用计数。
我不会深入讲解Java的内存管理,直接回答你的问题: WeakReference通过为AsyncTask提供一种方法来了解其父活动是否仍然有效来解决这种情况。
方向更改本身不会自动重新启动AsyncTask。您必须使用已知机制(onCreate/onDestroyonSave/RestoreInstanceState)编写所需的行为。
关于原始的AsyncTask,我不确定以下哪些选项将发生:
要么Java停止线程并丢弃AsyncTask,因为持有对它的唯一引用的对象(原始Activity)已被销毁
要么某些内部Java对象维护对AsyncTask对象的引用,阻止其垃圾收集,从而在后台保留AsyncTask以完成任务。
无论哪种方式,手动中止/暂停并重新启动/恢复AsyncTask(或将其移交给新的Activity),或者使用Service都是很好的做法。

当您使用Activity的AsyncTask内部类时,WeakReference是无用的。 - Qamar

2
这怎么解决问题呢?它并不能解决。

当垃圾收集器确定引用对象是弱可达时,WeakReference的引用对象会被设置为null。当一个活动被暂停时,这种情况不会发生,而且当活动被销毁并且框架丢弃所有对其的引用时,也不一定会立即发生。如果GC没有运行,那么AsyncTask在其WeakReference仍然包含对已死亡活动的引用时完成是完全可能的。

不仅如此,这种方法无法防止AsyncTask无意中消耗CPU资源。

更好的方法是让Activity维护AsyncTask的强引用,并在适当的拆除生命周期方法中使用cancel(...)方法取消它。AsyncTask应该监视isCancelled()并在不再需要时停止工作。

如果你想要一个AsyncTask在配置更改后仍然存在(但不是其他形式的活动销毁),你可以将它托管在一个保留的片段中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接