Handler-源码分析

·  阅读 1929
1.简介

Handler 是一套 Android 消息传递机制,主要用于线程间通信。

为什要有Handler机制?

解决在子线程更新UI的问题

由于在Android机制中,为了保证UI操作是线程安全的,规定只允许在原始线程更新UI,但在实际开发中存在多个线程并发操作UI组件的情况,会导致线程不安全,所以采用Handler机制,当子线程需要操作UI组件时,通过Handler通知主线程,从而在主线程中更新UI。

2.介绍

Handler机制重要组成部分

  • Message:需要传递的消息,可以传递数据及对象;
  • MessageQueue:消息队列,但是它的内部实现并不是用的队列,而是通过单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优势。主要功能是向消息池投递消息(enqueueMessage)和和取走消息池的消息(next)
  • Handler:消息的真正处理者,主要功能是向消息队列发送消息(sendMessage)和处理对应的消息(handleMessage)
  • Looper:消息轮训器,重要功能是,不断的从消息队列中取出消息交与Handler处理 Handler流程介绍
发送消息 --> 添加消息到队列 --> 从队列中获取消息 --> 处理消息
复制代码

由Handler发送Message开始,将Message发送到MessageQueue中,由Looper不断的轮训去除MessageQueue中的Message,交给Handler处理 ,大致流程如下

WechatIMG36.png

源码解读

发送消息的方式主要有sendXXX()postXXX()两种,

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}
复制代码

handler.sendXXX()handler.postXXX()最终都会调用到sendMessageAtTime()方法。

public boolean sendMessageAtTime(@NonNull 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(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码

在这里将Hanlder本身赋值给Messagetarget,让后调用MessageQueueenqueueMessage(),将Message加入到MessageQueue的队列当中,接下来就到MessageQueue的enqueueMessage()方法当中去看。

boolean enqueueMessage(Message msg, long when) {
    ......
    
    synchronized (this) {
       ......

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // 当消息队列为空或者将要入队的消息(msg)的时间(when)在所有消息队列的消息最前面,则把msg插入到队头,最先执行
            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) {
                // 当p为空(遍历到最后一个了),或者新消息的时间在p的时间之前,就不用再遍历了,因为可以确定新消息要插在消息p前面了
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            // break跳出for循环后执行到这里,将msg插入到p前面,prev后面
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        ......
    }
    return true;
}
复制代码

Message类的实现可以看出它是个单链表,消息队列中的消息都是按照时间先后顺序链接起来的。

接下来就详细的分析一下上述的代码,首先我们看一下这个if (p == null || when == 0 || when < p.when) 判断:

  • p == null:我们可以从上面看到p 就是 mMessages;而mMessages 是在我们if判断里面赋值的,所以我们第一次进入时,p也就是mMessages为空,表示MessageQueue中没有添加过消息。
  • when == 0 :when是我们从Hanlder当中传过来的值,表示当前时间SystemClock.uptimeMillis()加上延迟时间delayMillis
  • when < p.when:判断新Message.when是否小于p.when

以上三个判断条件满足一个就会进入if代码块中,将新消息放在p消息的前面,当满足p==null时,即为队头,就是以下情况:

WechatIMG39.png 当满足p!=nullwhen == 0 || when < p.whentrue时,就是以下这种情况:

WechatIMG42.png 当以上三个条件不满足时,就会执行else的代码块,先来看一下下面这段代码的意思:

 Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                ......
            }
            // break跳出for循环后执行到这里,将msg插入到p前面,prev后面
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
复制代码

第一行代码中的P消息上在if代码块中添加的消息,说明指针指向P消息,第二行p消息指向下一个消息,指针小猴移动了一位。 从第二行代码可以看出,再次添加消息时,遍历消息都是从添加if代码块中的消息开始的,因为从if代码块中添加的消息要么就是第一个消息,要么就是when小于p消息的wnen的,也就是说可确保前面的消息都是时间有序递增的。而系统的时间是一直在增加的,所以enqueueMessage()方法的第二个参数when一直大于mMessageswhen,所以要把指针向后移一位,从后面一个消息开始比对。

WechatIMG40.png 以上两种都是在队头队尾添加的,再看一个从插入到队列中间的情况:

image.png 总结

  • 添加第一个消息时,将消息添加到消息队列的队头

  • 再次添加消息时,先判断待添加消息的whenp.when的大小关系,小于p.when,则把消息添加到p的前面,否则循环消息队列找出合适的插入位置

  • 如果遍历完整个消息队列都没有满足条件的位置,则把新消息插入到队尾

  • 消息队列的消息按照时间从先到后排序

取出消息

文章开始已经说了Looper是消息轮询器,Looper.loop**不断的从MessageQueue中取出Message,那么Looper.loop()是在哪里调用的呢?由于Android是由消息驱动的,一定是在app的入口ActivityThreadmain()方法中调用了,下面看下Looper.prepare()的创建和Looper.loop()轮训.

Looper.prepare()

对于app的入口ActivityThread调用的是Looper.prepareMainLooper()实际上也是调用了Looper.prepare()。下面一步步来看一下:

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}
复制代码
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));
}
复制代码
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
复制代码

可以看到在Looper.prepareMainLooper()中调用了Looper.prepare(),在Looper.prepare()中首先判断当前ThreadLocal中是否已经存在Lopper对象,如果存在则抛出异常,一个线程对应一个一个Looer对象。如果不存在Looper对象则 new一个出来,在Looper的构造方法中创建了MessageQueue

