Android的消息机制
概述
Handler不是专门用于更新UI的,它只是常被开发者用于更新UI
Android消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程,这三个是一个整体
Handler作用是将一个任务切换到某个指定的线程中去执行
MessageQueue虽说叫消息队列但其内部存储结构并非队列,而是采用单链表的数据结构来存储消息列表
Looper可以理解为消息循环,会以无限循环的形式去查找是否有新消息,如果有就处理消息如果没有就等待
线程默认没有Looper,如果需要使用Handler就必须为线程创建Looper
为什么要提供在某个具体的线程中执行任务这种功能呢:
- Android规定访问UI只可以在主线程中进行
- Android又建议不要在主线程中进行耗时操作不然会导致ANR
为什么不允许在子线程中访问UI
- Android的UI控件不是线程安全的
- 如果在多线程中并发访问可能导致UI控件处于不可预期的状态
为什么不对UI控件的访问加上锁机制呢?
- 加上锁机制会让UI访问的逻辑变得复杂
- 会降低UI访问效率,因为锁机制会阻塞某些线程的执行
Looper是运行在创建Handler所在的线程中的
Android消息机制分析
ThreadLocal工作原理
概念
ThreadLocal是线程内部的数据存储类,通过它可以在指定的线程中存储数据,并且只有在指定线程中可以获取到存储的数据
使用场景
- 当某些数据是以线程作为定义域并且不同线程具有不同的数据副本的时候
- 复杂逻辑下的对象传递比如监听器的传递
实例
1
private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<Boolean>();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 1. 在主线程中设置值为 true
mBooleanThreadLocal.set(true);
Log.d(TAG, "[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
// 2. 在子线程 1 中设置值为 false
new Thread("Thread#1") {
public void run() {
mBooleanThreadLocal.set(false);
Log.d(TAG, "[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
};
}.start();
// 3. 在子线程 2 中不设置值,直接获取
new Thread("Thread#2") {
public void run() {
Log.d(TAG, "[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
};
}.start();1
2
3
4//运行结果:
//[Thread#main]mBooleanThreadLocal=true
//[Thread#1]mBooleanThreadLocal=false
//[Thread#2]mBooleanThreadLocal=null
MessageQueue工作原理
- 主要包含两个操作:插入和读取,读取操作本身就会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next其中enqueueMessage是往消息队列中插入一条消息,而next是从队列中取出一条消息并将其从消息队列中移除
- next方法是一个无限循环的方法,如果队列中无新消息那么next就会一直阻塞在这里,当有消息时会返回该消息并将其从单链表中移除
Looper工作原理
扮演着消息循环的角色,具体来说就是它会不停地从MessageQueue里面查看是否有新消息,如果有新消息就立刻处理,否则就一直阻塞在那里
创建Looper
1
2
3
4
5
6
7
8new Thread("Thread#2") {
public void run() {
Looper.prepare();//为当前线程创建一个Looper
Handler handler=new Handler();
Looper.loop();//开启消息循环
};
}.start();- 除了prepare外还准备了prepareMainLooper方法,这个是给主线程创建Looper使用的,通过getMainLooper可以在任何地方获取到主线程的Looper。
Looper也可以通过quit或quitSafely来退出,quit会直接退出而quitSafely只是设定一个退出标记,然后把消息队列中已有的数据处理完之后才安全地退出
Looper必须退出否则loop方法会无限循环下去
Handler工作原理
Handler工作主要包含消息的发送和接收过程,发送可以通过post或send的一系列方法,post方法最终也是通过send方法实现的
Handler发送消息过程仅仅是向消息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper,Looper收到消息后进行处理,最终消息由Handler进行处理即Handler的dispatchMessage方法会被调用,这时Handler就进入了处理消息阶段
处理过程流程图

Handler消息处理机制实例
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//模拟异步加载数据
public class MainActivity extends AppCompatActivity {
private TextView tvStatus;
private Button btnStart;
// 1. 定义消息类型常量
private static final int MSG_UPDATE_TEXT = 1;
private static final int MSG_FINISHED = 2;
// 2. 创建 Handler 实例(注意:在主线程创建)
private Handler mHandler = new Handler(Looper.getMainLooper()) {
public void handleMessage( Message msg) {
// 4. 在主线程接收并处理消息
switch (msg.what) {
case MSG_UPDATE_TEXT:
tvStatus.setText("正在加载数据..." + msg.arg1 + "%");
break;
case MSG_FINISHED:
tvStatus.setText("加载完成!");
break;
}
}
};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvStatus = findViewById(R.id.tv_status);
btnStart = findViewById(R.id.btn_start);
btnStart.setOnClickListener(v -> startDownload());
}
private void startDownload() {
// 3. 开启子线程执行耗时操作
new Thread(() -> {
for (int i = 1; i <= 100; i += 10) {
try {
Thread.sleep(500); // 模拟耗时操作
// 发送进度消息
Message message = Message.obtain();
message.what = MSG_UPDATE_TEXT;
message.arg1 = i;
mHandler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 发送完成消息
mHandler.sendEmptyMessage(MSG_FINISHED);
}).start();
}
}




