盒子
盒子
文章目录
  1. 主线程的原理
  2. 主线程的独特机制
    1. 消息队列(Message Queue)
    2. Handler
  3. 同步屏障
    1. 应用场景
  4. 经典问题
  5. 结论
  6. 推荐

触摸Android的心脏跳动

在Android开发中,主线程扮演着至关重要的角色。毫不夸张的说,它就相当于Android的心脏。只要它还在跳动的运行,Android应用就不会终止。

它负责处理UI事件、界面更新、以及与用户交互的各种操作。本文将深入分析Android主线程的原理、独特机制以及应用,为开发者提供全面的了解和掌握主线程的知识。

主线程的原理

Android应用的核心原则之一是单线程模型,也就是说,大多数与用户界面相关的操作都必须在主线程中执行。这一原则的背后是Android操作系统的设计,主要有以下几个原因:

  • UI一致性:在单线程模型下,UI操作不会被多线程竞争导致的不一致性问题,确保了用户界面的稳定性和一致性。

  • 性能优化:单线程模型简化了线程管理,降低了多线程带来的复杂性,有助于提高应用性能。

  • 安全性:通过将UI操作限制在主线程,可以减少因多线程竞争而引发的潜在问题,如死锁和竞争条件。

主线程的原理可以用以下伪代码表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MainThread {
public static void main(String[] args) {
// 初始化应用
Application app = createApplication();

// 创建主线程消息循环
Looper.prepareMainLooper();

// 启动主线程
while (true) {
Message msg = Looper.getMainLooper().getNextMessage();
if (msg != null) {
// 处理消息
app.handleMessage(msg);
}
}
}
}

在上述伪代码中,主线程通过消息循环(Message Loop)来不断处理消息,这些消息通常包括UI事件、定时任务等。应用的UI操作都会被封装成消息,然后由主线程依次处理。

主线程的独特机制

主线程有一些独特的机制,其中最重要的是消息队列(Message Queue)和Handler。

消息队列(Message Queue)

消息队列是主线程用来存储待处理消息的数据结构。每个消息都有一个与之相关的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
public Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}

int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}

nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
nextPollTimeoutMillis = -1;
}

...
}

...
}
}

Handler

Handler是一个与特定线程关联的对象,它可以用来发送和处理消息。在主线程中,通常使用new Handler(Looper.getMainLooper())来创建一个与主线程关联的Handler。开发者可以使用Handler来将任务提交到主线程的消息队列中。

1
2
3
4
5
6
7
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
// 在主线程执行
}
});

同步屏障

在Android中,消息可以分为同步消息和异步消息。通常,我们发送的消息都是同步消息。
然而,有一种特殊情况,即开启同步屏障。同步屏障是一种消息机制的特性,可以阻止同步消息的处理,只允许异步消息通过。通过调用MessageQueue的postSyncBarrier()方法,可以开启同步屏障。在开启同步屏障后,发送的这条消息它的target为null。

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
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
// 没有设置target,target为null
msg.when = when;
msg.arg1 = token;

Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}

那么,开启同步屏障后,所谓的异步消息又是如何被处理的呢?
我们又可以回到之前MessageQueue中的next方法了

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
public Message next() {
// 省略部分代码,只体现出来同步屏障的代码
...
for (;;) {
...

synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//注意这里,开始出来同步屏障
//如果target==null,认为它就是屏障,进行循环遍历,直到找到第一个异步的消息
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}

...
}

...
}
}

所以同步屏障是会让消息顺序进行调整,让其忽略现有的同步消息,来直接处理临近的异步消息。
现在听起来已经知道了同步屏障的作用,但它的实际应用又有哪些呢?

应用场景

虽然在日常应用开发中,同步屏障的使用频率较低,但在Android系统源码中,同步屏障的使用场景非常重要。一个典型的使用场景是在UI更新时,例如在View的绘制、布局调整、刷新等操作中,系统会开启同步屏障,以确保与UI相关的异步消息得到优先处理。当UI更新完成后,同步屏障会被移除,允许后续的同步消息得以处理。

对应的是ViewRootImpl#scheduleTraversals()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 设置同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}

经典问题

Android 主线程的消息循环是通过 LooperHandler 来实现的。以下是一段伪代码示例:

1
2
3
4
5
6
7
8
9
// 创建一个 Looper,关联当前线程
Looper.prepare();
Looper loop = Looper.myLooper();

// 创建一个 Handler,它将和当前 Looper 关联
Handler handler = new Handler();

// 进入消息循环
Looper.loop();

开启loop后的核心代码如下:

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
public static void loop() {
final Looper me = myLooper();
...
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}

private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
// 注意没消息会被阻塞,进入休眠状态
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}

...

try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();

return true;
}

在这段示例中,主线程的消息循环被启动,它会等待来自消息队列的消息。有了这个基础下面的问题就简单了:

  1. 为什么主线程不会陷入无限循环?

    主线程的消息循环不会陷入无限循环,因为它不断地从消息队列中获取消息并处理它们。如果没有消息要处理,消息循环会进入休眠状态,不会持续消耗 CPU 资源。只有在有新消息到达时,主线程才会被唤醒来处理这些消息。这个机制确保主线程能够响应用户的操作,而不陷入死循环。

  2. 如果没有消息,主线程会如何处理?

    如果消息队列为空,主线程的消息循环会等待,直到有新消息到达。在等待期间,它不会执行任何操作,也不会陷入循环。这是因为 Android 的消息循环是基于事件驱动的,只有当有事件(消息)到达时,才会触发主线程执行相应的处理代码。当新消息被投递到消息队列后,主线程会被唤醒,执行相应的处理操作,然后再次进入等待状态。

这种事件驱动的消息循环机制使得 Android 应用能够高效地管理用户交互和异步操作,同时保持了响应性和低能耗。所以,主线程不会陷入无限循环,而是在需要处理事件时才会执行相应的代码。

结论

Android主线程是应用的核心,负责处理UI事件、界面更新和定时任务等。了解主线程的原理和独特机制是Android开发的关键,它有助于确保应用的稳定性和性能。通过消息队列和Handler,开发者可以在主线程中安全地处理各种任务,提供流畅的用户体验。

推荐

android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。

AwesomeGithub: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。

flutter_github: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。

android-api-analysis: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。

daily_algorithm: 每日一算法,由浅入深,欢迎加入一起共勉。

支持一下
赞赏是一门艺术