android多线程与线程间通信
一、核心概念:为什么需要多线程?
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()) {@Overridepublic 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:发送MessageMessage message = mainHandler.obtainMessage();message.what = 1; // 自定义的消息标识message.obj = result; // 要传递的数据mainHandler.sendMessage(message);// 方式2:更简单的,发送一个RunnablemainHandler.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()) {@Overridepublic 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从后台线程更新LiveDatamData.postValue(newData); // 自动切到主线程通知Observer}).start();}
}// 在Activity/Fragment中观察
myViewModel.getData().observe(this, newData -> {// 这个Observer回调在主线程执行,可以安全更新UItextView.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. 在主线程更新UItextView.text = resultloadingIndicator.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 -> {// 在主线程接收结果,更新UItextView.setText(result);
});
三、基础java方案
7.Thread 和 Runnable
最基础的创建线程的方式。
// 方式一:实现 Runnable 接口
Thread thread = new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic void run() {doHeavyWork();}
});// 3. 如果需要获取后台任务的结果,使用 Callable 和 Future
Future<String> future = executor.submit(new Callable<String>() {@Overridepublic 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 进行复杂通信,因为它们难以维护且容易出错。