Android 多线程与线程池:从 Handler 到 Executor 的实战演进

Android 多线程与线程池:从 Handler 到 Executor 的实战演进


Android 开发中,多线程是绕不开的话题。从最初的 Thread + Handler 到后来的 AsyncTask,再到如今的 Executor 和 Coroutines,线程管理方式一直在演进。本文从实际场景出发,梳理多线程在 Android 中的正确用法。

## 为什么需要多线程

Android 主线程(UI 线程)有严格的限制:

- 不能执行耗时操作(网络请求、数据库查询、文件 I/O)
- ANR 阈值:Activity 5 秒,BroadcastReceiver 10 秒
- 只有主线程能更新 UI

违反这些规则会导致 ANR 或 `CalledFromWrongThreadException`。

## Handler + Looper:Android 的消息机制

Handler 是 Android 线程通信的基础。

### 基本用法


class MainActivity : AppCompatActivity() {
private val handler = Handler(Looper.getMainLooper())

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// 子线程执行耗时操作
Thread {
val result = fetchDataFromNetwork()
// 切回主线程更新 UI
handler.post {
textView.text = result
}
}.start()
}
}


### Handler 的常见陷阱

**内存泄漏**:非静态内部类 Handler 持有 Activity 引用。


// 错误写法
class MainActivity : AppCompatActivity() {
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
// 持有 Activity 引用,Activity 销毁后消息未清理会泄漏
}
}
}

// 正确写法:静态 Handler + 弱引用
class MainActivity : AppCompatActivity() {
private val handler = MyHandler(this)

private class MyHandler(activity: MainActivity) : Handler(Looper.getMainLooper()) {
private val weakRef = WeakReference(activity)

override fun handleMessage(msg: Message) {
weakRef.get()?.apply {
// 更新 UI
}
}
}

override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null) // 清理所有消息
}
}


### HandlerThread:带 Looper 的子线程

普通 Thread 没有 Looper,无法接收消息。HandlerThread 内置了 Looper。


class DataProcessor {
private val handlerThread = HandlerThread("data-processor")
private lateinit var handler: Handler

fun start() {
handlerThread.start()
handler = Handler(handlerThread.looper)
}

fun processData(data: String) {
handler.post {
// 在 data-processor 线程执行
val result = heavyComputation(data)
Log.d("Thread", Thread.currentThread().name) // data-processor
}
}

fun stop() {
handlerThread.quitSafely()
}
}


## 线程池:复用线程,避免频繁创建

频繁创建 Thread 开销大。线程池通过复用线程提升性能。

### Executors 工具类


// 固定大小线程池
val fixedPool = Executors.newFixedThreadPool(4)

// 缓存线程池(自动扩容,适合大量短任务)
val cachedPool = Executors.newCachedThreadPool()

// 单线程池(保证任务顺序执行)
val singlePool = Executors.newSingleThreadExecutor()

// 调度线程池(延时和周期任务)
val scheduledPool = Executors.newScheduledThreadPool(2)


### 实战:并发下载多张图片


class ImageDownloader {
private val executor = Executors.newFixedThreadPool(3)

fun downloadImages(urls: List, callback: (List) -> Unit) {
val results = mutableListOf()
val latch = CountDownLatch(urls.size)

urls.forEachIndexed { index, url ->
executor.execute {
try {
val bitmap = downloadBitmap(url)
synchronized(results) {
results.add(bitmap)
}
} catch (e: Exception) {
results.add(null)
} finally {
latch.countDown()
}
}
}

// 等待所有任务完成
Thread {
latch.await()
Handler(Looper.getMainLooper()).post {
callback(results.filterNotNull())
}
}.start()
}

fun shutdown() {
executor.shutdown()
}
}


### ThreadPoolExecutor:自定义线程池

Executors 内部都是调用 ThreadPoolExecutor,直接使用它能精确控制参数。


val threadPool = ThreadPoolExecutor(
2, // corePoolSize:核心线程数
4, // maximumPoolSize:最大线程数
60L, // keepAliveTime:空闲线程存活时间
TimeUnit.SECONDS,
LinkedBlockingQueue(100), // workQueue:任务队列
ThreadFactory { r ->
Thread(r, "custom-worker").apply {
isDaemon = false
}
},
ThreadPoolExecutor.CallerRunsPolicy() // rejectedExecutionHandler:拒绝策略
)


**核心参数说明**:

- `corePoolSize`:常驻线程数,即使空闲也不回收
- `maximumPoolSize`:最大线程数,队列满后才会创建新线程
- `keepAliveTime`:超出核心数的线程空闲后回收时间
- `workQueue`:任务队列,核心线程忙时任务先入队
- `rejectedExecutionHandler`:队列满且线程数达到上限时的策略
- `AbortPolicy`:抛异常(默认)
- `CallerRunsPolicy`:调用者线程执行
- `DiscardPolicy`:静默丢弃
- `DiscardOldestPolicy`:丢弃队列最老的任务

### 执行流程

1. 提交任务
2. 核心线程未满 → 创建核心线程执行
3. 核心线程已满 → 任务入队
4. 队列满 → 创建非核心线程(直到 maximumPoolSize)
5. 线程数达到上限 → 触发拒绝策略

## AsyncTask 已废弃

Android 11(API 30)正式废弃 AsyncTask,原因:

- 内存泄漏隐患(持有 Activity 引用)
- 生命周期管理混乱(Activity 销毁后任务继续)
- 串行执行默认值(`SERIAL_EXECUTOR`)容易被忽略

**替代方案**:Kotlin Coroutines + ViewModel + LiveData。

## 实战场景:分页加载 + 缓存


class DataRepository {
private val executor = Executors.newFixedThreadPool(2)
private val cache = ConcurrentHashMap>()

fun loadPage(page: Int, callback: (List) -> Unit) {
executor.execute {
// 先查缓存
cache[page]?.let {
Handler(Looper.getMainLooper()).post { callback(it) }
return@execute
}

// 缓存未命中,请求网络
val data = fetchFromNetwork(page)
cache[page] = data

Handler(Looper.getMainLooper()).post {
callback(data)
}
}
}
}


## 线程安全:避免并发问题

### 问题示例


class Counter {
private var count = 0

fun increment() {
count++ // 非原子操作,并发时会丢失更新
}
}


### 解决方案

**1. synchronized**


class Counter {
private var count = 0

@Synchronized
fun increment() {
count++
}
}


**2. AtomicInteger**


class Counter {
private val count = AtomicInteger(0)

fun increment() {
count.incrementAndGet()
}
}


**3. ConcurrentHashMap**(替代 HashMap)


private val cache = ConcurrentHashMap()


## 最佳实践

1. **避免裸 Thread**:用线程池管理
2. **合理设置线程数**:
- CPU 密集型:`核心数 + 1`
- I/O 密集型:`核心数 * 2`
3. **及时释放**:Activity 销毁时清理 Handler 和线程池
4. **命名线程**:方便日志追踪和 ANR 分析
5. **优先用 Coroutines**:现代 Android 开发的首选

## 小结

- Handler:线程通信基础,注意内存泄漏
- HandlerThread:需要 Looper 的子线程场景
- 线程池:复用线程,避免频繁创建
- ThreadPoolExecutor:精确控制线程池行为
- 线程安全:并发访问共享数据时用锁或原子类

多线程是 Android 性能优化的关键一环。掌握线程池和 Handler 的正确用法,能有效避免 ANR 和内存泄漏,为后续学习 Coroutines 打下基础。
posted @ 2026-09-02 11:08  天总会晴的  阅读(11)  评论(0)    收藏  举报