Android消息机制总结

深入解析Android消息机制,探讨Handler、Message、MessageQueue、Looper的工作原理,揭示子线程与主线程间通信机制,剖析Looper死循环为何不致应用卡死及Handler内存泄漏预防。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

常见问题

  1. 子线程为什么不能直接new Handler?
  2. 子线程真的不能更新UI吗?
  3. 主线程可以向子线程发送消息吗?
  4. 子线程中Toast,showDialog,的方法
  5. Looper死循环为什么不会导致应用卡死?
  6. Handler的内存泄漏如何避免?
  7. post方法为什么可以直接在主线程更新UI?

概述

如同四大组件一样,消息机制是Android学习的一道门槛,下面就个人理解进行总结记录一波。Handler是Android消息机制的上层接口,在我们日常使用过程中只需要和Handler交互即可。谈到消息机制,就不得不提Handler、Message、MessageQueue、Looper这四个重要对象,他们合力完成整个消息机制。通常情况下,我们会在主线程创建Handler的实例,开启子线程执行耗时任务,当耗时任务完成以后发送消息给主线程更新UI。其大概流程是Handler发送消息时将消息添加到消息队列MessageQueue(虽然叫队列,实际上是单链表,插入,删除更高效),通过消息轮询器Looper的looper()方法,将消息分发给Handler的handlerMessage方法进行处理。举个例子,就好比写信人把信装入信封,投放到邮筒,邮差小哥从邮桶中取出交给收信人,其角色如下:

  • Handler:发件人/收信人 执行具体操作/发送消息
  • Message:信封,包装消息的实体
  • MessageQueue:邮筒,消息队列,存放消息实体
  • Looper:邮差小哥,从消息队列中取出消息,并进行分发

Handler构造方法:

先从Handler构造方法说起,Handler的构造方法有6个,但最终都是调用同一个,代码如下:

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
		//获取当前线程的looper对象
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

不难发现,通过构造方法创建Handler的实例时,会通过myLooper()方法获取当前线程的looper对象,如果为空则会报异常,提示要调用Looper.prepare(),那为什么通常我们在主线程new Handler()时,没报异常呢?是因为在主线程中默认调用了Looper.prepareMainLooper();

Looper

//这个方法创建了一个Looper,并且将它作为主线程的Looper
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
               throw new IllegalStateException("The main Looper has already been prepared.");
         }
         //获取当前线程对应的Looper
         sMainLooper = myLooper();
     }
}

//获取当前线程的looper
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

在主线程中,也就是ActivityThread的main方法中,实例化了looper,但本质仍然是通过prepare()方法实现的,接下来看看prepare()方法到底做了什么?有一点需要注意的是,主线程的Looper和子线程的Looper细微区别,通过对比就可以发现,调用prepare时方法的参数不同,主线程创建Looper时prepare方法参数为false,表示Looper不可以退出,这很好理解,因为Android系统就是一个无限循环的消息机制,如果Looper退出,系统就无法正常运行了。而子线程的创建Looper时,prepare方法参数为true,表示子线程的Looper可以退出。

public static void prepare() {
    prepare(true);
}
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
         throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

可以看到,prepare首先会判断当前线程是否已经创建过Looper,若是创建过则会抛出异常信息。也就是说,一个线程只能对应一个Looper,也就是只能prepare一次。若是没有创建过,则会创建一个Looper放到ThreadLocal中。而ThreadLocal是按照线程信息储存数据的,获取时也是根据当前线程获取对应的数据。
为什么不能直接在子线程中new Handler呢?

因为主线程默认调用了Looper.prepare()和Looper.loop()方法,实例化了创建handler的必要条件looper对象。这样我们就可以直接在主线程通过new的方式创建Hanler的实例,在子线程给主线程发送消息。而子线程没有默认调用prepare方法,实例化Looper,故不能直接new Handler。Handler是不同线程之间通信的桥梁,并不局限于子线程给主线程发送消息,只要我们手动调用Looper.prepare()和Looper.loop()方法,就可以在子线程实例化Handler,并在主线程给子线程发送消息,甚至是子线程给子线程发送消息。

