Android 异步消息机制 Handler - 系列 (2)从源码深入了解Handler

Android 异步消息机制 Handler - 系列 (2)从源码深入了解Handler,第1张

前几天笔者分享了对于Handler的一些浅显的认识与使用,相信想要在Android方向继续学习下去的同学们一定不会止步于上个阶段,那么笔者今天就和伙伴们一起进阶,去详细的了解Handler的工作流程以及它的四大成员的详细分析。如果没有阅读过上个阶段的伙伴想要去了解的话,可以到本站Android目录下查看。

注: 文中所有源码基于Android 10

Handler的运行流程

先上一张图解,该图片是从gityuan 大佬哪搞来的,实际上这就是整个Handler的工作流程了。先给出这张图让大家在整体上能有一些概念,然后我们再去细讲。

在上述过程发生之前,其实Handler机制还需要我们做一些准备工作,比如looper的初始化,Handler的创建以及looper的启动,接下来就是上述流程了。所以在了解上述流程之前,笔者想先和大家聊聊Handler 的前期准备工作。

Looper.prepaer()

如果我们在主线程中使用过Handler,就会知道,我们(开发者)并主动没有去调用它,但是这并不代表没有调用它,实际上是系统在启动应用Activity的时候就帮我们在ActivityThread的Main方法里初始化了looper以及启动了它。那么我们来看看prepare()具体是怎么实现的

那我们就进入到Looper.java的prepare()方法中看看吧。

//该方法在同一线程中只能调用一次否则会报错。
public static void prepare() {
prepare(true);
}

private static void prepare(boolean quitAllowed) {

    //如果我们是第二次调用,那么在sThreadLocal中肯定是存在该线程的副本了,那么会抛出错误。
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }

    //如果没有抛出错误那么我们就会新建一个looper存放在sThreadLocal中
    sThreadLocal.set(new Looper(quitAllowed));
}

我们会发现prepare()中的逻辑其实并不多,就是检查是否已经初始化,若没有则初始化一个并利用Looper类中维护的全局唯一的sThreadLocal将其存储起来。

static final ThreadLocal sThreadLocal = new ThreadLocal();
我们看到 sThreadLocal 是由static final修饰的,所以这个特性决定了它在全局中唯一。我们讲的每个线程都有唯一的looper也是跟ThreadLocal有很大的关系。所以我们先来了解一下ThreadLocal类

ThreadLocal

ThreadLocal 类在其内部实际上维护了一个类似hashmap的结构(一个ThreadLocalMap静态内部类),它是存储以ThreadLocal的弱引用为key,looper对象为value的单元。(具体大家自己去了解一下,笔者不在这里做太过详细的介绍),然后我们来看看mThreadLocal.set()方法

