13、线程优化:线程池参数配置、AsyncTask替代方案、协程在Android中的应用、HandlerThread使用场景

线程优化这个话题,说大不大,说小不小。我见过太多应用卡顿、ANR,追根溯源都是线程用错了。你想想看,主线程被一堆杂事拖住,界面怎么可能流畅?今天咱们就把线程优化的几个核心点掰开揉碎聊清楚。

线程池参数配置:别让线程成为野马

线程池这东西,说白了就是个线程管家。你不需要自己new Thread(),把任务丢给线程池就行。但参数怎么配,这里头有讲究。

核心参数就七个:

  • corePoolSize:核心线程数。我习惯设为CPU核心数+1,IO密集型任务可以再大些。
  • maximumPoolSize:最大线程数。别设太大,否则线程切换开销会吃掉性能。
  • keepAliveTime:非核心线程空闲存活时间。默认60秒,短任务可以设小点。
  • TimeUnit:时间单位。
  • workQueue:任务队列。LinkedBlockingQueue无界队列要慎用,容易OOM。
  • ThreadFactory:线程工厂。给线程起个有意义的名字,方便排查问题。
  • RejectedExecutionHandler:拒绝策略。默认抛异常,我一般用CallerRunsPolicy。
// 我个人习惯这样配
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    4,                      // corePoolSize
    8,                      // maximumPoolSize
    60L,                    // keepAliveTime
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<Runnable>(128),  // 有界队列
    new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "BizPool-" + mCount.getAndIncrement());
        }
    },
    new ThreadPoolExecutor.CallerRunsPolicy()
);

避坑指南:我曾经遇到过线程池参数配错,导致大量任务堆积在队列里,内存直接飙到200MB。后来改成有界队列+CallerRunsPolicy,问题就解决了。记住,无界队列是魔鬼。

AsyncTask替代方案:它真的该退休了

AsyncTask在Android 11已经标记为废弃。为什么?因为它太容易出问题了。Activity销毁后AsyncTask还在跑,结果回调里更新UI直接崩溃。我建议用以下方案替代:

场景 替代方案 推荐指数
简单后台任务+UI更新 ViewModel + LiveData + Coroutine ⭐⭐⭐⭐⭐
需要取消的任务 CoroutineScope + Job ⭐⭐⭐⭐⭐
串行任务 HandlerThread 或 单线程协程 ⭐⭐⭐⭐
大量并行任务 线程池 + Callable/Future ⭐⭐⭐⭐

说白了,AsyncTask的替代方案就是协程。它更轻量,生命周期可控,而且写起来更舒服。

协程在Android中的应用:这才是现代写法

协程不是线程,它是轻量级的并发框架。我刚开始用的时候也觉得玄乎,用多了才发现真香。

// 在ViewModel中使用协程
class MyViewModel : ViewModel() {
    private val _data = MutableLiveData<String>()
    val data: LiveData<String> = _data

    fun loadData() {
        viewModelScope.launch {
            // 切换到IO线程
            val result = withContext(Dispatchers.IO) {
                fetchFromNetwork()
            }
            // 自动切回主线程更新UI
            _data.value = result
        }
    }
}

协程的三个关键点:

  • Dispatchers.Main:主线程,用于UI操作
  • Dispatchers.IO:IO密集型任务,网络请求、文件读写
  • Dispatchers.Default:CPU密集型任务,计算、排序

为什么协程比线程池好?因为它能自动处理生命周期。Activity销毁了,viewModelScope会自动取消所有协程,不会出现内存泄漏。你想想看,这省了多少事。

注意:协程不是银弹。如果任务特别多,还是要配合线程池使用。我见过有人一个协程里开1000个并发,结果把线程池撑爆了。嗯,这里要注意控制并发数。

HandlerThread使用场景:串行任务的利器

HandlerThread是什么?说白了就是一个自带Looper的线程。它内部维护了一个消息队列,所有任务按顺序执行。适合什么场景?

  • 数据库写入:避免并发写入导致数据错乱
  • 文件操作:串行读写,防止文件被同时打开多次
  • 蓝牙通信:指令需要按顺序发送
  • 传感器数据采集:保证数据时序正确
// HandlerThread使用示例
HandlerThread handlerThread = new HandlerThread("DbWriter");
handlerThread.start();

Handler handler = new Handler(handlerThread.getLooper()) {
    @Override
    public void handleMessage(@NonNull Message msg) {
        // 这里在子线程执行,按消息顺序处理
        switch (msg.what) {
            case MSG_INSERT:
                insertToDatabase((Data) msg.obj);
                break;
            case MSG_DELETE:
                deleteFromDatabase((String) msg.obj);
                break;
        }
    }
};

// 发送任务
handler.sendMessage(handler.obtainMessage(MSG_INSERT, data));
handler.sendMessage(handler.obtainMessage(MSG_DELETE, id));

避坑指南:我曾经用HandlerThread做数据库批量写入,结果忘了调用handlerThread.quitSafely(),导致线程一直不释放。后来在Activity的onDestroy里加上退出逻辑,内存就正常了。

知识体系总览

下面这张图把线程优化的核心脉络梳理清楚了,你可以对照着看:

Android线程优化 线程池参数配置 AsyncTask替代方案 协程应用 HandlerThread corePoolSize / maximumPoolSize workQueue / 拒绝策略 ThreadFactory命名 ViewModel + LiveData CoroutineScope 线程池 + Callable Dispatchers调度器 viewModelScope withContext切换线程 串行消息队列 数据库写入 quitSafely释放 核心原则:主线程只做UI,其他全扔子线程 生命周期感知 · 避免泄漏 · 控制并发

线程优化说到底就一句话:主线程只做UI,其他全扔子线程。但怎么扔、扔给谁、扔完怎么收回来,这里头的门道我今天都讲了。你回去看看自己的项目,线程池参数是不是随便写的?AsyncTask是不是还在用?HandlerThread有没有正确释放?改一改,性能提升立竿见影。


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