本文由Lin Shen 译自 Common questions on AsyncTask ,原文作者 Colt McAnlis , 转载请务必注明出处!
我在 Google 工作最喜欢的一点,就是把一些比较复杂的概念,分解成一小部分一小部分,这样就可以确保每一位工程师都能清楚地理解。在我最近的《 Android 性能优化典范 》视频中,我提到了一个表面上看上去很直观,但是它的一些属性可能会带来一些万万没想到的负面影响的东西,我是说,毫无悬念,这货就是 关于 AsyncTask :
我在这个视频里强调了一些开发者之前在使用 AsyncTask 时可能并不会注意到的事项。多亏了我们有一些非常棒的 android 开发者交流社区,能让我们看到大家反馈的一些问题:
让我们来挖掘其中的一些问题,看看能不能深入展开讨论一下:
在 Java 当中, WeakReferences 是一个不错的引用对象的方法,而且当你持有对象的引用的时候,它还能保证不被 GC(garbage-collect) ( 这儿 有个关于这个问题不错的讨论)。
这么一来,用 WeakReference 就可以让你的异步任务任意对一个你感兴趣的 UI 对象持有引用,而且还不用担心造成内存泄露。
这确实很棒,因为这似乎直接解决了我们对内存泄露的担心。但是从它自身角度来看,这不是一个完整的解决方案;它并没有解决接下来的一个问题:当你的 UI 对象被销毁的时候,你该怎么办?
假设你持有了一个对 Activity 的引用,解决方案可能就比较简单,只要加几行代码判断一下 WeakReference.get() 是不是返回 null 就行,在一些 Activity 已经被销毁的情况下, 这样判断一下你就能知道接下来该怎么做了。
引用一些特定的 View 的时候也可以用相同的办法,但也会随之带来一些精神上的焦虑(至少对我而言),因为你要知道,你持有的这个对象,并没办法知道在异步操作的时候,它的数据状态是不是已经冻结了。举个例子,你正持有的这个对象已经被从 view 层级(view hierarchy)中移除了,或者它的内容已经被你一个已完成的操作给刷新了。这两个例子,只要有其中一种情况,你就仍然要考虑一些策略来应对这些场景。
所以说,使用 WeakReference 会帮助降低潜在的内存泄露风险,如果你愿意多写几行代码,一个对象一个对象地处理 WeakReference.get()
返回 null 的情况的话,那我无话可说。
完全正确。(译著:这里 Colt McAnlis 是想赞同上面这个开发者的评论,这个开发者认为静态嵌套类比内部类更好,而并不是赞同这个副标题)把 AsyncTask 声明成 activity 一个静态嵌套类(static nested class) , 可以消除一些隐式引用带来的七七八八的泄露问题。关于“静态嵌套类(static nested class)”和“内部类(inner class)”的区别, StackOverflow 上 这篇文章 总结得相当不错:
一个内部类的实例只能在一个外部类的实例中存在,而且对于它的外围实例(enclosing instance)中的方法和属性有直接访问权。
这段话帮助解释了前面说的那些 “七七八八的泄露问题” 是怎么来的。再直接一点的话,静态的嵌套类就不会干这样的事,它们的实例并不需要外部类的实例。
所以,这解决了你的关于“隐式引用”的问题,但没解决 “我们究竟应该怎样持有对 UI 对象的引用” 的问题,这还是讲到目前为止的一个首要问题。
从古至今,线程都是个很复杂的问题。一些线程相关的第三方库之所以成功,归功于它们“能将一些细微差别抽象出来”这个事实,而且能从代码层面使线程调度变得容易些。我记得 《 Intel Threading Building Blocks 》第一次出版的时候写到: “当我发现有一套‘久经考验’的线程安全容器可以给我使用,而不用我自己去折腾线程调度的时候,简直如释重负。结果也是自然而言,我只要花很少的时间去测试线程,剩下的时间就可以拿来 制定我邪恶的计划 (译著:美嘉里面的台词,老外写文章都喜欢这样)”
诚然, RxJava 不例外又是一个围绕特定类型的用例和 API 模型设计的线程库。 由于它的 API 提供的一些功能,一大帮 Android 开发者发誓“这就是我要的线程库!!!我的线程库时尚时尚最时尚……”。坦率地说,RxJava 对那些把线程相关的问题捧上天的人来讲,或者你的应用里确实有很多线程相关的复杂调度,对于这些人而言,RxJava 确实提供了很多不错的功能。
而对于那些持观望态度的人,请容我说几句:
首先, RxJava 说白了是一个纯 java 库。对于 Android 平台而言它没有任何特别的定制或者优化。
对那些用 java 开发服务端和客户端代码的人,这也许是一件幸事。对于服务端的人,这样做对于他们完成这项工作而言,不一定要去改变原来的“心智模式”。然而,客户端这边这么搞就比较麻烦了。我们知道 Android 有自己的线程模型,而 RxJava 在用的时候呢?有些代码或者思想又是必须得保留的。怎样在 Android 里保留这些纯粹为 Java 设计的代码或者思想,你得花一番功夫吧?
坦率地说,RxJava 只是对线程优先级操作进行的一个 API 层级的替换, 而对于 Android 特定的一些,比如解决 UI 对象(或者其它对象,如果它们也很重要的话)的线程安全问题,它什么也没做。 你依然还是要想着,还是要担心怎么正确地引用一个 UI 对象, 而且当配置变化的时候你还是要写额外的代码来处理。
技术层面上讲,现在也有 RxAndroid , 它在 RxJava 的基础上对 Application 的主线程有了更深的理解,使得向主线程发送消息来执行变得更容易。但依然缺少对 UI 对象的认知。
所以就有像 AsyncTaskLoader 这样框架级别的基础类(primitive),能提供比 RxJava/RxAndroid 更好的解决方案。这个类能像一个标准的 AsyncTask 一样工作, 但提供了一些其它的回调,来让你看到 activity 发生了什么变化,以便你可以做出反应。它是一个“Android 生命周期有意型基础线程类(Android-lifecycle aware threading primitive)”,意味着它能随时回调给你一大堆有用的值,或者就像 这哥们儿说的一样 :
在今天结束前我得说,“线程相关的讨论”依然是这个问题里最简单的部分;你真正要想的是 如何正确地跟内存打交道 。请留意我说的重点。
AsyncTask 仅仅是另一个实用的线程调度类,但和其它组件一样,它也有你需要注意的地方。了解这些“花边”的知识有助于你在写好一个 app 的代码时,做出更加明智的决定。
不管怎么样, 感谢关注《 Android 性能优化典范 》,如果有问题,尽管提别犹豫!