Android多线程:HandlerThread的原理及使用
前言
在Andorid实现多线程的方式中, HandlerThread
的使用并不常见,最近开始扎实Android基础,我们都知道,若是在子线程中创建Handler实例并调用 sendMessage()
方法时,子线程由于并不会创建 Lopper
和 MessageQueue
对象,等同于消息没有入队(MessageQuue),消息也无法实现出队循环(Looper),故在子线程发送的消息任务无法执行,这时候需要调用方法 Looper.prepare()和Looper.loop()
实现消息的入队、出队、循环分发给指定的Handler。
为了解决这个问题,Android封装了自己的HandlerThread,在内部调用方法 Looper.prepare()和Looper.loop()
,方便了开发人员的使用。
下面我们来揭开HandlerThread的神秘面纱:
1.是什么
HandlerThread是Android封装好的异步消息处理类,其原理即是继承自Thread并封装了Handler
2.为什么
- 保证多线程并发需要更新UI线程时的线程安全
- 不需要使用任务线程(继承自Thread)+ Handler的复杂组合,方便了开发人员使用创建新线程与其他线程通信的过程。
3.怎么做
原理:继承的Thread类 + 封装的Handler类
- 继承的Thread类:快速创建一个带有Looper的工作线程
- 封装的Handler类:快速创建Handler与其他线程通信
4.使用步骤
1)创建ThreadHandler实例对象mThreadHandler
2)开启线程mThreadHandler.start()
3)创建工作线程的Handler实例,workHandler,并实现handleMessage方法
4)使用工作线程workHandler向工作线程的消息队列发送消息
workHandler.sendMessage(msg);
5)停止线程
mThreadHandler.quit()
mThreadHandler.quitSafely()
- demo 示例完整代码
模拟窗口卖票,每个窗口有6张票,工作线程workHandler发送一条消息则开启一个窗口开始买票
public class HandlerThreadActivity extends AppCompatActivity {
private int j = 1;
private String note;
private TextView note_text;
private Handler workHandler = new Handler();
private HandlerThread handlerThread;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_thread);
initView();
initHandlerThread();
}
private void initHandlerThread() {
//step1: 创建HandlerThread实例,传入的参数为线程名称
handlerThread = new HandlerThread("based on yourself");
//step2: 手动调用start()开启线程
handlerThread.start();
//step3:
//创建Handler,关联HandlerThread的Looper
//复写handleMessage根据消息更新UI布局,处理消息的线程即是创建的线程handlerThread
workHandler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(final Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
break;
case 2:
for (int i = 1; i < 7; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
note = msg.obj + "卖出" + i + "张票";
note_text.setText(note);
Log.d("workHandler----", note);
}
break;
}
}
};
}
private void initView() {
note_text = findViewById(R.id.note_text);
Button startThread = findViewById(R.id.start_thread);
startThread.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//step4: 点击一次Button,工作线程workHandler向工作线程队列发送消息
Message msg = Message.obtain(); //不用new一个Message,采用obtain
msg.what = 2;
msg.obj = "窗口" + j;
workHandler.sendMessage(msg);
j++;
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
//step5: 停止handlerThread
handlerThread.quit(); //结束线程,效率高
handlerThread.quitSafely(); //结束线程
}
}
-
效果展示
Log日志
- 结论
从demo展示中和打印的Log中看到,第一次点击Button窗口1开始卖票,窗口1卖第3张票时,再次点击Button,HandlerThread不会停止当前窗口卖票,而是等待当前卖完6张票之后后再开启窗口2执行卖票任务。
5.应用场景
使用HandlerThread处理本地的 I/O操作
(数据库,文件,SharePreferences),推荐使用postAtFrontOfQueue(),快速将读取操作加入队列的前端执行,必要时更新主线程UI。示例场景,从数据库读取数据显示在ListView中。
6.总结
HandlerThread
继承自Thread
,在复写的run()
方法中,调用了Looper.prepare()和Looper.loop(),方便在创建的子线程中使用Handler时需要自己手动调用Looper.prepare()和Looper.loop()。HandlerThread
内部处理消息队列时是按顺序串行执行
,即在处理完一条消息任务后再处理下一条消息任务,不适合处理网络请求
需要并行处理的耗时任务,更适合处理本地的需要按序执行的I/O操作
- 缺点:若其中一条任务处理时间过长,仍然需要等待此条任务处理完成之后才处理下一条任务,会导致后续任务延时执行,造成
线程阻塞
的现象。 - 一个线程对应一个Looper
Looper.prepare()
自动创建一个Looper对象,Looper创建时,自动创建MessageQueue对象,并赋值mThread为当前Thread,ThreadLocal中存入创建的Looper对象 Looper.loop()
开启消息循环
思考
- 什么是线程阻塞,造成原因有哪些?
- HandlerThread需要手动调用quit()/quitSafely(),为什么新建的子线程不用手动调用释放Looper和MessageQueue呢?