异步向来都是提高性能的必要手段,当然也是引发问题的常见根源。Android之中更是如此,主线程事关应用的流畅程度,所以把更多能放在工作线程中事,放到工作线程中去,是提升App性能之必经之路。Android中使用线程有多种方式,我们该如何选择,以及在使用过程中要注意哪些问题,今天就来探讨一下。
Bitmap处理
在Android中显示图片,Bitmap是必须要学会的。Bitmap是Android中抽象出来代表图片的类,把图片文件或者数据转化成Bitmap进而再放置到视图系统中显示。Bitmap的处理也常常会引发OOM问题。
关于Bitmap的处理,遵守二个原则,就是按需解析和及时释放。现在的图片文件都很大,但很多时候我们需要展示的区域(目标View的大小)却不是很大,这个时候就需要在解析的时候,适当的降低帧率,以减小所需要申请的内存。及时释放时也需要注意,要确定不用了,再释放,如果有View仍在显示时,在另外的地方把Bitmap释放了,那会引发异常的。
对于Bitmap的使用,官方文档有着比较详细的教程。现在的Android文档是很齐全的,相比较2010年代时2.0和2.1时多了很多最佳实践,所以不要浪费。
除内存外,Bitmap的处理也是比较耗时的,因为它经常涉及IO,以及压缩和处理,即使不耗时,因为它跟UI没关系,所以也是可以放在工作线程中去做的。Android中记住一个准则就是主线程做的事情越少越好,越少你的应用就越流畅。至于如何具体的去做也建议参考官方教程。
异步的方式
在Android中异步实现的方式非常多:
- Thread
- AsyncTask
- Executors
- Handler, Looper
各种方式的使用方法参阅相关文档或者查阅网上浆料就可以了。这里主要说一下区别和适用的场景:
Thread
这是Java的简单粗暴的方式,优点就是简单,方便。对于某些简单的,一次性的,不需要与主线程通信和操作UI,不是很频繁(一次启动执行一次),的费时操作,直接使用Thread方式还是相当方便的。
AsyncTask
这是Android中专门针对平台本身的特性而封装的一个类,它的优势在于与主线程通信方便,能非常方便的操作UI。所以使用AsyncTask的理由就是当你需要在异步操作完成时直接操作UI时,除此外,你不应该使用它。
使用AsyncTask时,最好使用AsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);以免任务不能及时获得执行,详细原因可以看这篇文章。
Executors
当有大量的任务,或者重复性的任务需要执行时,就要使用线程池来复用和管理线程。
Handler与Looper
这是Android事件循环的基础类,也是AsyncTask的实现方式,是线程间通信的利器。所以如果你有复杂的线程通信(通常是工作线程和主线程之间),可以使用这二个类来自行进行定义。
其他的高级工具
对于多线程,已经有足够的工具供我们使用比如java.util.concurrent.*,以及Universal Image Loader,或者EventBus,所以当设计时首先要看现在的工具能否满足我们的需求。而不是自己去实现,子曰:不能重复造轮子,就是就是这个道理。
异步时要注意Activity的生命周期
异步时最容易出错的就是忽略Activity的生命周期。比如,当异步执行完成了,Activity却退出了前台,或者已经结束,如果异步完成时要操作UI,那么这种情况下肯定会报错,具体的错误取决于场景。这个问题的解法就是在异步操作完成后要用Activity.isFinishing()来判断下Activity是否还是alive的。或者设置一个变量来查看Activity是否还在前台。
另外,即使异步操作中不涉及UI,那么当Activity转入后台,或者退出时,也要及时的终止工作线程,否则也会造成Activity的对象无法及时销毁而最终导致内存泄露。这个问题需要在设计异步task时把可取消考虑进去,当Activity退出前台时发送消息给线程,让其终止执行。对于常见的费时操作,比如IO,网络,复杂计算等在都要考虑取消,每一个小步骤执行前都要判断取消标志位,以及时终止操作。通常这需要在Activity中持有任务的引用,或者使用Executors来管理任务,或者有一个类似的对象来管理异步任务,当Activity退出时,来终止任务。或者使用EventBus这类工具来降低耦合。
有用的资源
关于Android中的异步已经有了一本专门的书了《Efficient Android Threading》,内容还是比较丰富的,可以看一看。