触摸Android的心脏跳动

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

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

主线程的原理

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

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

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

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

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

java 复制代码
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,它负责将消息放入队列中,然后由主线程依次处理。消息队列的机制确保了消息的有序性和及时性。

java 复制代码
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来将任务提交到主线程的消息队列中。

java 复制代码
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
    @Override
    public void run() {
        // 在主线程执行
    }
});

同步屏障

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

ini 复制代码
    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方法了

ini 复制代码
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()

scss 复制代码
    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 来实现的。以下是一段伪代码示例:

java 复制代码
// 创建一个 Looper,关联当前线程
Looper.prepare();
Looper loop = Looper.myLooper();

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

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

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

java 复制代码
    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: 每日一算法,由浅入深,欢迎加入一起共勉。

相关推荐
合作小小程序员小小店37 分钟前
web网页开发,在线%考试管理%系统,基于Idea,vscode,html,css,vue,java,maven,springboot,mysql
java·前端·系统架构·vue·intellij-idea·springboot
天天进步20151 小时前
CSS Grid与Flexbox:2025年响应式布局终极指南
前端·css
Boop_wu2 小时前
[Java EE] 计算机基础
java·服务器·前端
江上清风山间明月2 小时前
Android 系统超级实用的分析调试命令
android·内存·调试·dumpsys
Novlan12 小时前
TDesign UniApp 组件库来了
前端
用户47949283569152 小时前
React DevTools 组件名乱码?揭秘从开发到生产的代码变形记
前端·react.js
百锦再3 小时前
第12章 测试编写
android·java·开发语言·python·rust·go·erlang
顾安r3 小时前
11.8 脚本网页 打砖块max
服务器·前端·html·css3
倚栏听风雨3 小时前
typescript 方法前面加* 是什么意思
前端
狮子不白3 小时前
C#WEB 防重复提交控制
开发语言·前端·程序人生·c#