//Looper的构造方法
private Looper(boolean quitAllowed) {
	//实例化消息队列MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

当调用了prepare创建Looper之后,在ThreadLocal中就会有一个当前线程的Looper对象了。而在Looper的构造方法中,还实例化了一个消息队列MessageQueue,也就是说,每个Looper都对应的有一个消息队列(每个送信人即邮差小哥负责一个邮筒)。
  现在已经有了邮筒了,那么邮差小哥什么时候知道有信封被放在了邮筒中呢?当然是时刻守着邮筒啊,也就是Looper将会一直关注着MessageQueue,这在代码中是通过loop方法的一个死循环实现的。

public static void loop() {
	//获取当前线程的looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //获取looper对应的MessageQueue
    final MessageQueue queue = me.mQueue;
    ...
    for (;;) {
        //从MessageQueue中取消息,队列中没有消息时,会阻塞在这里
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        try {
        	//分发消息,取出消息以后调用 handler 的 dispatchMessage() 方法来处理消息
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        msg.recycleUnchecked();
    }
}

Looper 中的 loop()方法, 它的作用就是从消息队列MessageQueue 中不断地取消息, 然后将事件分发出去,最终调用的是msg.target.dispatchMessage(msg)来处理消息。那么问题来了主线程中loop()方法的死循环为什么不会造成应用的卡顿呢?
原因如下:

1.耗时操作本身并不会导致主线程卡死, 导致主线程卡死的真正原因是耗时操作之后的触屏操作, 没有在规定的时间内被分发。
2.Looper 中的 loop()方法, 他的作用就是从消息队列MessageQueue 中不断地取消息, 然后将消息分发出去。
补充:网上有很多博主说原因是因为 linux 内核的 epoll 模型, native 层会通过读写文件的方式来通知我们的主线程, 如果有事件就唤醒主线程, 如果没有就让主线程睡眠。 这种说法的正确性有待商榷, 如果说没有事件让主线程休眠是不会造成主线程卡死的原因, 那么有事件的时候, 在忙碌的时候不也是在死循环吗??那为什么忙碌的时候没有卡死呢?这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源

这不是本文重点,欲知详细分析请点击看原文分析
原文为什么Looper中的Loop()方法不能导致主线程卡死?

ThreadLocal

前面提到了Looper对象的存取是通过ThreadLocal实现的,一个线程只会有一个looper对象,那ThreadLocal是如何做到的呢?

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获得存储到的数据,对于其它线程来说则无法获取数据。原因是不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找对应的value值。很显然,不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同的线程中维护一套数据的副本并且彼此互不干扰。

ThreadLocal的set()源码分析:

//ThreadLocal.java
public void set(T value) {
    //1. 获取当前线程
    Thread t = Thread.currentThread();
    //2. 获取当前线程对应的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //3. map存在就进行存储
        map.set(this, value);
    else
        //4. 不存在就创建map并且存储
        createMap(t, value);
}
//ThreadLocal.java内部类: ThreadLocalMap
private void set(ThreadLocal<?> key, Object value) {
    //1. table为Entry数组
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    //2. 根据当前ThreadLocal获取到Hash key,并以此从table中查询出Entry
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        //3. 如果Entry的ThreadLocal与当前的ThreadLocal相同,则用新值覆盖e的value
        if (k == key) {
            e.value = value;
            return;
        }
        //4. Entry没有ThreadLocal则把当前ThreadLocal置入,并存储value
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //5. 没有查询到Entry,则新建Entry并且存储value
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
//ThreadLocal内部类ThreadLocalMap的静态内部类
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

ThreadLocal的get()源码分析:

public T get() {
    //1. 获取当前线程对应的ThreadLocalMap
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //2. 取出map中的对应该ThreadLocal的Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            //3. 获取到entry后返回其中的value
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //4. 没有ThreadLocalMap或者没有获取到ThreadLocal对应的Entry,返回规定数值
    return setInitialValue();
}
private T setInitialValue() {
    //1. value = null
    T value = initialValue();//返回null
    Thread t = Thread.currentThread();
    //2. 若不存在则新ThreadLocalMap, 在里面以threadlocal为key,value为值,存入entry
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
  1. 当前线程对应了一个ThreadLocalMap
  2. 当前线程的ThreadLocal对应一个Map中的Entry(存在table中)
  3. Entry中key会获取其对应的ThreadLocal, value就是存储的数值

MessageQueue

消息队列扮演着邮筒的角色,主要包含两个操作:插入和读取。读取操作本身伴随着删除操作,插入和读取对应的方法 分别为enqueueMessage和next,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除。尽管MessageQueue叫消息队列,但是它的内部实现并不是队列,而是通过一条单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势。
enqueueMessage的源码分析:

boolean enqueueMessage(Message msg, long when) {
    //这里的msg.target指的就是发送该条消息的handler
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }
    //同步锁
    synchronized (this) {
        
        ......
       
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;//消息队列头部结点
        boolean needWake;
        //p == null 表示当前消息队列为空;
        //when == 0 表示新消息立即执行;
        //when < p.when 表示新消息触发时间比消息队列中头部结点消息触发时间早
        //满足三者之一,则将新的消息置为头部结点,也就是插入链表的最前面,可以理解为把刚进入消息队列的消息
        //置为消息队列的头部结点
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            //插入消息到链表的中间:如果插入消息到链表头部的条件不具备,则遍历               
            //消息链表比较触发时间的早晚,然后将消息插入到消息链表的合适位置。接着
            //如果需要唤醒线程处理则调用C++中的nativeWake()函数.
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                //遍历MessageQueue,如果新消息触发时间早则跳出循环,并将新消息msg插入到p的前面
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

它的主要操作就是单链表的插入操作,往MessageQueue中存放消息。具体分析见代码中注释。
接下来是next方法:

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) { //当消息循环已经退出,则直接返回
        return null;
    }
    int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                //当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,则退出循环。
                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 {
                    // 获取一条消息,并返回
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    //设置消息的使用状态,即flags |= FLAG_IN_USE
                    msg.markInUse();
                    return msg;   //成功地获取MessageQueue中的下一条即将要执行的消息
                }
            } else {
                //没有消息
                nextPollTimeoutMillis = -1;
            }
            //消息正在退出,返回null
            if (mQuitting) {
                dispose();
                return null;
            }
            //当消息队列为空,或者是消息队列的第一个消息时
            if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                //没有idle handlers 需要运行,则循环并等待。
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        //只有第一次循环时,会运行idle handlers,执行完成后,重置pendingIdleHandlerCount为0.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; //去掉handler的引用
            boolean keep = false;
            try {
                keep = idler.queueIdle();  //idle时执行的方法
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        //重置idle handler个数为0,以保证不会再次重复运行
        pendingIdleHandlerCount = 0;
        //当调用一个空闲handler时,一个新message能够被分发,因此无需等待可以直接查询pending message.
        nextPollTimeoutMillis = 0;
    }
}

nativePollOnce是阻塞操作,其中nextPollTimeoutMillis代表下一个消息到来前,还需要等待的时长;当nextPollTimeoutMillis = -1时,表示消息队列中无消息,会一直等待下去。当处于空闲时,往往会执行IdleHandler中的方法。当nativePollOnce()返回后,next()从mMessages中提取一个消息。next()方法是一个无线循环的方法,如果消息队列中没有消息,那么next()方法会一直阻塞,当有新消息到来时,next会将这条消息返回同时也将这条消息从链表中删除。

接下来是从sendMessage方法或者是post方法到handleMessage,从源码角度分析整个消息机制流程,当我们发送消息时,无论采用sendMessage还是post,最总调用的sendMessageAtTime方法,其过程如下:
在这里插入图片描述

首先看sendMessage方法:

public final boolean sendMessage(Message msg)
    {
        Log.d(TAG, "sendMessage: " + Thread.currentThread().getName());
        return sendMessageDelayed(msg, 0);
    }

调用sendMessageDelayed:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

再调用sendMessageAtTime:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

再看看post方法:

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);//getPostMessage方法是两种发送消息的不同之处
    }

