HandlerThread
MoMo Lv5
  1. HandlerThread是什么?⭐⭐⭐⭐⭐
  2. HandlerThread原理和使用场景?⭐⭐⭐

HandlerThread是什么?

在安卓开发中,如果需要执行耗时操作,则可以开启子线程来完成,然而手动创建销毁线程又麻烦又消耗系统性能,因此可以使用线程池来完成。如果还需要在线程中使用Handler异步消息机制,或者需要实现子线程和子线程之间的通讯(Handler是主线程和子线程之间的通讯),那么就可以用HandlerThreaad

HandlerThread是Google封装好的一个类,它的内部有自己的Looper对象,可以进行Loop轮询,用于执行多个耗时操作,而不需要多次开启线程,本质是使用Handler和Looper实现的。

HandlerThread的本质: 继承Thread类 & 封装Handler类

HandlerThread怎么使用

如果我们需要使用HandlerThread来读取一个大文件的内容,可以这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 步骤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();

使用mHandlerThread的looper创建的mThreadHandler,里面的handleMessage是可以进行耗时操作的,因为它是执行在mHandlerThread所在的子线程。因此其优点是不会阻塞主线程,但是多任务时也需要有序执行,导致执行效率低。因此HandlerThread比较适合耗时不且不会产生较大的阻塞,比如读取文件,操作数据库等,至于网络IO操作这种可能有较大阻塞等,HandlerThread并不适合。

HandlerThread的使用方法:

  1. 创建HandlerThread实例
  2. 执行start函数来启动HandlerThread线程
  3. 将HandlerThread和Handler绑定
  4. 然后执行startHandlerThread(),最后退出HandlerThread

内存泄漏

如果没有及时退出会造成内存泄漏:

  1. 当Handler消息队列 还有未处理的消息 / 正在处理消息时,存在引用关系: “未被处理 / 正处理的消息 -> Handler实例 -> 外部类”
  2. 若出现 Handler的生命周期 > 外部类的生命周期 时(即 Handler消息队列 还有未处理的消息 / 正在处理消息 而 外部类需销毁时),将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露

解决方案

静态内部类

原理:静态内部类不默认持有外部类的引用,从而使得 “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系 不存在。

具体方案:将Handler的子类设置成静态内部类。此外,还可使用WeakReference弱引用持有外部类,保证外部类能被回收。因为:弱引用的对象拥有短暂的生命周期,在垃圾回收器线程扫描时,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存

解决代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 实例化自定义的Handler类对象->>分析1
// 注:
// a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue;
// b. 定义时需传入持有的Activity实例(弱引用)
showhandler = new FHandler(this);

new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 1;// 消息标识
msg.obj = "AA";// 消息存放

showhandler.sendMessage(msg);
}
}.start();

}

// 设置为:静态内部类
private static class FHandler extends Handler{

// 定义 弱引用实例
private WeakReference<Activity> reference;

// 在构造方法中传入需持有的Activity实例
public FHandler(Activity activity) {
// 使用WeakReference弱引用持有Activity实例
reference = new WeakReference<Activity>(activity); }

// 通过复写handlerMessage() 从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到线程1的消息");
break;
case 2:
Log.d(TAG, " 收到线程2的消息");
break;


}
}
}
}

当外部类结束生命周期时,清空Handler内消息队列

原理:不仅使得 “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系 不复存在,同时 使得 Handler的生命周期(即 消息存在的时期) 与 外部类的生命周期 同步

具体方案:当 外部类(此处以Activity为例) 结束生命周期时(此时系统会调用onDestroy()),清除 Handler消息队列里的所有消息(调用removeCallbacksAndMessages(null))

具体代码

1
2
3
4
5
6
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
// 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期
}

为了保证Handler中消息队列中的所有消息都能被执行,此处推荐使用解决方案1解决内存泄露问题,即 静态内部类 + 弱引用的方式

源码分析

步骤1:创建HandlerThread的实例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 具体使用
* 传入参数 = 线程名字,作用 = 标记该线程
*/
HandlerThread mHandlerThread = new HandlerThread("handlerThread");

/**
* 源码分析:HandlerThread类的构造方法
*/
public class HandlerThread extends Thread { // 继承自Thread类
int mPriority; // 线程优先级
int mTid = -1; // 当前线程id
Looper mLooper; // 当前线程持有的Looper对象

// HandlerThread类有2个构造方法
// 区别在于:设置当前线程的优先级参数,即可自定义设置 or 使用默认优先级

// 方式1. 默认优先级
public HandlerThread(String name) {
// 通过调用父类默认的方法创建线程
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

// 方法2. 自定义设置优先级
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
...
}
  • HandlerThread类继承自Thread类
  • 创建HandlerThread类对象 = 创建Thread类对象 + 设置线程优先级 = 新开1个工作线程 + 设置线程优先级

步骤2:启动线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 具体使用
*/
mHandlerThread.start();

/**
* 源码分析:此处调用的是父类(Thread类)的start(),最终回调HandlerThread的run()
*/
@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);

