handler解析(2) -Handler源码解析

目录

基础了解:

相关概念解释

整体流程图:

源码解析

Looper

总结:

sendMessage

总结:

ThreadLocal


基础了解:

Handler是一套 Android 消息传递机制,主要用于线程间通信。实际上handler其实就是主线程在起了一个子线程,子线程运行并生成Message,Looper获取message并传递给Handler,Handler逐个获取子线程中的Message,在这个机制下中包括了Looper、MessageQueue,ThreadLocal等

相关概念解释


Handler、Message、Message Queue、Looper

Message :代表一个行为what或者一串动作Runnable, 每一个消息在加入消息队列时,都有明确的目标Handler

ThreadLocal: 线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。ThreadLocal的作用是提供线程内的局部变量TLS,这种变量在线程的生命周期内起作用,每一个线程有他自己所属的值(线程隔离)

MessageQueue (C层与Java层都有实现) :以队列的形式对外提供插入和删除的工作, 其内部结构是以双向链表的形式存储消息的

Looper (C层与Java层都有实现) :Looper是循环的意思,它负责从消息队列中循环的取出消息然后把消息交给Handler处理

Handler :消息的真正处理者, 具备获取消息、发送消息、处理消息、移除消息等功能

整体流程图:

handler解析(2) -Handler源码解析_第1张图片

源码解析

分析源码:API 31

Looper

首先从new Handler点进去分析

handler解析(2) -Handler源码解析_第2张图片

handler解析(2) -Handler源码解析_第3张图片

 可以看到,handler的构造方法里先判断当前创建的handler是否为static的,如果不是会弹log,

The following Handler class should be static or leaks might occur: ,这块后面在解释为啥会这么设置

同时声明了需要个looper,然后这个looper是Looper.myLooper中获取的,然后如果looper如果为空的话,则会抛出异常:

Can't create handler inside thread " + Thread.currentThread()
        + " that has not called Looper.prepare()

可以看出这个looper还是挺重要的,然后发现消息队列也是在looper中声明的,那么 我们就来看这个looper是怎么获取的,点

Looper.myLooper()方法进去看

handler解析(2) -Handler源码解析_第4张图片

发现是sThreadLocal中获取的 ,然后全局搜这块的实现

handler解析(2) -Handler源码解析_第5张图片

handler解析(2) -Handler源码解析_第6张图片

发现是在prepare(boolean)中赋值的,这块主要是创建一个looper,然后再looper的构造方法中创建了消息队列message queue,最后添加到sThreadLocal中

同时发现这个set方法有点熟悉呀,点进去看,我们发现

handler解析(2) -Handler源码解析_第7张图片

 主要是通过ThreadLocalMap的set值实现的,这块是只进行一次set值,从而保证了一个looper的存在。

同时回到了之前的prepare()方法中,我们ctrl看看,发现有个方法特别熟悉

prepareMainLooper()

handler解析(2) -Handler源码解析_第8张图片

点进去看发现这块已经在

handler解析(2) -Handler源码解析_第9张图片 ActivitThread中调用了,这块之前通过其他大佬了解到,其实当我们点击了app图标后,根据启动流程会执行到ActivityThread中,这块也是通俗意义上说的主线程,所以说如果是在主线程中使用handler的话是不需要在调用Looper.loop()方法的,因为已经创建好了。

 同时,我们注意到ActivityThread中的另外一个方法

handler解析(2) -Handler源码解析_第10张图片

点击进去看,发现 

handler解析(2) -Handler源码解析_第11张图片

handler解析(2) -Handler源码解析_第12张图片


    /**
     * Poll and deliver single message, return true if the outer loop should continue.
     */
    @SuppressWarnings("AndroidFrameworkBinderIdentity")
    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;
        }

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " "
                    + msg.callback + ": " + msg.what);
        }
        // Make sure the observer won't change while processing a transaction.
        final Observer observer = sObserver;

        final long traceTag = me.mTraceTag;
        long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
        long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
        if (thresholdOverride > 0) {
            slowDispatchThresholdMs = thresholdOverride;
            slowDeliveryThresholdMs = thresholdOverride;
        }
        final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
        final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

        final boolean needStartTime = logSlowDelivery || logSlowDispatch;
        final boolean needEndTime = logSlowDispatch;

        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }

        final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
        final long dispatchEnd;
        Object token = null;
        if (observer != null) {
            token = observer.messageDispatchStarting();
        }
        long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
        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);
            }
        }
        if (logSlowDelivery) {
            if (me.mSlowDeliveryDetected) {
                if ((dispatchStart - msg.when) <= 10) {
                    Slog.w(TAG, "Drained");
                    me.mSlowDeliveryDetected = false;
                }
            } else {
                if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                        msg)) {
                    // Once we write a slow delivery log, suppress until the queue drains.
                    me.mSlowDeliveryDetected = true;
                }
            }
        }
        if (logSlowDispatch) {
            showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
        }

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();

        return true;
    }

