一、核心概念:为什么需要多线程?
1. 主线程(UI 线程):
- 负责处理用户交互(点击、触摸)、绘制UI、更新界面。
- 黄金法则:绝对不能在主线程执行耗时操作!
- 如果主线程被阻塞超过 5秒,系统会抛出 Application Not Responding (ANR) 错误,导致应用崩溃。
2. 工作线程(后台线程):
用于执行所有可能阻塞主线程的任务,例如:
- 网络请求(Retrofit, Volley)
- 数据库操作(Room)
- 读写文件
- 解析大型数据(JSON, XML)
- 复杂计算
二、Android线程间通信
Android 线程间通信是一个核心话题。由于 Android 的 UI 操作必须在主线程(UI线程)执行,而耗时工作必须在后台线程执行,因此线程间通信是必不可少的。
1.Handler & Looper & MessageQueue (最基础的底层机制)
这是 Android 线程间通信的基石,几乎所有其他高级方案都基于此。
- Looper (循环器): 一个不断从 MessageQueue 中取出 Message 或 Runnable 的循环。线程默认没有 Looper,需要调用 Looper.prepare() 和 Looper.loop() 来创建和启动。主线程(UI线程)自带一个 Looper。
- MessageQueue (消息队列): 一个单向链表结构的消息队列,用于存放 Handler 发送来的 Message。
- Handler (处理器): 用于发送和处理 Message 或 Runnable 对象。每个 Handler 都会绑定到一个特定的线程(即该线程的 Looper)上。
工作流程: - 工作线程使用 Handler 将 Message 或 Runnable 发送到主线程的 MessageQueue 中。
- 主线程的 Looper 不断循环,从 MessageQueue 中取出这些消息。
- 取出的消息最终会由主线程的 Handler 的 handleMessage() 方法处理,或者直接执行 Runnable 的 run() 方法。此时,代码就在主线程中执行了。
代码示例:
// 在主线程中创建Handler,并关联主线程的Looper
Handler mainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 在这里处理消息,此方法运行在主线程
if (msg.what == 1) { // 根据消息标识判断
String data = (String) msg.obj; // 获取消息内容
textView.setText(data); // 安全更新UI
}
}
};
// 在工作线程中发送消息
new Thread(() -> {
// ... 执行耗时操作,比如网络请求
String result = "Data from network";
// 方式1:发送Message
Message message = mainHandler.obtainMessage();
message.what = 1; // 自定义的消息标识
message.obj = result; // 要传递的数据
mainHandler.sendMessage(message);
// 方式2:更简单的,发送一个Runnable
mainHandler.post(() -> {
// 这个Runnable里的代码会在主线程执行
textView.setText(result);
});
}).start();
2.AsyncTask (已废弃,但需了解)
AsyncTask 内部封装了 Handler 和线程池,提供了 onPreExecute, doInBackground, onProgressUpdate, onPostExecute 等回调方法,使得“后台工作-更新进度-UI更新”的流程非常清晰。
由于其内存泄漏、生命周期管理等问题,官方已正式废弃它。但它的设计思想值得了解。
3.HandlerThread
一个自带 Looper 的 Thread 子类。它为你准备好了 Looper,你只需要为其创建 Handler 即可与之通信。
适用场景:需要一个长时间运行、且有自己消息循环的后台线程。
// 1. 创建并启动HandlerThread
HandlerThread handlerThread = new HandlerThread("MyBackgroundThread");
handlerThread.start();
// 2. 获取它的Looper,并创建与之绑定的Handler
Handler backgroundHandler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
// 这个handleMessage运行在handlerThread这个后台线程中!
// 可以在这里执行耗时任务
doHeavyWork();
}
};
// 3. 在主线程向这个后台线程发送任务
backgroundHandler.post(() -> {
// 这个Runnable会在HandlerThread中执行
});
// 4. 退出时记得释放资源
handlerThread.quitSafely();
4.LiveData (生命周期感知的数据持有者)
LiveData 是一种可观察的数据存储器类,具有生命周期感知能力。它通常和 ViewModel 一起使用。
工作原理:当 LiveData 所持有的数据发生变化时,它会通知处于活跃生命周期状态(如 STARTED, RESUMED)的观察者。
线程通信:LiveData 的 postValue() 方法可以在后台线程调用,它会自动将值派发到主线程,然后通知观察者。setValue() 必须在主线程调用。
// 在ViewModel中
public class MyViewModel extends ViewModel {
private MutableLiveData<String> mData = new MutableLiveData<>();
public LiveData<String> getData() {
return mData;
}
public void fetchData() {
new Thread(() -> {
// 在后台线程工作
String newData = expensiveOperation();
// 使用postValue从后台线程更新LiveData
mData.postValue(newData); // 自动切到主线程通知Observer
}).start();
}
}
// 在Activity/Fragment中观察
myViewModel.getData().observe(this, newData -> {
// 这个Observer回调在主线程执行,可以安全更新UI
textView.setText(newData);
});
5. Kotlin Coroutines (协程) + 调度器 (Dispatchers)
这是当前 最主流、最简洁 的异步处理和线程通信方案。它用看似同步的代码写出了异步操作。
核心是通过挂起函数(suspend) 和协程上下文(CoroutineContext) 中的调度器(Dispatchers) 来切换线程。
// 在ViewModel中
fun fetchData() {
viewModelScope.launch { // 在主线程启动协程
// 1. 在主线程更新UI:显示加载框
loadingIndicator.value = true
// 2. 切换到IO线程执行耗时操作(不会阻塞主线程)
val result = withContext(Dispatchers.IO) {
// 这个代码块在IO线程执行
networkRequest() // 模拟网络请求
}
// 3. withContext结束后,自动切回原来的线程(主线程)
// 4. 在主线程更新UI
textView.text = result
loadingIndicator.value = false
}
}
Dispatchers 的类型:
- Dispatchers.Main: 用于更新UI。
- Dispatchers.IO: 用于网络、磁盘操作。
- Dispatchers.Default: 用于CPU密集型计算。
6.RxJava (响应式扩展)
一个基于事件流的异步编程库,功能极其强大,但学习曲线较陡。它通过 observeOn 和 subscribeOn 操作符来精确控制线程切换。
Observable.fromCallable(() -> {
// 在IO线程执行耗时任务 (因为subscribeOn)
return doHeavyWorkInBackground();
})
.subscribeOn(Schedulers.io()) // 指定上游执行的线程
.observeOn(AndroidSchedulers.mainThread()) // 指定下游观察者的线程
.subscribe(result -> {
// 在主线程接收结果,更新UI
textView.setText(result);
});
三、基础java方案
7.Thread 和 Runnable
最基础的创建线程的方式。
// 方式一:实现 Runnable 接口
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 在后台线程执行耗时任务
doHeavyWork();
// 不能在这里直接更新UI!
// runOnUiThread(() -> textView.setText("Done")); // 需要特殊方法
}
});
thread.start();
// 方式二:使用Lambda表达式(Java 8+)
new Thread(() -> {
// 后台工作
}).start();
缺点:难以管理、复用;需要自己处理线程间通信。
8.ExecutorService(线程池)
管理和复用线程的最佳Java实践。避免了频繁创建和销毁线程的开销。
// 1. 创建固定大小的线程池(推荐)
ExecutorService executor = Executors.newFixedThreadPool(4);
// 2. 提交任务(Runnable 或 Callable)
executor.execute(new Runnable() {
@Override
public void run() {
doHeavyWork();
}
});
// 3. 如果需要获取后台任务的结果,使用 Callable 和 Future
Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
doHeavyWork();
return "Result";
}
});
// 在需要结果的时候获取(会阻塞当前线程)
try {
String result = future.get();
runOnUiThread(() -> updateUI(result));
} catch (Exception e) {
e.printStackTrace();
}
// 4. 关闭线程池(通常在应用退出时)
executor.shutdown();
四、总结与对比
| 机制 | 特点 | 适用场景 | 现代性 |
|---|---|---|---|
| Handler/Looper | 底层基础,灵活但稍显繁琐 | 需要精细控制消息、自定义后台循环 | 传统,但必不可少 |
| HandlerThread | 自带Looper的Thread | 需要一个常驻的、有消息循环的后台线程 | 传统 |
| LiveData | 生命周期感知,与ViewModel搭配 | 基于生命周期的UI数据更新 | 现代 (推荐) |
| Kotlin Coroutines | 代码简洁,结构化并发,易于管理 | 几乎所有异步场景的首选 | 最现代 (强烈推荐) |
| RxJava | 功能强大,流操作符丰富 | 复杂的异步事件流处理 | 现代,但复杂度高 |
最佳实践建议:
对于新项目:毫不犹豫地选择 Kotlin 协程 + LiveData/StateFlow。这是 Google 官方主推的现代方案。
理解底层:理解 Handler/Looper 机制有助于你更好地理解其他高级框架的原理。
避免使用:AsyncTask 和直接使用裸 Thread 进行复杂通信,因为它们难以维护且容易出错。
1987

被折叠的 条评论
为什么被折叠?



