HandlerThreadAndroid线程和线程池(二)HandlerThread学习记录 使用+源码
java与android在线程方面大部分相同,本篇我们要介绍的是android的专属类HandlerThread,因为HandlerThread在设置思想上挺值得我们学习。
HandlerThread本质上是一个Android
已封装好的轻量级异步类,一个线程类,它继承了Thread;类似于Handler+Thread
一、作用
- 实现多线程在工作线程中执行任务,如 耗时任务
- 异步通信、消息传递实现工作线程 & 主线程(
UI
线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI
操作
从而保证线程安全
二、工作原理
内部原理 = Thread
类 + Handler
类机制,即:
- 通过继承
Thread
类,快速地创建1个带有Looper
对象的新工作线程 - 通过封装
Handler
类,快速创建Handler
& 与其他线程进行通信 - HandlerThread有自己的内部Looper对象,可以进行looper循环,通过消息队列来重复使用当前线程,节省资源开销;
- 通过获取HandlerThread的looper对象传递给Handler对象,可以在handleMessage方法中执行异步任务。
- 创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。
- 他和普通线程的区别就是他在内部维护了一个looper(也正是这个原因所以他可以创建对应的Handler),然后就会通过Handler的方式来通过Looper执行下一个任务。
- 由于内部的Looper是一个无限循环,所以当我们不再使用HandlerThread的时候就需要调用quit方法来终止他。
三、HandlerThread的特点
它和普通的Thread有显著的不同之处。
- 普通Thread主要用于在run方法中执行一个耗时任务
- 而HandlerThread在run方法内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread执行一个具体的任务。
优势:
- 将loop运行在子线程中处理,减轻了主线程MainLooper的压力,使主线程、界面更流畅
- 串行执行,按消息发送顺序进行处理,开启一个线程起到多个线程的作用。HandlerThread本质是一个线程,在线程内部,代码是串行处理的。
- 有自己的消息队列,不会干扰UI线程
- 方便实现异步通信,即不需使用 “任务线程(如继承
Thread
类) +Handler
”的复杂组合
通过继承Thread类和封装Handler类的使用,使得创建新线程和与其他线程进行通信变得更加方便易用
劣势:
- 由于每一个任务队列逐步执行,一旦队列耗时过长,消息延时
- 执行网络IO等操作,线程会等待,不能并发,因为它只有一个线程,还得排队一个一个等着。
- 但是由于每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
- HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程。
四、使用
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对象、实现消息处理操作 & 与其他线程进行通信
* 注:消息处理操作(HandleMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
*/
workHandler = new Handler(mHandlerThread.getLooper()){
@Override
// 消息处理的操作
public void handleMessage(Message msg)
{
//设置了两种消息处理操作,通过msg来进行识别
switch(msg.what){
// 消息1
case 1:
try {
//延时操作
Thread.sleep(600);
} 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(1200);
} 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();
}
});
}
}
五、源码
步骤1:创建HandlerThread的实例对象
构造函数
/**
* 具体使用
* 传入参数 = 线程名字,作用 = 标记该线程
*/
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
/**
* 源码分析:HandlerThread类的构造方法
*/
/**
* A {@link Thread} that has a {@link Looper}.
* The {@link Looper} can then be used to create {@link Handler}s.
* <p>
* Note that just like with a regular {@link Thread}, {@link #start()} must still be called.
*/
public class HandlerThread extends Thread {
int mPriority;// 线程优先级
int mTid = -1;// 当前线程id
Looper mLooper;//成员变量mLooper就是HandlerThread自己持有的Looper对象
private @Nullable Handler mHandler;
// HandlerThread类有2个构造方法
// 区别在于:设置当前线程的优先级参数,即可自定义设置 or 使用默认优先级
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
- 创建
HandlerThread
类对象 = 创建Thread
类对象 + 设置线程优先级 = 新开1个工作线程 + 设置线程优先级
步骤2:启动线程
/**
* 具体使用
*/
mHandlerThread.start();
/**
* 源码分析:此处调用的是父类(Thread类)的start(),最终回调HandlerThread的run()
*/
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
//onLooperPrepared()该方法是一个空实现,是留给我们必要时可以去重写的,
//但是注意重写时机是在Looper循环启动前
protected void onLooperPrepared() {
}
@Override
public void run() {
// 1. 获得当前线程的id
mTid = Process.myTid();
// 2. 创建1个Looper对象 & MessageQueue对象
Looper.prepare();
// 3. 通过持有锁机制来获得当前线程的Looper对象
synchronized (this) {
mLooper = Looper.myLooper();
// 发出通知:当前线程已经创建mLooper对象成功
// 此处主要是通知getLooper()中的wait()
notifyAll(); //唤醒等待线程
// 此处使用持有锁机制 + notifyAll() 是为了保证后面获得Looper对象前就已创建好Looper对象
}
// 4. 设置当前线程的优先级
Process.setThreadPriority(mPriority);
// 该方法实现体是空的,子类可实现 / 不实现该方法
onLooperPrepared();
// 6. 进行消息循环,即不断从MessageQueue中取消息 & 派发消息
Looper.loop();
mTid = -1;
}
在创建HandlerThread对象后必须调用其start()方法才能进行其他操作,而调用start()方法后相当于启动了线程,也就是run方法将会被调用,而我们从run源码中可以看出其执行了Looper.prepare()代码,这时Looper对象将被创建,当Looper对象被创建后将绑定在当前线程,才可以把Looper对象赋值给Handler对象,进而确保Handler对象中的handleMessage方法是在异步线程执行的。
Looper对象创建后将其赋值给HandlerThread的内部变量mLooper,并通过notifyAll()方法去唤醒等待线程,最后执行Looper.loop();
,开启looper循环语句。
步骤3:创建工作线程Handler & 复写handleMessage()
为什么要唤醒等待线程呢?我们来看看,getLooper方法
/**
* 具体使用
* 作用:将Handler关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
* 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
*/
Handler workHandler = new Handler( handlerThread.getLooper() ) {
@Override
public boolean handleMessage(Message msg) {
...//消息处理
return true;
}
});
/**
* 源码分析:handlerThread.getLooper()
* 作用:获得当前HandlerThread线程中的Looper对象
*/
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {//先判断当前线程是否启动(存活)了
return null;
}
// 若当前线程存活,再判断线程的成员变量mLooper是否为null
// 直到线程创建完Looper对象后才能获得Looper对象,若Looper对象未创建成功,则阻塞
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();//等待唤醒
} catch (InterruptedException e) {
}
}
}
// 上述步骤run()使用 持有锁机制 + notifyAll() 获得Looper对象后
// 则通知当前线程的wait()结束等待 & 跳出循环
// 最终getLooper()返回的是在run()中创建的mLooper对象
return mLooper;
}
如果线程已经启动,那么将会进入同步语句并判断Looper是否为null,为null则代表Looper对象还没有被赋值,也就是还没被创建,此时当前调用线程进入等待阶段,直到Looper对象被创建并通过 notifyAll()方法唤醒等待线程
,最后才返回Looper对象,之所以需要等待唤醒机制,是因为Looper的创建是在子线程
中执行的,而调用getLooper方法则是在主线程
进行的,这样我们就无法保障我们在调用getLooper方法时Looper已经被创建,到这里我们也就明白了在获取mLooper对象时会存在一个同步的问题,只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值,HandlerThread内部则通过等待唤醒机制解决了同步问题。
步骤4:使用工作线程Handler
向工作线程的消息队列发送消息
/**
* 具体使用
* 作用:在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
* 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
*/
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2; //消息的标识
msg.obj = "B"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
workHandler.sendMessage(msg);
/**
* 源码分析:workHandler.sendMessage(msg)
* 此处的源码即Handler的源码,故不作过多描述
*/
步骤5:结束线程,即停止线程的消息循环
/**
* 具体使用
*/
mHandlerThread.quit();
/**
* 源码分析:mHandlerThread.quit()
* 说明:
* a. 该方法属于HandlerThread类
* b. HandlerThread有2种让当前线程退出消息循环的方法:quit() 、quitSafely()
*/
// 方式1:quit()
// 特点:效率高,但线程不安全
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
**looper.quit();**
return true;
}
return false;
}
// 方式2:quitSafely()
// 特点:效率低,但线程安全
/**
* Quits the handler thread's looper safely.
* <p>
* Causes the handler thread's looper to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* Pending delayed messages with due times in the future will not be delivered.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p>
* If the thread has not been started or has finished (that is if
* {@link #getLooper} returns null), then false is returned.
* Otherwise the looper is asked to quit and true is returned.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*/
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
**looper.quitSafely();**
return true;
}
return false;
}
我们调用quit方法时,其内部实际上是调用Looper的quit方法而最终执行的则是MessageQueue中的removeAllMessagesLocked
方法(Handler消息机制知识点),该方法主要是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送)还是非延迟消息。
当调用quitSafely方法时,其内部调用的是Looper的quitSafely方法而最终执行的是MessageQueue中的removeAllFutureMessagesLocked
方法,该方法只**会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理完成后才停止Looper循环,**quitSafely相比于quit方法安全的原因在于清空消息之前会派发所有的非延迟消息
。最后需要注意的是Looper的quit方法是基于API 1,而Looper的quitSafely方法则是基于API 18的。
// 注:上述2个方法最终都会调用MessageQueue.quit(boolean safe)->>分析1
/**
* 分析1:MessageQueue.quit(boolean safe)
*/
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked(); // 方式1(不安全)会调用该方法 ->>分析2
} else {
removeAllMessagesLocked(); // 方式2(安全)会调用该方法 ->>分析3
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
/**
* 分析2:removeAllMessagesLocked()
* 原理:遍历Message链表、移除所有信息的回调 & 重置为null
*/
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
/**
* 分析3:removeAllFutureMessagesLocked()
* 原理:先判断当前消息队列是否正在处理消息
* a. 若不是,则类似分析2移除消息
* b. 若是,则等待该消息处理处理完毕再使用分析2中的方式移除消息退出循环
* 结论:退出方法安全与否(quitSafe() 或 quit()),在于该方法移除消息、退出循环时是否在意当前队列是否正在处理消息
*/
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
// 判断当前消息队列是否正在处理消息
// a. 若不是,则直接移除所有回调
if (p.when > now) {
removeAllMessagesLocked();
} else {
// b. 若是正在处理,则等待该消息处理处理完毕再退出该循环
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
参考资料
https://blog.youkuaiyun.com/carson_ho/article/details/79285760