方法只有一句,内部实现和普通的sendMessage是一样的,但是只有一点不同,那就是 getPostMessage这个方法:

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

这个方法我们发现也是将我们传入的参数Runnable封装成了一个消息,只是这次是m.callback = r,刚才是msg.what=what,至于Message的这些属性就不看了,有兴趣可自行查看。

刚才我们发现两个方法后面都是调用了sendMessageAtTime:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

这个方法又调用了 enqueueMessage,看名字应该是把消息加入队列的意思,点进去看下:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

mAsynchronous这个异步有关的先不管,继续将参数传给了queue的enqueueMessage方法,至于那个msg的target的赋值,就是将Message的成员target(也就是Handler)赋值为我们实例化的handler,最总分发消息时,就是交给我们实例化的handler进行处理。现在继续进入MessageQueue类的enqueueMessage方法,前面已经分析过了,它的主要操作就是单链表的插入操作,往MessageQueue中存放消息。但是有放就有拿,这个消息怎样把它取出来呢?就是MessageQueue的 next()方法,前面已分析过,我们知道它是用来把消息取出来的就行了。不过这个方法是在什么地方调用的呢,不是在Handler中,我们找到了Looper这个关键人物,我叫他轮询器,专门负责从消息队列中拿消息,关键代码如下:

for (;;) {
     Message msg = queue.next(); // might block
     ...
     msg.target.dispatchMessage(msg);
     ...
     msg.recycleUnchecked();
}

简单明了,我们看到了我们刚才说的msg.target,刚才在Handler中赋值了msg.target=this,所以我们来看Handler中的dispatchMessage:

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {//callback在message的构造方法中初始化或者使用
                                  //handler.post(Runnable)时候才不为空
            handleCallback(msg);
        } else {
            if (mCallback != null) {//mCallback是一个Callback对象,通过无参的构造
            //方法创建出来的handler,该属性为null,此段不执行
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);//最终执行handleMessage方法
        }
    }
  1. msg的callback不为空,即post的Runnable不为空,调用handleCallback方法(message.callback.run())//优先级最高
  2. mCallback不为空,调用mCallback.handleMessage(msg) //优先级居中
  3. 最后如果其他都为空,执行Handler自身的 handleMessage(msg) 方法//优先级最低

再看看第一种情况下的handleCallback方法
Handler.java

private static void handleCallback(Message message) {
		//Message的成员变量Runnable callback;
        message.callback.run();
    }

为什么在post中传了个Runnable还是在主线程中可以更新UI?
如果有耐心看到这里,相信你应该明白了msg的callback应该已经想到是什么了,就是我们通过Handler.post(Runnable r)传入的Runnable的run方法,这里就要提提java基础了,直接调用线程的run方法相当于是在一个普通的类调用方法,还是在当前线程执行,并不会开启新的线程。只有执行了start方法时,才会开启新的线程。所以到了这里,我们解决了为什么在post中传了个Runnable还是在主线程中可以更新UI的问题。
未完待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值