public void set(T value) {

        //获取当前线程id
        Thread t = Thread.currentThread();

        //通过线程id获取它内部的map(threadLocals)(Thread类中维护了线程唯一的ThreadLocalMap)
        ThreadLocalMap map = getMap(t);

        //如果线程内部的map已经存在则直接调用去set,如果没有则创建一个。
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

再来看看get()方法

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

通过上面源码的阅读以及笔者告诉大家Thread类 中也维护了一个ThreadLocalMap类型的threadlocals,相信大家肯定能知道为什么looper与线程的关系是一对一的。如果还不清楚,再理理思路,笔者在后续文章中也会给出答案。

然后我们会利用looper去创建Handler

Hanlder的构造

在旧版本中Handler是存在无参构造函数的,也就是默认去使用本线程的looper去创建,那么在新版本中该方法是被弃用了,因为存在着一些风险,具体想知道为什么,可转战官方api文档。

//传入looper
public Handler (Looper looper)

//传入looper以及回调
public Handler (Looper looper, 
Handler.Callback callback)

除了通过构造函数来创建handler,我们还可以通过继承的方式来创建,但是必须要注意,在利用该种方法进行创建的时候,必须使用静态内部类的方式。否则会造成内存泄露,至于为什么大家在看完整篇文章后也能知晓。答案留在下一篇文章。

 static class MyHandler extends Handler{
        public MyHandler(Looper looper){
                super(looper);
            }
            @Override
            public void handleMessage(Message msg){
                super.handleMessage(msg);
                // TODO(重写这个方法)
            }
        }
looper.loop()

在笔者看来这个方法才是整个Handler机制的核心,为什么这么说呢,因为整个消息的获取,分发都发生在这个方法中,并且主线程不能退出也是依赖这个方法。

话不多说,直接看源码(为了篇幅,笔者删除了部分代码,但是主要流程是有的):

 public static void loop() {
        final Looper me = myLooper();

        //如果looper没有初始化直接抛出错误
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }

        //将looper内部维护的消息队列赋值给 queue
        final MessageQueue queue = me.mQueue;

       
        for (;;) {

            //取消息,这里可能被阻塞
            Message msg = queue.next(); // might block
            //当调用了quit或者quitSafely时 next()方法会返回null。
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

        ...
            try {
                /通过msg的target(handler)属性调用分发消息
                msg.target.dispatchMessage(msg);
               
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
           
            //恢复调用者信息
            final long newIdent = Binder.clearCallingIdentity();
            //将消息放入回收池
            msg.recycleUnchecked();
        }
    }

接下来就可以进入到上述图中的环节了。

利用Hanlder发送消息

先看看官方给的一些API,方法有点多,但是都是殊途同归

//send***

//发送仅包含 what 值的消息。
public final boolean sendEmptyMessage (int what)

//发送仅包含 what 值的消息,以在特定时间传递。
public final boolean sendEmptyMessageAtTime (int what, 
long uptimeMillis)

//发送仅包含 what 值的消息,在指定的时间量过后发送。
public final boolean sendEmptyMessageDelayed (int what, long delayMillis)

//在当前时间之前的所有待处理消息之后,将消息推送到消息队列的末尾。它将在handleMessage(Message)附加到此处理程序的线程中接收。
public final boolean sendMessage (Message msg)   //msg不能为null

//在消息队列的前面将消息排入队列,以在消息循环的下一次迭代中处理。您将在 handleMessage(Message)附加到此处理程序的线程中收到它。 此方法仅用于非常特殊的情况——它很容易使消息队列饿死、导致排序问题或有其他意想不到的副作用。
public final boolean sendMessageAtFrontOfQueue (Message msg)

//在绝对时间(以毫秒为单位)之前所有未决消息之后将消息排入消息队列uptimeMillis. 时基为SystemClock.uptimeMillis()。 在深度睡眠中花费的时间会增加执行的额外延迟。您将在handleMessage(Message)附加到此处理程序的线程中收到它。
public boolean sendMessageAtTime (Message msg, 
long uptimeMillis)

//在之前的所有未决消息之后将消息排入消息队列(当前时间 + delayMillis)。您将在 handleMessage(Message)附加到此处理程序的线程中收到它。
public final boolean sendMessageDelayed (Message msg, 
long delayMillis)

//post***

//使 Runnable r 添加到消息队列中。可运行对象将在附加此处理程序的线程上运行。
public final boolean post (Runnable r)

//向实现 Runnable 的对象发布消息。使 Runnable r 在消息队列的下一次迭代中执行。可运行对象将在附加此处理程序的线程上运行。 此方法仅用于非常特殊的情况——它很容易使消息队列饿死、导致排序问题或有其他意想不到的副作用。
public final boolean postAtFrontOfQueue (Runnable r)

//使 Runnable r 被添加到消息队列中,在给定的特定时间运行uptimeMillis. 时基为SystemClock.uptimeMillis()。 在深度睡眠中花费的时间会增加执行的额外延迟。可运行对象将在附加此处理程序的线程上运行。
public final boolean postAtTime (Runnable r, 
long uptimeMillis)

//使 Runnable r 被添加到消息队列中,在给定的特定时间运行uptimeMillis. 时基为SystemClock.uptimeMillis()。 在深度睡眠中花费的时间会增加执行的额外延迟。可运行对象将在附加此处理程序的线程上运行。
public final boolean postAtTime (Runnable r, 
Object token, 
long uptimeMillis)

//使 Runnable r 添加到消息队列中,在经过指定的时间后运行。可运行对象将在附加此处理程序的线程上运行。 时基为SystemClock.uptimeMillis()。 在深度睡眠中花费的时间会增加执行的额外延迟。
public final boolean postDelayed (Runnable r, 
long delayMillis)

//使 Runnable r 添加到消息队列中,在经过指定的时间后运行。可运行对象将在附加此处理程序的线程上运行。 时基为SystemClock.uptimeMillis()。 在深度睡眠中花费的时间会增加执行的额外延迟。
public final boolean postDelayed (Runnable r, 
Object token, 
long delayMillis)

上面是从Android 官方文档中copy下来的,所以应该是很全了,但是它们其实最终都是调用的MessageQueue.enqueueMessage()进行的入队 *** 作。这里说一下 其实我们所说的消息队列实际上底层数据结构并不是队列,而是链表。至于为什么,大家可以好好想想。

上述函数参数中的Message也是Handler机制中很重要的一个成员,随后笔者会在后续文章中补充。

MassgeQueue.enqueueMessage()

当最终调用enqueueMessage方法后,会根据情况是否唤醒looper,具体的我们来看源码:

#MessageQueue
//为了方便大家看,笔者选取了类中部分全局变量
private long mPtr; // used by native code
private boolean mQuitting; //是否退出
private boolean mBlocked; //是否被阻塞

 boolean enqueueMessage(Message msg, long when) {
        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.");
        }

         //如果looper退出了,那么在这对消息进行回收
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;

            //如果队头为空,或者该消息是要立即执行的、它将被插入到队头 那么我们会根据线程是否被阻塞决定唤醒
            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 {
                //否则根据是否已经被阻塞 且 是否存在同步屏障  且 该消息是否异步消息  决定是否唤醒
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    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()方法,那么我们就继续看看next()方法中有些什么吧

MessageQueue.next()

#MessageQueue
//源码有点多,但是为了更好的理解,笔者就没做删减了,耐心看完咯

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.

        //其实在上个方法中我们就了解到了,如果loop退出了,那么mPtr就是0,如果是0我们就会在这里返回null
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;  //取到下个消息需要等待的时间
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

             阻塞 *** 作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
            nativePollOnce(ptr, nextPollTimeoutMillis); 

            //在这里利用循环加锁的机制加上上面的阻塞唤醒机制是解决了消息队列的并发访问的问题。
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;  //取消息队列头节点

                //如果消息队列不为空 且 msg的target属性为空(同步屏障)就跳过同步消息取异步消息。
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                 
                if (msg != null) {

                    //当前时间还未到下个消息的执行时间则为nextPollTimeoutMillis赋值。
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.

                        //否则取到msg引用的消息,并且为mBlocked赋值false不阻塞线程
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);

                        //设置该消息的状态,表示它被使用
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1; // 等于-1表示没有消息,需要一直等待被唤醒。
                }

                //正在退出,返回null
                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.

                //在第一次进入循环的时候,如果消息队列为空,或者第一个消息还取不到,那么就会执行IdleHandler
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }

                //如果没有idlehandler需要处理则继续阻塞线程 
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);

            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            //我们只会在第一次进入该循环的时候执行以下代码 ,执行完成后,重置pendingIdleHandlerCount为0.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.

            //因为我们可能在上面运行了闲置的handler 所以会有新消息加入到队列中,所以无需等待。
            nextPollTimeoutMillis = 0;
        }
    }

