8为什么需要HandlerThread
如果我们需要创建一个子线程,主线程发送的消息能否交给子线程处理呢?如何实现呢?
- HandlerThread正是开辟一个带有Looper的工作线程
- 通过创建一个与这个工作线程的Looper绑定的workHandler
- 在主线程中用workHandler发送消息,最终消息又交给workHandler在工作线程中处理
- 由此实现在主线程发送的消息交给子线程处理
设计本质
本质上HandlerThread是通过继承Thread类和封装Handler来实现线程之间的通信。内部原理依旧是Thread类+Hnadler类机制。
- 通过继承Thread类,快速地创建1个带有Looper对象的新工作线程,并且拥有自己的MessageQueue
- 通过封装Handler类,快速创建Handler & 与其他线程进行通信
- 因此HandlerThread本身就是一个线程
- 不同于直接通过Thread类创建的线程,HandlerThread它是一个带有Looper,并且通过Looper会创建MessageQueue的线程。
- Handler中必须要有Looper,而在UI主线程中默认为我们创建了Looper,如果通过Thread创建的线程是没有Looper。在创建Handler的过程中可以指定任意线程的Looper对象。
- 这样的设计使得HandlerThread其实和UI主线程类似,可以通过HandlerThread来分担UI线程的工作量,降低主线程的压力。
具体使用
- 创建HandlerThread对象,并指定线程的名字
- 启动线程
- 创建工作线程Handler 并 指定该Handler的Looper ,复写handlerMessage()
- 在主线程中使用工作线程Handler向工作线程的消息队列发送消息
- 结束线程,即停止线程的消息循环
java
// 步骤1:创建HandlerThread实例对象
// 传入参数 = 线程名字,作用 = 标记该线程
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
// 步骤2:启动线程
mHandlerThread.start();
// 步骤3:创建工作线程Handler & 复写handleMessage()
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
Handler workHandler = new Handler( mHandlerThread.getLooper() ) {
@Override
public boolean handleMessage(Message msg) {
...//消息处理
···//将处理结果通知给主线程
return true;
}
});
// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2; //消息的标识
msg.obj = "B"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
workHandler.sendMessage(msg);
// 步骤5:结束线程,即停止线程的消息循环
mHandlerThread.quit();
使用实例
java
public class MainActivity extends AppCompatActivity {
Handler mainHandler,workHandler;
HandlerThread mHandlerThread;
TextView text;
Button button1,button2,button3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 显示文本
text = (TextView) findViewById(R.id.text1);
// 创建与主线程关联的Handler
mainHandler = new Handler();
/**
* 步骤1:创建HandlerThread实例对象
* 传入参数 = 线程名字,作用 = 标记该线程
*/
mHandlerThread = new HandlerThread("handlerThread");
/**
* 步骤2:启动线程
*/
mHandlerThread.start();
/**
* 步骤3:创建工作线程Handler & 复写handleMessage()
* 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
* 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
*/
workHandler = new Handler(mHandlerThread.getLooper()){
@Override
// 消息处理的操作
public void handleMessage(Message msg)
{
//设置了两种消息处理操作,通过msg来进行识别
switch(msg.what){
// 消息1
case 1:
try {
//延时操作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 通过主线程Handler.post方法进行在主线程的UI更新操作
mainHandler.post(new Runnable() {
@Override
public void run () {
text.setText("我爱学习");
}
});
break;
// 消息2
case 2:
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mainHandler.post(new Runnable() {
@Override
public void run () {
text.setText("我不喜欢学习");
}
});
break;
default:
break;
}
}
};
/**
* 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
* 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
*/
// 点击Button1
button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 通过sendMessage()发送
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 1; //消息的标识
msg.obj = "A"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
workHandler.sendMessage(msg);
}
});
// 点击Button2
button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 通过sendMessage()发送
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2; //消息的标识
msg.obj = "B"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
workHandler.sendMessage(msg);
}
});
// 点击Button3
// 作用:退出消息循环
button3 = (Button) findViewById(R.id.button3);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHandlerThread.quit();
}
});
}
}
我们通过在主线程中使用工作线程的workhandler去发送消息,然后在工作线程中去处理耗时操作,处理完成之后通知主线程并更新界面,通过mainhandler.post将消息传回给主线程。
java
mainHandler.post(new Runnable() {
@Override
public void run () {
text.setText("我不喜欢学习");
}
});
源码分析
Android多线程:这是一份详细的HandlerThread源码分析攻略
1.创建HandlerThread对象
在创建HandlerThread对象的过程中主要完成了两件事:
- 创建一个工作线程
- 设置线程的优先级
java
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
java
public class HandlerThread extends Thread {
// 线程优先级
int mPriority;
// 线程号
int mTid = -1;
// 线程内部的 Looper 对象
Looper mLooper;
private @Nullable Handler mHandler;
// 有两个构造方法
// 只指定线程名字并使用默认的线程优先级来构造 HandlerThread 对象
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
// 同时指定线程名字和优先级来构造 HandlerThread 对象
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
// 省略其他内容
...
}
2.启动handlerThread
HandlerThread本身就是一个Thread,所以也是通过start()方法启动,最终调用到run()方法。
java
/**
* 具体使用
*/
mHandlerThread.start();
/**
* 源码分析:此处调用的是父类(Thread类)的start(),最终回调HandlerThread的run()
*/
@Override
public void run() {
// 1. 获得当前线程的id
mTid = Process.myTid();
// 2. 创建1个Looper对象 & MessageQueue对象
Looper.prepare();
// 3. 通过持有锁机制来获得当前线程的Looper对象
//为什么要给HandlerThread加同步锁机制呢?
synchronized (this) {
mLooper = Looper.myLooper();
// 发出通知:当前线程已经创建mLooper对象成功
// 此处主要是通知getLooper()中的wait()
notifyAll();
// 此处使用持有锁机制 + notifyAll() 是为了保证后面获得Looper对象前就已创建好Looper对象
}
// 4. 设置当前线程的优先级
Process.setThreadPriority(mPriority);
// 5. 在线程循环前做一些准备工作 ->>分析1
// 该方法实现体是空的,子类可实现 / 不实现该方法
onLooperPrepared();
// 6. 进行消息循环,即不断从MessageQueue中取消息 & 派发消息
Looper.loop();
mTid = -1;
}
}
/**
* 分析1:onLooperPrepared();
* 说明:该方法实现体是空的,子类可实现 / 不实现该方法
*/
protected void onLooperPrepared() {
}
为什么要给HandlerThread加同步锁机制呢? 因为在后面我们需要创建一个workHandler,并为其指定一个Looper,而这个Looper就是HandlerThread的Looper,所以为了防止我们在getLooper()时,它还没创建好,所以通过锁机制,当Looper在创建的时候,让getLooper()等待wait(),直到Looper创建完成,通过notifyAll()通知Looper已经创建好了。