19、Android多线程机制:AsyncTask、HandlerThread、IntentService、线程池与协程

多线程,说白了就是让App同时干好几件事。你想想看,如果所有任务都挤在主线程里,用户点个按钮就得等网络请求回来才能动,那体验得多糟糕。我早期做Android开发时,就吃过这个亏——一个图片加载没处理好,直接ANR,用户差评如潮。

Android的多线程方案,从最早的AsyncTask,到HandlerThread、IntentService,再到线程池和现在的协程,每个方案都有它的适用场景。今天我就带你把这些东西捋清楚。

19.1 AsyncTask:曾经的宠儿,如今的弃子

AsyncTask是Android早期提供的轻量级异步任务工具。它封装了线程和Handler,让你在后台做耗时操作,然后轻松更新UI。

class MyTask extends AsyncTask<Void, Integer, String> {
    @Override
    protected void onPreExecute() {
        // 主线程,准备工作
    }

    @Override
    protected String doInBackground(Void... params) {
        // 后台线程,做耗时操作
        publishProgress(50); // 更新进度
        return "完成";
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        // 主线程,更新进度条
    }

    @Override
    protected void onPostExecute(String result) {
        // 主线程,更新UI
    }
}

嗯,这里要注意。AsyncTask在Android 11已经被标记为废弃了。为什么?

  • 生命周期问题:Activity销毁后,AsyncTask还在跑,容易内存泄漏。我在项目中遇到过,用户旋转屏幕,Activity重建,结果AsyncTask还在更新旧的UI,直接崩溃。
  • 串行执行坑:Android 1.6到3.0是并行,3.0之后默认串行。如果你同时启动多个AsyncTask,它们会排队执行,不是你想象的那样一起跑。
  • 结果丢失:屏幕旋转后,AsyncTask的结果无法传递给新的Activity。
避坑指南:我曾经在一个图片加载项目里用了AsyncTask,结果列表快速滑动时,几十个AsyncTask同时创建,线程数暴涨,内存直接OOM。后来我换成了线程池,问题才解决。

19.2 HandlerThread:带消息循环的工作线程

HandlerThread本质上就是一个自带Looper的Thread。它适合那种需要反复执行任务、且任务之间有顺序依赖的场景。

HandlerThread handlerThread = new HandlerThread("WorkThread");
handlerThread.start();

Handler handler = new Handler(handlerThread.getLooper()) {
    @Override
    public void handleMessage(@NonNull Message msg) {
        // 这里已经在子线程了
        // 处理耗时任务
    }
};

// 发送任务
handler.sendEmptyMessage(1);

我个人习惯用HandlerThread来处理一些后台定时任务,比如本地数据库的批量写入。你想想看,如果每次写数据库都开一个新线程,那线程开销得多大。用HandlerThread,所有写操作都在同一个线程里排队执行,既省资源又保证顺序。

使用技巧:记得在不需要时调用handlerThread.quitSafely(),否则线程会一直运行,造成内存泄漏。

19.3 IntentService:自动停止的后台服务

IntentService是Service的子类,它内部使用HandlerThread来处理任务。任务执行完后,Service会自动停止,不需要你手动调用stopSelf()。

public class MyIntentService extends IntentService {
    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        // 这里在子线程执行
        // 处理耗时任务
    }
}

IntentService最大的好处就是省心。你只管发Intent,它帮你排队执行,执行完自动销毁。我在做文件下载功能时用过它,用户点击下载,发个Intent,后台默默下载,下载完Service自己停掉,非常干净。

不过要注意,IntentService在Android 8.0之后也被标记为废弃了。官方推荐用JobIntentService或者直接使用WorkManager。

19.4 线程池:掌控全局的调度器

线程池是Java提供的线程管理工具。它解决了频繁创建和销毁线程的开销问题。Android中常用的线程池有四种:

类型 特点 适用场景
FixedThreadPool 固定线程数,超出的任务排队 控制并发数,比如数据库连接池
CachedThreadPool 按需创建,空闲线程60秒回收 大量短时任务,比如网络请求
ScheduledThreadPool 支持定时和周期性任务 定时检查、心跳包
SingleThreadPool 只有一个线程,任务串行执行 需要顺序执行的场景
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.execute(new Runnable() {
    @Override
    public void run() {
        // 执行耗时任务
    }
});

我建议你在项目里尽量使用线程池,而不是直接new Thread()。为什么呢?因为线程池可以复用线程,减少开销。我曾经优化过一个项目,把所有的new Thread()替换成线程池,内存占用直接降了30%。

核心要点:线程池的核心参数——corePoolSize、maximumPoolSize、keepAliveTime、workQueue。理解这四个参数,你就掌握了线程池的精髓。

19.5 协程:Kotlin带来的革命

协程是Kotlin提供的轻量级并发方案。它不是线程,而是一种可以在单线程内实现并发的能力。说白了,协程就是让代码看起来像同步的,但实际上是异步执行的。

// 在ViewModel中启动协程
viewModelScope.launch {
    val result = withContext(Dispatchers.IO) {
        // 在IO线程执行网络请求
        apiService.fetchData()
    }
    // 自动回到主线程更新UI
    textView.text = result
}

协程最大的优势是什么?结构化并发。你不需要手动管理线程的生命周期,协程会自动处理。比如viewModelScope,当ViewModel被清除时,所有协程自动取消,不会内存泄漏。

我个人现在做新项目,基本都用协程了。它比线程池更轻量,一个线程可以跑成千上万个协程。而且代码可读性极强,没有回调地狱。

避坑指南:我曾经在协程里直接做了网络请求,忘了切换到IO线程,结果卡住了主线程。记住,网络请求和数据库操作一定要用Dispatchers.IO,UI更新用Dispatchers.Main。

19.6 知识体系总览

下面这张图,是我梳理的Android多线程知识体系。你可以把它当作一个导航图,遇到多线程问题,先看这张图,找到对应的方案。

Android多线程机制知识体系 Android多线程 AsyncTask 已废弃 HandlerThread 带Looper IntentService 自动停止 线程池 四种类型 Kotlin协程 Dispatchers.IO Dispatchers.Main 选择建议:新项目优先使用协程,旧项目维护可继续使用线程池

从这张图可以看出,Android多线程方案经历了从重量级到轻量级的演变。AsyncTask和IntentService已经逐渐被淘汰,线程池依然稳定可靠,而协程是未来的趋势。

最后说一句,没有最好的方案,只有最合适的方案。如果你的项目还在用Java,线程池是你的不二选择。如果你已经切换到Kotlin,那就拥抱协程吧。


公众号:蓝海资料掘金营,微信deep3321