next()的过程就是上面这些了,还记得笔者在上面loop()方法中说的当调用消息队列的next()方法可能被阻塞吗,那么通过上面的源码阅读,笔者相信大家都能懂了,至于阻塞的原理,是调用了本地方法,那么涉及到native层的知识了,笔者会考虑在下一篇中给出答案。

那么直到这里,我们就已经获取到一个消息了,那么我们会返回到looper的loop方法中,不知道大家还记得loop中发生了什么吗?哈哈,因为篇幅确实也有点长哈,不记得可以翻上去看看,实际上我们获得消息后就会使用消息的target属性来进行分发方法,那么这个target就是发送该消息的Handler的引用了(疯狂暗示内存泄漏问题)。

Hanlder.dispatcheMessage()
 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {

            //这种情况就是我们利用postMessage发送的Runnable回调。
            handleCallback(msg);
        } else {

             //这种是我们使用构造方法传入的回调
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }

            //这种是我们使用继承的方式创建handler,那么我们在内部重写的方法。
            handleMessage(msg);
        }
    }

不要问为什么一上来就扔代码,由于不是下载的完整的系统包,笔者在几十个handler文件中才找到了真正的handler,所以真的是迫不及待想和大家分享了,上面就是分发的流程了,很简单。就不做过多的解释了哈。

那么到这其实整个Handler的流程我们就走完了,后续笔者还会更新有关Handler机制中各个角色中比较重要的,但是在流程中未提及的一些方法、知识等,但是也是需要大家能够学习和理解的。

参考 : gityuan

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/923823.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存