// 5. 在线程循环前做一些准备工作
// 该方法实现体是空的,子类可实现 / 不实现该方法
onLooperPrepared();

// 6. 进行消息循环,即不断从MessageQueue中取消息 & 派发消息
Looper.loop();

mTid = -1;
}
  • 为当前工作线程(即步骤1创建的线程)创建1个Looper对象 & MessageQueue对象
  • 通过持有锁机制来获得当前线程的Looper对象
  • 发出通知:当前线程已经创建mLooper对象成功
  • 工作线程进行消息循环,即不断从MessageQueue中取消息 & 派发消息

步骤3:创建工作线程Handler & 复写handleMessage()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 具体使用
* 作用:将Handler关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
* 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
*/
Handler workHandler = new Handler( handlerThread.getLooper() ) {
@Override
public boolean handleMessage(Message msg) {
...//消息处理
return true;
}
});

/**
* 源码分析:handlerThread.getLooper()
* 作用:获得当前HandlerThread线程中的Looper对象
*/
public Looper getLooper() {
// 若线程不是存活的,则直接返回null
if (!isAlive()) {
return null;
}
// 若当前线程存活,再判断线程的成员变量mLooper是否为null
// 直到线程创建完Looper对象后才能获得Looper对象,若Looper对象未创建成功,则阻塞
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
// 此处会调用wait方法去等待
wait();
} catch (InterruptedException e) {
}
}
}
// 上述步骤run()使用 持有锁机制 + notifyAll() 获得Looper对象后
// 则通知当前线程的wait()结束等待 & 跳出循环
// 最终getLooper()返回的是在run()中创建的mLooper对象
return mLooper;
}
  • 在获得HandlerThread工作线程的Looper对象时存在一个同步的问题:只有当线程创建成功 & 其对应的Looper对象也创建成功后才能获得Looper的值,才能将创建的Handler 与 工作线程的Looper对象绑定,从而将Handler绑定工作线程
  • 解决方案:即保证同步的解决方案 = 同步锁、wait和 notifyAll,即 在run()中成功创建Looper对象后,立即调用notifyAll通知 getLooper()中的wait结束等待&返回run()中成功创建的Looper对象,使得Handler与该Looper对象绑定

步骤4:使用工作线程Handler向工作线程的消息队列发送消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 具体使用
* 作用:在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
* 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
*/
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2; //消息的标识
msg.obj = "B"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
workHandler.sendMessage(msg);

/**
* 源码分析:workHandler.sendMessage(msg)
* 此处的源码即Handler的源码
*/

步骤5:结束线程,即停止线程的消息循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
* 具体使用
*/
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()
// 特点:效率低,但线程安全
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}

// 注:上述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(安全)会调用该方法 ->>分析3
} else {
removeAllMessagesLocked(); // 方式2(不安全)会调用该方法 ->>分析2
}
// 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);
}
}

总结

  • HandlerThread 是内部会创建Looper的线程,因此只能作为子线程使用;
  • HandlerThread 需要配合 Handler 使用,HandlerThread 的耗时操作在handleMessage()中执行,因为此时执行的是在HandlerThread所在的子线程执行的,但不能像普通线程一样在run()方法中进行,因为HanderThread的run()方法已经被写死。
  • HandlerThread和Handler绑定之前,必须先调用 mHandlerThread.start() 让 run() 方法跑起来。

  1. 创建HandlerThread的实例对象
    1. 创建HandlerThread类对象=创建Thread类对象+设置线程优先级
    2. 即:新开1个工作线程+设置线程优先级
  2. 启动HandlerThread工作线程
    1. 为当前工作线程(即步骤1创建的线程)创建1个Looper对象&MessageQueue对象
    2. 通过持有锁机制来获得当前线程的Looper对象
    3. 向getLooper()的wait()发出通知:当前线程已经创建mLooper对象成功
    4. 工作线程进行消息循环,即不断从MessageQueue中取消息 & 派发消息
  3. 创建工作线程的Handler (需复写handleMessage())
    1. 将Handler关联到HandlerThread的Looper对象,从而绑定到HandlerThread的工作线程
  4. 使用工作线程Handler向工作线程的消息队列发送消息
    1. 在工作线程中,当消息循环时取出对应消息 &在工作线程执行相关操作
    2. 此处的源码即Handler的源码
  5. 结束工作线程
    1. 即停止线程的消息循环
    2. 可使用安全/不安全的方式结束:quit()/quitSafely()
    3. (安全与否(quitSafe() 或 quit()),在于该方法移除消息、退出循环时是否在意当前队列是否正在处理消息)
    4. 停止消息循环的本质:遍历Message链表、移除所有信息的回调&重置为null
Powered by Hexo & Theme Keep
Unique Visitor Page View