HandlerThread之源码完全解析

此文是我还在学校[2016-06-28]那段时间写的文章, 当时记录在公众号中,由于长期没有继续维护,随搬迁至此博客.


前言

在正文开始之前如果你对Handler还存有疑惑,建议先看看我的另一篇文章从源码角度分析Handler、Looper、MessageQueue三角关系再继续看下去,如果已经有了一定的了解了,接下来这篇文章读起来将会很轻松哒~

在开始HandlerThread源码之前我们先写一个与线程相关的Handler

代码部分

  • time.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:id="@+id/id_textview"
android:text="正在加载..."
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="100dp"
android:textSize="15sp"/>
</RelativeLayout>

布局很简单,一个TextView去展示界面

  • TimeActivity.java
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
/**
* Created by linhao on 16/6/26.
*/
public class TimeActivity extends Activity {
private TextView textView;
private static int MSG_UPDATE_INFO = 1;
class MyThread extends Thread{
public Handler mhandler ;
public Looper looper;
@Override
public void run() {
super.run();
Looper.prepare();
looper = Looper.myLooper();
mhandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Looper.loop();
}
}
private MyThread myThread;
//创建主线程handler
private Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.block);
textView = (TextView) findViewById(R.id.id_textview);
//创建后台线程
initBackThread();
}
private void initBackThread() {
myThread = new MyThread();
myThread.start();
myThread.mhandler = new Handler(myThread.looper){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
checkForUpdate();
myThread.mhandler.sendEmptyMessage(MSG_UPDATE_INFO);
}
};
}
private void checkForUpdate() {
//模拟耗时操作
try {
Thread.sleep(1000);
mHandler.post(new Runnable() {
@Override
public void run() {
Date date = new Date();
DateFormat today = new SimpleDateFormat("yyyy年MM月dd日 hh时mm分ss秒 EE", Locale.CHINA);
textView.setText(today.format(date));
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected void onResume() {
super.onResume();
myThread.mhandler.sendEmptyMessage(MSG_UPDATE_INFO);
}
@Override
protected void onPause() {
super.onPause();
myThread.mhandler.removeMessages(MSG_UPDATE_INFO);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}

代码我们定义一个内部类MyThread,紧接着通过looper = Looper.myLooper()拿到储存在sThreadLocal的Looper实例,如果这里听不懂的话建议回头看看从源码角度分析Handler、Looper、MessageQueue三角关系这篇文章,重写handleMessage最后调用Looper的loop方法,从MessageQueue中去取消息。定义一个checkForUpdate方法模拟从服务器解析数据,通过post方法创建一个线程,最后在run方法里面更新UI,模拟时钟。

咳咳,这里我得先打住,有谁看到这段代码以为是创建了一个线程请举个手(yo~put ur hands up🙌)!!!

1
2
3
4
5
6
mHandler.post(new Runnable() {
@Override
public void run() {
......
}
});

我告诉你其实什么线程都没有被创建,请看源码

1
2
3
4
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}

是不是觉得这几行代码很亲切呢,继续看下去

1
2
3
4
5
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}

可以看到,getPostMessage将Runnable的对象r复制给了Message的callback。回头看看sendMessageDelayed是不是很熟悉呢?没错,跟handler的sendMessageDelayed一模一样。我们再看看下面这段代码

1
2
3
4
5
6
7
8
9
10
11
12
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

我们拿到Looper对象后通过Looper.loop()方法在MessageQueue中不断轮回拿取数据,然后回调msg.target.dispatch(…),这里通过判断msg.callback是否为空,如果不为空就执行回调方法handleCallback(msg),而在前面我们已经给callback赋值了,就是Runnable

1
2
3
private static void handleCallback(Message message) {
message.callback.run();
}

紧接着调用run方法在里面更新我们的UI代码,代码其实就这些,一点也不复杂是不是?
插叙就这么多,接着我们打开模拟器Genymotion运行下我们的代码

stack

我们发现程序奔溃了,正常思维我们看看日志报什么错误

stack

日志报了个RuntimeException空指针错误,为什么呢?我们再看看这行代码myThread.mhandler = new Handler(myThread.looper),我们传入新建的thread的looper,这里涉及到了线程并发的问题因为两个线程交叉运行当编译器运行到这段代码时但此时的looper却没被创建就会报出空指正的问题。那我们改如何避免这个问题呢?很幸运google已经帮我事先考虑到了,所以才有了HandlerThread,下面修改下代码,仅两处

  • 1、定义一个HandlerThread和Handler
1
2
private HandlerThread mCheckMsgThread;
private Handler mCheckHandler;

这里我们不再需要MyThread这个类了,在initBackThread方法内实例化HandlerThread

1
2
mCheckMsgThread = new HandlerThread("随意");
mCheckMsgThread.start();

HandleThread需要传入有个线程名字,这里随意

1
2
3
4
5
6
7
8
9
mCheckHandler = new Handler(mCheckMsgThread.getLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
checkForUpdate();
mCheckHandler.sendEmptyMessage(MSG_UPDATE_INFO);
}
};
}

我们重新跑一次程序(掉帧得好厉害,但效果还是看得出来啦~)

stack

感谢您的阅读,本文由 lynhao 原创提供。如若转载,请注明出处:lynhao(http://www.lynhao.cn
从源码角度分析Handler、Looper、MessageQueue三角关系
webpack渐入佳境