根据注释发现这个方法是在当前线程中允许一个message queue进行for死循环从消息队列中取出消息,其中next方法会在下面分析,然后分发给msg.target中的dispatchMessage方法(其中msg.target是handler发送消息的时候赋值上去的,将在下面会说到),其中msg.target对应的就是handler,然后dispatchMessage源码为:

handler解析(2) -Handler源码解析_第13张图片

检查是否有由Runnable封装的消息,如果有,首先处理;其次处理的是mCallback,mCallback是什么?mCallback是Callback的对象,Callback是Handler中的一个内部接口,这个接口的作用是,当你实例化一个Handler对象的时候,可以避免不去实现Handler的handleMessage方法。

    /**
     * Callback interface you can use when instantiating a Handler to avoid
     * having to implement your own subclass of Handler.
     *
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public interface Callback {
        public boolean handleMessage(Message msg);
    }

从消息的分发可以看到,如果返回了true,那么handler中的handleMessge方法是不会被执行的,如果返回false或者mCallback没有被赋值,那么就会回调Handlerd的handleMessge,这就是一个Message的分发流程。

总结:

1.一个线程中只能有一个looper,这块是由ThreadLocal决定的,因为ThreadLocal中包含了一个ThreadLocalMap,在调用looper.prepare时将looper对象set进去ThreadLocal中,其次修饰符是final static表示唯一的,然后我们在perpare那也发现,它会在代码的开头判断sThreadLocal.get是否为空,其中sThreadLocal就是根据key获取对应的value,这块如果为空的话,就直接抛异常了

2.UI线程,就是主线程是不需要在调用looper.prepare跟looper.loop()的,因为这2个在ActivityThread中的main方法中已经调用过了,其中looper.prepare调用的为looper.prepareMainLooper()

3.looper.prepare方法主要做的事就是赋值looper对象到ThreadLocal中,其中looper对象实例化时,创建了消息队列message queue,Looper.loop方法主要做的事就是调用message queue.next方法循环拿消息,如果有消息的话,则将消息进行分发到msg.target上对应的handler.dispatchMessage中,其中handler.dispatchMessage就是熟悉的handlerMessage

sendMessage

其实这块我们常见的handler的那些操作,本质上都是调用sendMessage的,只是换了个说法而已,常见的handler操作handler.sendMessage(msg)、handler.sendEmptyMessage(1)

handler.postDelay()、handler.post(),下面我们一个个点进去看

1.handler.sendMessage(msg)

handler解析(2) -Handler源码解析_第14张图片

 handler解析(2) -Handler源码解析_第15张图片

 handler解析(2) -Handler源码解析_第16张图片

 2、sendEmptyMessage

handler解析(2) -Handler源码解析_第17张图片

handler解析(2) -Handler源码解析_第18张图片

handler解析(2) -Handler源码解析_第19张图片 handler解析(2) -Handler源码解析_第20张图片

 3.handler.postDelay(Runable,time)

handler解析(2) -Handler源码解析_第21张图片

 handler解析(2) -Handler源码解析_第22张图片

handler解析(2) -Handler源码解析_第23张图片

handler解析(2) -Handler源码解析_第24张图片

 4.handler.post()

handler解析(2) -Handler源码解析_第25张图片

。是不是觉得跟postDelay一样,Runable通过getPostMessage方法,将传入的Runable变为 message的callback方法

handler解析(2) -Handler源码解析_第26张图片

handler解析(2) -Handler源码解析_第27张图片

 可以发现,这几个handler的方法,都会调用sendsendMessageDelayed,然后调用sendMessageAtTime,最后调用enqueueMessage,进行消息处理,同时我们发现sendMessageAtTime中会设定个时间SystemClock.uptimeMillis(),这块时间为手机开机后系统非深度休眠时间,而不是手机时间。所以手机上修改当前时间是不会影响Message执行的。一般我们也可以通过这个值来获取手机开机多久。

然后我们来看enqueueMessage,其他那些looper、message queue已经在handler初始化的时候获取到了,其中在handler初始化的时候,通过looper.prepare已经创建好了

handler解析(2) -Handler源码解析_第28张图片

 这块是跳转到message queue的enqueueMessage方法中

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    //保证多线程入队先加锁
    synchronized (this) {
        //....
        msg.markInUse(); //标记正在使用
        msg.when = when; // when 属性
        Message p = mMessages; //拿到链表头部的消息
        boolean needWake;
        // 满足以下3种情况之一就把msg插入到链表头部
        //1.队列为null
        //2.当前时间没有延迟0
        //3.插入的时间比链表的头节点的when时间早
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; //如果处于阻塞状态,需要唤醒
        } else {
            //唤醒标识 ,
            //4.如果p != null且msg并不是最早触发的,就在链表中找一个位置把msg插进去
            //5.如果处于阻塞状态,并且链表头部是一个同步屏障(target为null的Message),并且插入消息是最早的异步消息,需要唤醒(   //队列为空,或者队列头消息还未到执行时间,且当前消息待执行时间小于队列头消息,此时才需要唤醒)
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                //当遍历到队尾、或者是  msg的时间比当前时间更早 
                if (p == null || when < p.when) {
                    break;
                }
                //发现了异步消息的存在,不需要唤醒
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            //单链表、插入msg信息
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }
      // 如果looper阻塞/休眠中,则唤醒looper循环机制处理消息
        if (needWake) {
            nativeWake(mPtr);//唤醒
        }
    }
    return true;
}

可以看到MessageQueue queue是通过链表形式对Message 进行存储,并通过when 的大小对 Message 进行排序。

其中,遍历插入过程如图:
遍历队列,当某个数据的时间戳优先级低于插入数据时,把数据插入;否则把数据放在队列尾部。

handler解析(2) -Handler源码解析_第29张图片
 

 
注意
Handler可以无限插入数据,没有大小限制。

然后我们回到之前分析过的looper中的loop方法


    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    @SuppressWarnings("AndroidFrameworkBinderIdentity")
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }

        me.mInLoop = true;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        me.mSlowDeliveryDetected = false;

        for (;;) {
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }
@SuppressWarnings("AndroidFrameworkBinderIdentity")
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {
    // 从Looper中取出MessageQueue进行轮询获取消息msg
    Message msg = me.mQueue.next(); 
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }
    //....
    try {
        // Message.target 就是对应的Handler.dispatchMessage回调消息
        msg.target.dispatchMessage(msg);
    }
    //....
    //回收消息
    msg.recycleUnchecked();

    return true;
}

 核心就是这个next方法了,这个方法主要是将存在message queue中的消息取出来的

@UnsupportedAppUsage
    Message next() {
        // 如果消息循环已经退出并被处理,请返回此处。
        // 如果应用程序尝试退出后不支持的循环程序,则会发生这种情况。
        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判断是否要阻塞
           // 阻塞方法,主要是通过 native 层的 epoll 监听文件描述符的写入事件来实现的。
           // 如果 nextPollTimeoutMillis = -1,一直阻塞不会超时。
          // 如果 nextPollTimeoutMillis = 0,不会阻塞,立即返回。
          // 如果 nextPollTimeoutMillis > 0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。
            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) {//队列中拿到的消息不为null
                    if (now < msg.when) {
                        // 下一条消息尚未准备好。 设置超时以使其在准备就绪时醒来。
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 正常返回处理
                        ...
                } else {
                    // 队列中没有消息,标记阻塞looper循环进入休眠
                    nextPollTimeoutMillis = -1;
                }

                // 现在已处理所有挂起的消息,处理退出消息。
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // 空闲句柄仅在队列为空或将来要处理队列中的第一条消息(可能是屏障)时才运行。
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                ...
            }

            ...

            // 将空闲处理程序计数重置为0,这样我们就不会再次运行它们。
            pendingIdleHandlerCount = 0;

            // 在调用空闲处理程序时,可能已经传递了一条新消息,
            //因此返回并再次查找未处理消息,而无需等待。
            nextPollTimeoutMillis = 0;
        }
    }

 然后找到后返回loopOnce,将message分发给msg.target就是handler,然后就是如果有消息的话,则将消息进行分发到msg.target上对应的handler.dispatchMessage中,其中handler.dispatchMessage就是熟悉的handlerMessage,然后一切做完后会调用    msg.recycleUnchecked()进行消息的回收

具体流程如下:

总结:

1.handler.sendMessage、handler.sendEmptyMessage、handler.post、handler.postDelay本质上都是调用handler.sendsendMessageDelayed,然后调用sendMessageAtTime,最后调用enqueueMessage,进行添加消息处理

2.sendMessageAtTime中会设定个时间SystemClock.uptimeMillis(),这块时间为手机开机后系统非深度休眠时间,而不是手机时间。所以手机上修改当前时间是不会影响Message执行的。一般我们也可以通过这个值来获取手机开机多久

3.MessageQueue queue是通过链表形式对Message 进行存储,并通过when 的大小对 Message 进行排序,其中链表头的延迟时间小,尾部延迟时间最大

4.Looper属于某个线程,而MessageQueue存储在Looper中,所以MessageQueue通过Looper特定的线程上关联,而Handler在构造中又与LooperMessageQueue相互关联,通过Handler发送消息的时候,消息就会被插入到Handler关联的MessageQueue中,而Looper会不断的轮询消息,从MessageQueue中取出消息给相应的Handler处理,所有最终通过Handler发送的消息就会被执行到Looper所在的线程上,这就是 Handler线程切换的原理,无论发送消息Handler对象处于什么线程,最终处理消息都是在Looper所在的线程。

5.handler 阻塞、唤醒

阻塞条件:

handler解析(2) -Handler源码解析_第30张图片

分析MessageQueue的next函数,发生阻塞只会存在以上两种情况

  1. 队列中消息个数为0,且没有可处理的IdleHandler,此时会一直阻塞
  2. 队列中消息个数不为0,但是队列头消息还未到执行时间,且没有IdleHandler要执行,此时会阻塞时间n,n表示队列头消息距离执行的时间

以上两种阻塞情况,mBlock都为true.只有这两种情况需要唤醒。

一言以蔽之,没有IdleHandle要执行,且队列中没有立即要可执行的消息时,会阻塞

唤醒条件:

  1. 队列为空,或者队列头消息还未到执行时间,且当前消息待执行时间小于队列头消息,此时才需要唤醒。一言以蔽之,也就是当前消息插入到队列头部时,才需要唤醒。

  2. mBlocked为true,也就是没有Idle消息要处理,且当前队列头消息时同步屏障消息,且当前消息时异步消息,此时需要立即处理,因为该异步消息对响应及时性要求比较高。

其实关于这个还涉及到一个面试题:“为啥looper.loop内部消息队列死循环不会导致应用卡死?”通过这个总结就很清晰了吧,

这里涉及线程,先说说说进程/线程,进程:每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。

线程:线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片。

有了这么准备,再说说死循环问题:

对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。

真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

ThreadLocal

handler解析(2) -Handler源码解析_第31张图片

 handler解析(2) -Handler源码解析_第32张图片

 通过get和set方法不难发现,其中频繁出现了一个ThreadLocalMap对象,这个变量跟map有点类似,是按键值对的方式对数据进行存储的,其中

key:指的当前ThreadLocal变量

value:T ,当前要存储的值

然后在点getMap方法看ThreadLocalMap是如何获取的

handler解析(2) -Handler源码解析_第33张图片

然后我们发现原来每个线程的threadLocals就是ThreadLocalMap来着,然后这个变量存储着ThreadLocal和对应的保存对象,这样的话,在不同的线程,访问同一个ThreadLocal对象,但是获取到的值是不一样的 ,这样就相对于用一个map存储所有线程的方式,会好很多了,因为那样的话管理也很混乱,每个线程有联系的话,也容易造成内存泄露

参考文章:

Handler源码分析 - 简书

Handler源码解析_Pioneer_Chang的博客-CSDN博客

Handler源码解析_handler messagequeue_醉饮千觞不知愁的博客-CSDN博客

Handler阻塞和唤醒条件 - 掘金

你可能感兴趣的:(android进阶,源码解析,android,android,studio,handler)