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里加上退出逻辑,内存就正常了。
知识体系总览
下面这张图把线程优化的核心脉络梳理清楚了,你可以对照着看:
线程优化说到底就一句话:主线程只做UI,其他全扔子线程。但怎么扔、扔给谁、扔完怎么收回来,这里头的门道我今天都讲了。你回去看看自己的项目,线程池参数是不是随便写的?AsyncTask是不是还在用?HandlerThread有没有正确释放?改一改,性能提升立竿见影。
公众号:蓝海资料掘金营,微信deep3321