Android 异步操作

Android为我们提供了几种异步线程操作:AsyncTask, HandlerThread, IntentService, ThreadPoolLoader。了解这几种操作的不同之处,有助于我们开发的时候使用正确的异步操作,避免不必要的坑。

AsyncTask

AsyncTask是我们平时使用频率最高的,利用它,我们可以很容易的将工作扔到异步线程中去,然后在工作完成之后再在UI线程中处理结果或者处理过程中与UI线程交互。但另一方面,它也很容易造成一些潜在的问题,比如说内存泄露。

应用场景:

与UI相关的工作,不太耗时的工作。例如,下载一张图片,并且在下载过程中需要不断更新进度条,然后下载完成后显示出来。

需要注意的坑:

  1. 默认情况下,所有创建的AsyncTask会共享一个线程,换句话说,AsyncTask是以串行的方式执行的。如果有某个任务耗时太长,将会影响后面的任务。解决方法是将任务放到线程池中去执行。AsyncTask.executeOnExecutor,或者按照官方(性能优化视频)的建议,直接用线程池ThreadPoolExecutor代替AsyncTask
  2. 异步任务的取消:虽然AsyncTask提供了cancel方法,但是你直接调用该方法并不能让任务停止下来,因为线程没有办法直接停止正在运行的代码。所以我们还需要在doInBackground方法中,在恰当的时机中去检查标志位。
doInBackground(...) {
    while (!isCanceled()) {
        // do your work.
    }
}

设置完标志位后,调用cancel方法才能使得线程中的任务提前终止。需要注意的一点是被取消掉的任务会回调onCancelled而不是onPostExecute
3.很容易出现内存泄露:

public class MainActivity extends AppCompatActivity {
    private class MyAsyncTask extends AsyncTask<Void, Integer, String> {

    @Override
    protected String doInBackground(Void... params) {
        // ...
        return null;
    }

    @Override
    protected void onPostExecute(String s) {
        // ...
    }
}

这是典型的内部类引用外部类问题。内部类MyAsyncTask隐形的持有了MainActivity的引用。假如Activity退出后,AsyncTask中的任务还没处理完,Activity还会被AsyncTask持有着,这将导致Activity无法被释放,直到AsyncTask中的任务处理完。并且,如果这个时候,你在onPostExecute中去操作UI,还有可能会报错。

HandlerThread

HandlerThread继承自Thread,内部封装了一个属于该线程的Looper,源码加上注释也就是150行左右。可以看出还是非常简单的一个类。有了Looper之后,我们就可以利用Handler发送消息到该线程中去执行了。

应用场景:

与UI无关的或者耗时的工作。例如,后台压缩一张大图片。与AsyncTask对比可以看出,HandlerThread是对后者的补充。

需要注意的坑:

  1. 需要为HandlerThread设置线程优先级,默认情况下,它是和UI线程拥有相同优先级的,根据需要降低优先级可以保证UI线程不被阻塞。
  2. HandlerThread使用完成之后记得调用quitSafely或者quit方法完成退出,否则由于Looper是一个无限循环,会导致线程无法正常退出。

ThreadPool

线程池,顾名思义,一个池子里面躺着很多的线程。在开始的时候就将这个池里的线程初始化完,待到需要使用的时候,就从池子里取出线程。在Android中我们可以使用ThreadPoolExecutor来实现线程池。

应用场景:

适合并发工作的。假设你有40张图片需要处理,每张图片需要4ms,放在HandlerThread中的话需要160ms,而如果将这些图片处理放到5个线程中去同时处理的话,则只需要32ms,整整提高了5倍速度。

需要注意的坑:

理论上来说,你可以为一个线程池创建很多很多的线程,但是CPU只能同时执行一定数量的线程,当线程超过这个数量时,就需要进行线程调度了,即根据每个线程的优先级来分配时间片,所以线程并不是越多越好的。而且,创建线程也是有代价的,每一个线程至少要占用64k内存,还很容易就上升。所以我们需要为线程池找到一个合理的线程数。我们可以根据每个机器当前可用的CPU个数来确定线程数,如:

// 需要注意的是,该方法并不是返回你手机物理上的CPU个数,因为有些CPU可能处于未激活状态,这取决于系统的负载。
private static int NUMBER_OF_THREAD = Runtime.getRuntime().availableProcessors();

IntentService

IntentServiceServiceHandlerThread的结合体。虽然Service是运行在后台的,但是实际上它还是运行在UI线程中的,所以它并不适合用来执行耗时的操作。好在Android为我们提供了IntentService,它继承自Service,但在内部封装了HandlerThread。它在收到Intent时,会把Intent发送到HandlerThread中,让它来完成相应的耗时任务。同时,IntentService又拥有Service的优点(其中之一是当系统资源紧张的时候,它被回收掉的可能性在处于前台的app与处于后台的app之间)。

应用场景:

假设你在UI线程收到了一个Intent,但处理这个Intent的工作又会比较耗时,这时候就可以利用IntentService来处理。

需要注意的坑:

  1. HandlerThread内部是通过消息队列来处理任务的,这意味着如果有某个任务耗时太长的话,会影响其它任务的执行。因为Service是可以接收很多的Intent的,即消息队列很可能会有很多的消息等待着处理。这与我们直接使用HandlerThread还是有点不同的(因为直接使用可能消息队列中就只有一个任务需要处理)。
  2. 用到IntentService的时候,我们经常会使用BroadcastReceiver来将结果返回到UI线程中,建议使用LocalBroadcastReceiver来代替,它比较轻量级。

Loader

Loader是Android 3.0之后才出现的,也是官方推荐的异步操作:它的优点有

  1. 不用担心内存泄露,这是因为Loader会帮我们同步Activity或者Fragment的生命周期。这是其它异步线程操作所不具备的。
  2. 缓存结果,所以当结果已经获得之后再次查询可以立即返回结果。

应用场景

Activity和Fragment中需要异步操作的地方。

总结

这篇文章并没有对每一种异步操作进行详细的分析,只是为了对比几种操作的优缺点、应用场景以及需要注意的地方。了解这些异步操作的不同之处,有助于帮助我们在开发过程中正确的选择相应的异步操作,减少不必要的麻烦。

参考资料

来源于Android官方的性能优化视频(需要梯子):

  1. Good AsyncTask Hunting.
  2. Getting a HandlerThread
  3. Swimming in ThreadPools
  4. The Zen of IntentService
  5. Threading and Loaders
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容