1. Android多线程编程基础
1.1 Android线程模型解析
Android系统基于Linux内核实现了一套独特的线程模型。每个Android应用启动时,系统会为其创建一个主线程(也称为UI线程),这个线程负责处理所有用户界面交互事件。理解这个模型是进行多线程编程的基础。
UI线程的工作机制类似于餐厅里的服务员 - 它需要快速响应顾客(用户)的点单(操作),如果被某个耗时任务(比如复杂的菜品制作)阻塞,整个餐厅(应用)就会显得卡顿。这就是为什么Google强制要求网络请求、数据库操作等耗时任务必须在后台线程执行。
重要提示:在UI线程执行耗时操作超过5秒会触发ANR(Application Not Responding)错误,导致应用被系统强制关闭。
1.2 多线程实现基础方案
Android提供了多种实现多线程的方式,每种都有其适用场景:
- Thread类:最基本的线程创建方式
java复制new Thread(() -> {
// 后台任务代码
runOnUiThread(() -> {
// 更新UI的代码
});
}).start();
- HandlerThread:自带消息循环的线程
java复制HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
- AsyncTask(已废弃):曾经是Android特有的异步任务处理方式
虽然这些基础方案仍然可用,但在现代Android开发中,我们更推荐使用更高级的并发工具。
2. 线程池深度解析与应用
2.1 线程池核心参数详解
要真正掌握线程池,必须理解ThreadPoolExecutor的七个核心参数:
- corePoolSize(核心线程数):线程池中长期保持的线程数量
- maximumPoolSize(最大线程数):线程池允许创建的最大线程数量
- keepAliveTime(线程空闲时间):非核心线程空闲时的存活时间
- unit(时间单位):keepAliveTime的时间单位
- workQueue(工作队列):用于保存等待执行的任务的阻塞队列
- threadFactory(线程工厂):用于创建新线程的工厂
- handler(拒绝策略):当线程池无法处理新任务时的处理策略
2.2 四种常用线程池对比
Android通过Executors类提供了四种预定义的线程池实现:
| 线程池类型 | 特点 | 适用场景 |
|---|---|---|
| FixedThreadPool | 固定大小的线程池,超出的任务会在队列中等待 | 需要限制并发数的场景 |
| CachedThreadPool | 可缓存的线程池,线程数随负载增减 | 执行大量短期异步任务 |
| SingleThreadExecutor | 单线程的线程池,保证所有任务按顺序执行 | 需要顺序执行任务的场景 |
| ScheduledThreadPool | 支持定时及周期性任务执行的线程池 | 需要定时或延迟执行任务的场景 |
2.3 自定义线程池最佳实践
对于大多数生产环境,建议创建自定义线程池而非使用预定义实现。以下是一个推荐配置:
java复制int cpuCount = Runtime.getRuntime().availableProcessors();
int corePoolSize = cpuCount + 1;
int maxPoolSize = cpuCount * 2 + 1;
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(128),
new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "MyThread #" + mCount.getAndIncrement());
}
},
new ThreadPoolExecutor.DiscardOldestPolicy()
);
// 设置核心线程在空闲时也会被回收
executor.allowCoreThreadTimeOut(true);
这个配置考虑了设备CPU核心数,设置了合理的队列大小,并添加了有意义的线程命名以便调试。
3. Android多线程高级技巧
3.1 线程间通信方案
Android提供了多种线程间通信机制:
- Handler/Looper机制:
java复制// 主线程中创建Handler
Handler mainHandler = new Handler(Looper.getMainLooper());
// 工作线程中发送消息
mainHandler.post(() -> {
// 这段代码会在主线程执行
});
- LiveData:生命周期感知的数据持有者
- RxJava:响应式编程框架
- Kotlin协程:轻量级线程方案
3.2 线程安全实践指南
多线程环境下必须注意线程安全问题:
- 同步代码块:
java复制private final Object lock = new Object();
public void safeMethod() {
synchronized(lock) {
// 临界区代码
}
}
- 使用并发集合:
java复制ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
- Atomic变量:
java复制private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet();
}
3.3 性能优化与调试技巧
- 线程泄漏检测:
java复制// 在Application中初始化
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectCustomSlowCalls()
.penaltyLog()
.build());
- 线程优先级管理:
java复制Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- 使用TraceView分析线程性能
4. 现代Android并发方案
4.1 Kotlin协程实践
协程已成为现代Android开发的首选并发方案:
kotlin复制// 在ViewModel中启动协程
viewModelScope.launch {
// 在IO线程执行网络请求
val result = withContext(Dispatchers.IO) {
repository.fetchData()
}
// 自动切换回主线程更新UI
_uiState.value = result
}
协程的主要优势:
- 更轻量级的线程切换
- 结构化并发避免内存泄漏
- 更简洁的异步代码编写方式
4.2 WorkManager的使用
对于需要持久化、保证执行的背景任务,推荐使用WorkManager:
java复制Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
OneTimeWorkRequest uploadWork = new OneTimeWorkRequest.Builder(UploadWorker.class)
.setConstraints(constraints)
.build();
WorkManager.getInstance(context).enqueue(uploadWork);
WorkManager会根据设备API级别自动选择最佳的实现方式(JobScheduler、AlarmManager等)。
4.3 常见问题排查
- ANR问题:
- 检查是否在主线程执行了耗时操作
- 使用StrictMode检测潜在问题
- 内存泄漏:
- 避免在静态集合中持有Activity引用
- 使用WeakReference处理回调
- 线程阻塞:
- 检查锁的获取顺序避免死锁
- 使用tryLock设置超时时间
- 并发修改异常:
- 使用并发集合替代传统集合
- 在遍历集合时不要修改集合内容
在实际项目中,我曾遇到一个典型的线程池配置问题:由于设置了无界队列,导致OOM崩溃。解决方案是改用有界队列并合理设置拒绝策略。这个经验告诉我,线程池参数需要根据具体场景精心调整,不能简单套用默认配置。