ThreadLocal保证了一个线程只有一个Looper对象,一个Looper对象只有一个MessageQueue。看一下ThreadLocal是如何做到的。回头看下sThreadLocal.get()方法。

public T get() {//这里的泛型指的是Looper
    Thread t = Thread.currentThread();
    //获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //获取当前线程中的Looper
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //如果map==null||looper==null执行以下方法
    return setInitialValue();
}
复制代码
private T setInitialValue() {
  //初始化value(Looper)当然为空,在后面ThreadLocal.set()方法会赋上具体的值
    T value = initialValue();
    //获取当前线程ThradLocalMap
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    //添加进map
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
复制代码
protected T initialValue() {
    return null;
}
复制代码

首先获取当前线程,根据当前线程获取到ThreadLocalMap对象,ThreadLocalMapThreadLocal中的一个内部类,表明一个Thread只存在一个ThreadLocalMap,然后ThreadLocal.get()方法中判断ThreadLocalMap是否为空、ThreadLocalMap中是否存在Looper对象,如果存在则返回该Looper对象,如果ThreadLocalMap为空或者ThreadLocalMap中不存在Looper对象,则创建ThreadLocalMap,赋值一个初始的空对象,等待ThreadLocal.set()方法赋具体的值。

Looper.loop()

public static void loop() {
    //获取到Looper对象
    final Looper me = myLooper();
    
    ......

    me.mInLoop = true;
    //根据Looper对象获取MessageQueue
    final MessageQueue queue = me.mQueue; 
    
     ......

    for (;;) {
    // 从MessageQueue中取出消息,无消息时可能会阻塞
        Message msg = queue.next(); 
        
        ......
        
        try {
            //交与Handler处理消息
            msg.target.dispatchMessage(msg);
            ......
        } catch (Exception exception) {
           ......
            throw exception;
        } finally {
           ......
        }
       
         ......
       //回收消息
        msg.recycleUnchecked();
    }
}
复制代码

首先获取Looper对象,根据Looper对象获取到MessageQueue队列,不断的调用next()方法来获取Message,交与Handler去处理,最后回收该消息。

下面看一下MessageQueue.next()做了些什么

// MessageQueue.java 中的 next 方法源码
Message next() {
        // 判断 native 层的 MessageQueue 对象有没有正常创建
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        // 消息执行需要等待的时间
        int nextPollTimeoutMillis = 0;
        for (;;) {
            // 执行 native 层的消息延迟等待,调 next 方法第一次不会进来
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                // 获取当前系统的时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                ...
                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;
                        // 标记为已在使用状态
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 如果队列里面没有消息,等待时间是 -1
                    nextPollTimeoutMillis = -1;
                }
                // 有没有空闲的 IdleHandler 需要执行,一般我们没关注这个功能
                // 后面内容有专门解释,这里目前分析是 == 0 ,跳出
                if (pendingIdleHandlerCount <= 0) {
                    mBlocked = true;
                    continue;
                }
                ...
            }
            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }
复制代码

通过源码分析我们发现消息的处理过程,是通过当前消息的执行时间与当前系统时间做比较,如果小于等于当前系统时间则立即返回执行该消息,如果大于当前系统时间则调用 nativePollOnce 方法去延迟等待被唤醒,当消息队列里面为空时则设置等待的时间为 -1。

总结

  • 调用Looper.loop()之前必须调用Looper.prepare()
  • 消息队列中没有消息的时候会堵塞在next()方法处让CPU休眠,不会ANR
  • 取到了消息后要判断当前时间和消息执行直接的先后关系,没到执行时间则继续休眠等待,否则就直接处理消息
  • 处理消息前,会将该消息从消息队列中去除

处理消息

Looper.loop()中调用 msg.target.dispatchMessage(msg)处理消息, msg.targetHanlder,在enqueueMessage()中赋值的。

下面看下dispatchMessage()

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
复制代码

msg.callback是在Message.obtain()方法中赋值的

public static Message obtain(Handler h, Runnable callback) {
    Message m = obtain();
    m.target = h;
    m.callback = callback;

    return m;
}
复制代码

如果msg.callback不为空的话,就会执行handleCallback(msg)方法,就是去处理这个Runnable对象

private static void handleCallback(Message message) {
    message.callback.run();
}
复制代码

如果msg.callback为空的话,就会判断Handler中的回调是否为空,这个callback对象是在Handler创建时选择参数,如果不为空的情况下,就会执行callback中的handleMessage()方法

public Handler(@Nullable Callback callback, boolean async) {
     .......
    mCallback = callback;
   
}
复制代码

最后执行到我们常用的handleMessage(msg),这个方法就是一个空的模版方法,交由我们去实现具体的逻辑。

public void handleMessage(@NonNull Message msg) {
}
复制代码

至此Handler整个流程执行完毕。

拓展

Message创建

Message创建的方法主要有两种

  • 通过构造方法new对象
  • 通过obtain()从池子里去Message对象
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}
复制代码

通过obtain()从池子里去Message对象,这里采用了享元设计模式,从池子里取出Message对象复用,避免了不必要的对象创建,减少了内存开销,题高了资源的利用率.因此个人认为使用这种方法创建对象比较好。

分类:
Android
标签:
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改