源码解析(一) Handler

前言:
这里还是需要提前说一下的,分析源码可能是一个比较枯燥的过程,它需要我们可以静下心来仔细的翻阅Android系统内部逻辑的实现。 而这篇文章那怕我自认为已经做到了尽可能的精简摘录,到最后我发现代码量依旧不算少,我也只能去努力的去把逻辑捋的比较顺一些,尽最大可能的提高了阅读性。
所以如果有人看到这篇文章的话,我还是希望你按照顺序阅读下去,相信最后一定会有所收获的。
最后希望大家都可以将一个枯燥的过程转变成一件开心的事!

一、Handler的概念

Handler 其实是 Android 中一个比较重要的知识点,它是 Android 在执行多线程并发处理时针对线程安全的一种防范机制,同时它也可以认为是一种在线程间的消息通信机制。因为在 Android 中,默认是支持多线程并发处理的,这样虽然拥有充分利用处理器资源、提高工作效率、缩短程序执行时间等等好处,但是它也不可避免的会造成以下一些缺陷:

  • 线程安全多线程同时访问一块内存,很容易造成并发问题。
  • 线程耗时在 Android 中,规定界面的渲染机制是有时间限制的,每秒内最优展示帧数为60帧,也就是说,最好的时间是在16毫秒内完成界面的一次刷新,当这个时间无限扩大后仍然没有完成界面的绘制,那么就会导致ANR现象。

所以为了避免这些问题,尤其是针对第二点,Android 规定了在非UI线程,也就是子线程中,不允许去更新 UI 组件,如果特殊情况下需要在子线程执行时更新 UI 组件,那么就必须用到 Handler 或者 runonUiThread() 等等方式切换到主线程中才能去执行对 UI 组件的处理。下面用张图来解释一下。
转至菜鸟教程
(转至菜鸟窝,如有侵权,请联系我,马上删除)。

二、Handler的简单执行流程

在解析Handler之前,首先需要先认识几个关键对象,因为只有先了解清楚了它们之间的关系,才能有助于我们去解析Handler的源码。

  • ActivityThread它其实就是主线程,也是我们常说的UI线程,在进程启动时就帮我们启动好了,主要是负责处理UI组件相关的事件,比如触发,修改等
  • Message消息本质对象,是一个封装类型,内部存在一些可以携带实际意义的参数,实现了Parcelable接口,可以进行进程间通信
  • MessageQueue消息队列,以一种先进先出的顺序管理着Message的单向链表
  • Looper可以把它当做一个工具类,它初始化时会自动创建一个与之对应的MessageQueue对象,通过不断地轮训从其中取出Message处理分发给对应的Handler,这里需要注意的概念:就是一个Thread对应一个Looper,一个Looper又对应一个MessageQueue,但是它们可以关联不同的Handler,也就是说可以发送给很多Handler,这是个一对多的关系。

好了,了解了上面几种对象的关系,甚至是它们跟 Handler 之间的关系后,我们就可以先大致描述一下 Handler 的简单处理流程了。因为在分析任何一份源码时,我更倾向于结论先行。

当我们的子线程想要刷新UI时,可以在主线程中新建一个Handler对象,通过这个对象向主线程发送Message,而我们发送的Message会先到主线程对应MessageQueue中等待,然后由Looper按照先进先出的顺序取出后发送给Handler,再根据Message中的what属性,在handlerMessage方法中进行不同的处理。

转至菜鸟教程
(转至菜鸟窝,如有侵权,请联系我,马上删除)。

三、Handler的源码分析

看过上面的结论,我们也就对Handler机制有了一个初略的理解,至于如何使用它我这边就不再演示Handler如何使用了,也没有那个必要,接下来我们还是直接从底层源码分析这些关系是如何实现的。

1、Handler中Message的发送逻辑

首先,我们分析源码要找到一个切入点,这样有助于我们梳理逻辑,那么针对Handler,我这里选择先从 Handler 中发送 Message 的入口作为突破口。其中涉及到了一些 MessageQueue 的知识点,它其实是一个单向链表结构的数据组,这里先记住结论就好,有助于你理解下面的代码。那么我们先看看 Message 到底是什么样的。

public final class Message implements Parcelable {
*************************省略代码*************************
private static Message sPool; //Message 的静态缓存类
public int what; //消息的识别标识
public int arg1; //消息的可携带参数
public int arg2; //消息的可携带参数
public Object obj; //消息的可携带参数
Bundle data; //消息的可携带参数
Handler target; //发送消息的Handler实例缓存
long when;  //消息想要传递的时间
Message next; //在跟MessageQueue关联时,指向Queue的上一个节点

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();
}     
*************************省略代码*************************
}

这里是我摘录出来,并非完整代码,下面的源码块都是这样,主要是为了让大家分析起来更加清晰明了。

其实 Message 就是一个实现了 Parcelable 接口的封装类,它内部有很多变量,我这里只是找出了几个经常使用的参数值标注出来。

另外我还单独取出来了一个方法,就是上面的 obtain(),它是一个Message的创建方法,这里为什么要单独拿出来讲一下呢,主要就是想提醒大家以后在使用 Handler 的时候,如果是在主线程中,最好还是利用这个方法构建 Message 实例,因为在 AndroidHandler 的使用是很频繁的,如果是不断的对 Message 进行创建和销毁,那么难免会造成很大的资源损耗,这里 Android 提供了一种缓存机制,来创建 Message ,这样就解决了这个问题。

接着我们看看发送 Message 的逻辑实现

public class Handler {
//发送一个空消息
public final boolean sendEmptyMessage(int what){
        //这里调用内部方法,延时为0
        return sendEmptyMessageDelayed(what, 0);
}

//当我们发送一个空方法时,对外对内 都提供的方法
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        //创建一个Message,所以说,那怕我们发送一个空消息时,其实在Handler内部也帮我们自动创建了一个message对象
        //这里大家还需要注意的一点就是,Android其实推荐我们使用obtain()方法创建Message对象,这点上面刚刚说过了
        Message msg = Message.obtain();
        msg.what = what;
        //调用内部方法
        return sendMessageDelayed(msg, delayMillis);
}

//这个方法大家应该很熟悉,就是提供给外部的延迟方法,我们发送的消息其实在Handler内部可以统一看成延迟消息处理
//只不过有些消息的延迟时间为0而已
public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

//这里就到重点了啊
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        //这个Queue是全局变量Looper中获取到的,代码为mQueue = mLooper.mQueue,是一个静态变量,也就是MessageQueue
        //它对应的是Looper,而不是Handler,这点需要注意,一个Handler可以对应很多Looper,但一个Looper只能有一个Queue
        MessageQueue queue = mQueue;
        //重点,这个方法就是Message入队的核心方法
        return enqueueMessage(queue, msg, uptimeMillis);
}
}

可以看到上面的这几个方法已经包含了我们在使用 Handler 时会涉及到的大部分操作了。当我们调用 Handler 发送消息的时候,内部其实都会一步一步的走到 sendMessageAtTine() 方法中,如果调用空方法,那么就帮我们创建一个延迟时间为零的 Message 放入到队列中,如果调用其他方法,我们就需要自己创建带有延迟时间和参数的 Message,接着就是无论任何操作,到最后都会调用 enqueueMessage() 这个入队方法,将消息添加到 Queue

/**Handler code**/
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //将发送该Message的Handler保存到自身,到时候当Looper在轮训取出时,获取实例,保证不会出错
        //所以记住一点每个Message中都带有自身对应的Handler(同步消息除外,下面会讲)
        msg.target = this;
        //入队列
        return queue.enqueueMessage(msg, uptimeMillis);
}

/**MessageQueue code**/
boolean enqueueMessage(Message msg, long when) {
        //这里处理的普通消息必须是包含Handler的,下面会提及一个同步屏障消息,它其中不包含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) {
            if (mQuitting) {
                //如果Queue已经退出,则将消息recycler
                msg.recycle();
                return false;
            }
            //标记使用状态
            msg.markInUse();
            //设置延迟时间
            msg.when = when;
            //mMessage是一个全局变量,代表MessageQueue的头节点
            Message p = mMessages;
            //头节点为空 或者 等待时间小于头节点  那么都放在队列的前端
            if (p == null || when == 0 || when < p.when) {
                //给msg的上一个节点赋值,这里将头节点设置为下一个,当前msg为头节点
                msg.next = p;
                //给头节点赋值
                mMessages = msg;
            } else {
                Message prev;
                for (;;) {
                    //比较时间,将消息按照时间进行排列
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    } 
                }
                //将消息插入合适的位置
                msg.next = p;
                //这里也就是一个排队的逻辑
                prev.next = msg;
            }
        }
        return true;
}

可以看到,逻辑还是相当简单的,就是一个按照时间排序的过程,消息队列是使用 单向链表 作为数据存储的,这样也就意味着它 插队 时特别方便,而这里有个 next 变量的赋值,可以把它理解为一个指针,指向下一个消息,主要是为了在以后循环取出时更加快捷同时减少误差。

值得注意的一点是内部采用了一个死循环来排列消息在 MessageQueue 的顺序的,如果没有消息过来,那么该方法将进入阻塞状态。但是这个阻塞并不会造成 ANR 现象,主要的原因它封装到了Native方法中了,我放到下面讲取出逻辑的地方详细的给大家分析一下(会有惊喜哦!)

这里的排序其实可以分为两种情况
第一种如果队列中没有消息或者当前进入的消息比消息队列中头部的消息等待时间短,那么就放在消息队列的头部
第二种反之,循环遍历消息队列,根据when时间值,判断先后顺序,把当前进入的消息放入合适的位置

而对于上面出现的同步消息和异步消息这里先不多做解释,只需要记住一点就是,他们两个的区别就是如果存在同步屏障时,异步消息还可以执行,而同步消息则会被过滤掉。而什么叫做同步屏障消息,也放在下面它出现的时候讲解。

到这里我们通过 HandlerMessage 的插入到 MessageQueue 的逻辑差不多就分析完了,大家跟着看下来是不是感觉有些简单了,其实本来就不是很难,源码就是这样,不能畏如猛虎,那样我们在技术之路上将难有建树。

2、Handler中Message的接受逻辑

总所周知,在 java 中,程序的入口肯定是一个 main(),而在 Android 中,先不论谷歌和甲骨文的官司之战,我们还是不得不承认 Android 这门语言在应用层实现上是基于 java 实现的,那么它的入口其实同样也是一个 main(),值得注意的是这个方法并不在我们通常使用的界面化窗口 Activity 或者 Application 中,而是在 ActivityThread 这个类里面,它也就是我们常说的 主线程,主要作用就是根据 ActivityManagerService 的要求负责调度四大组件的,这里我们直接看看它的入口 main()方法,如下:

 public static void main(String[] args) {
      *******省略代码*********
      //初始化Looper的方法,同时它会将这个实例赋值到ThreadLocal这个线程本地缓存类中
      Looper.prepareMainLooper();
      //实例化自身,在程序启动是就创建好了
      ActivityThread thread = new ActivityThread();
      //获取ActivityManagerService接口,同时设置ApplicationThread的接口,完成底层通信
      //这里其实就是Activity启动的关键起始位置,但是由于是讲Handler的,只简单提示一下
      //可以对应着Application的attachBaseCotext()方法
      thread.attach(false);
      //这是一个MainHandler,主要是用于管理Activity的生命周期的
      if (sMainThreadHandler == null) {
          sMainThreadHandler = thread.getHandler();
      }
      //开始轮训取出Message
      Looper.loop();
     *******省略代码*********
    }

我们可以看到在 ActivityThread 类中的 main() 中,主线程会先调用 Looper.prepareMainLooper(),完成 Looper 的初始化,然后调用 Looper.loop() 开始轮训 Message。这一段代码很重要,它说明了一个问题,一个为什么我们在主线程中使用 Handler 时不需要启动 Looper 的原因,这是因为 ActivityThread (主线程)已经帮我们是实例化好了。

下面我直接摘录了Looper的方法,补充上面注释内容

//这里是一个final类,不允许被继承,内部必要方法都是通过静态关键字提供出去
public final class Looper {
//Thread本地缓存类
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//实例化MainLooper
 public static void prepareMainLooper() {
        //缓存到本地ThreadLocal内,这里传递的是false
        prepare(false);
        //同步代码块
        synchronized (Looper.class) {
        //一个线程只能有一个Looper,如果当前线程中的looper不为空,就会报错
        if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
        }
 }
}
//获取Looper对象
private static void prepare(boolean quitAllowed) {
        //再次解释了一个线程只能有一个Looper
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //缓存到当前线程中
        sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
        //从本地缓存中获取
        return sThreadLocal.get();
}
//构造方法
private Looper(boolean quitAllowed) {
        //直接创建对应的MessageQueue对象
        mQueue = new MessageQueue(quitAllowed);
        //获取当前所在线程,判断是那种类型的线程
        mThread = Thread.currentThread();
}

我上面的注释相对而言已经很清晰了,在主线程中会自动调用 Looper 提供的 prepareMainLooper() ,这个方法主要是 ActivityThreadSystemServer 两个类才会调用,然后内部又调用了 prepare(),这里是私有化的,而在 Looper() 内部还有一个同样的命名的方法。

 public static void prepare() {
        prepare(true);
 }

这个方法才是提供给外部调用的,相信大家已经看到它两个的区别了,主要是私有化的方法中可以传递一个参数,而提供给外部的没有,而这个 quitAllowed 参数的含义就是当前 MessageQueue 是否可以退出,显而易见,主线程调用时传入的是一个 true,代表的意思是主线程的 MessageQueue 不可以中途退出,而外部所有调用传入的都是一个 false 代表的是子线程的可以中途退出。

紧接着它会从 ThreadLocal 中获取 Looper,如果没有则自己实例化一个。而在 Looper 的构造方法中,我们也可以看到它同步了一个 MessageQueue 对象,一个 Looper 只能对应一个 MessageQueue,而且是在其构造方法时就自动创建好了。

下面,是 Looper 如何从 MessageQueue 中获取 Message 的。

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        for (;;) {
            // 循环轮训,从MessageQueue中获取Message
            Message msg = queue.next(); 
            try {
                //这里的target就是一个发送Message的Handler实例,在构建消息时缓存起来的
                msg.target.dispatchMessage(msg);
            } finally {
            }
            msg.recycleUnchecked();
        }
    }
}

首先是判断是否初始化,没有就会抛出异常,这一点跟上面主线程有些不同,如果我们需要在子线程使用 Handler 时因为没有与之匹配的Looper,所以这里就我们主动调用prepare() 创建一个Looper对象,然后通过它从MessageQueue中不停的遍历 Message,接着就利用传递消息时缓存在 Message 中的 Handler(msg.target = this) 将消息传递出去,而接收者就是我们在 new Handler() 时重写的 handleMessage() 的方法了。

这里大家需要注意的到,它同样开启的是一个死循环,只要有 Message 传递进来,不管何时,它都会将消息传递出去.,如果为空时,该方法将进入阻塞状态。但是这个阻塞也不会造成 ANR 现象,至于原因,我上面有说到,说它是因为封装到了Native方法中,但是由于小弟能力有限,实在是没理清楚里面的实现,这里只能说声抱歉了。(之前说要讲原理的话,你们就当放屁就好,实在是研究了半天,没看出来一个所以然来,所以是不是一个小惊喜呢,笑哭1)

继续往下走,MessageQueue 中取出 Messagenext() 和 构造方法摘录

public final class MessageQueue {
   MessageQueue(boolean quitAllowed) {
        //这里上面有讲过,主线程创建的MessageQueue可以退出,其他的不可以
        mQuitAllowed = quitAllowed;
        //这里是调用了Native的方法去完成MessageQueue的初始化工作,很厉害的操作,我看的也是一知半解,这里就不献丑了
        //有兴趣的同学可以自行去领悟一下
        mPtr = nativeInit();
    }
}


Message next() {
        //当前Queue是否已经停止
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        //下个消息需要执行的时间,为延迟消息做准备
        int nextPollTimeoutMillis = 0;
        //同样是一个死循环
        for (;;) {
            //同步代码块
            synchronized (this) {
                //这里涉及到了一个同步消息屏障的概念,其实在Android中Message主要分两种,同步和异步
                //只有设置了同步屏障才会出现差异
                Message prevMsg = null;
                //将头节点值赋值给msg,成员变成替换成局部变量,保证安全性
                Message msg = mMessages;
                //同步消息的判断是根据msg中是否含有发送自身的Handler
                if (msg != null && msg.target == null) {
                    //如果是同步消息,将msg.next指向离的最近的一个异步消息,也就是说将同步消息给剔除出去
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    //msg链表头部的when字段,如果不是当前时间,则被认为是延迟消息
                    if (now < msg.when) {
                        //是一个延迟消息,需要设置一个定时时间,延迟后再处理
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        if (prevMsg != null) {
                            //有同步屏障,将msg的节点往后走一位,同时赋值给prevmMsg当前节点
                            prevMsg.next = msg.next;
                        } else {
                            //无同步屏障,即msg就是头结点,直接获取值
                            mMessages = msg.next;
                        }
                        //置空
                        msg.next = null;
                        //标记使用状态
                        msg.markInUse();
                        //返回msg
                        return msg;
                    }
                }
                // 处理完所有挂起的消息后,quit消息
                if (mQuitting) {
                    dispose();
                    //退出队列,同时loop()方法也会结束
                    return null;
                }
            }
}

这里算是 Handler 比较核心一点的地方了,也就是获取 Message 的位置,它主要就是从 MessageQueue 中利用一个 next() 对其中 Message 遍历获取,这里我们有本事的小伙伴已经可以看出 MessageQueue 虽然叫做消息队列,其实它内部是一个链表结构实现的,逻辑相对而言也比较简单,注释的也比较清晰了,就是从 MessageQueue 中不断轮训 Message,首先从链表的头结点开始,同时里面有判断延迟消息的方法,如果是延迟任务,就给它设置一个定时时间,另外有一点需要注意的就是它里面涉及到了一个同步消息屏障的概念,这里可以理解为 Message 有两种状态,一种带有同步屏障,一种不带有,而什么是屏障消息,那就需要分析一下 MessageQueue 中的 postSyncBarrier() 方法了,它就是设置屏障消息的方法,下面给出了摘录:

private int postSyncBarrier(long when) {
        // 注册一个新的同步屏障令牌
        // 这里并不需要唤醒队列,因为屏障的目的是阻止队列,主要是提供一种优先级
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            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) { 
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

上面的代码我就不添加多余的注释了,跟普通消息差不多一致,唯一不同的地方就是它没有设置一个 Handler,而我们在发送异步 Message 的时候通常调用的 enqueueMessage() 是有将发送的 Handler 对象实例缓存到 Message 中去的,但是这里并没有。

如果从代码层面上讲,同步屏障令牌就是一个不携带 HandlerMessage,但是由于设置了同步屏障后,next() 又会在处理时将掉所有的同步信息屏蔽,只允许返回异步消息,换句简单的话来形容,就是在设置了同步屏障之后,Handler 可以实现一种只处理异步消息,过滤掉同步消息的逻辑,这样在处理时,相对异步消息,有一个发送消息,但是不会进行具体处理的差异,这样就会提高传输速度,所以同步屏障令牌的出现主要是可以提升我们在 UI 控件上的刷新机制,而我在翻找 Android 源码时,也就看到了 ViewPootImpl 中有使用这个屏障设置。

至此,通过上面的这么多代码,我们梳理了一遍 Handler 的内部逻辑,也许其中没有涉及到一些Native的实现,纯属能力有限,就不献丑了,我们先做几个总结好了。

3、Handler机制的总结

一、Handler内部需要跟Looper结合使用,一个线程中只能存在一个Looper,对应一个MessageQueue,但是Handler可以对应很多个Looper,它们是一对多的关系。

二、Handler线程切换的实现,主要就是在子线程中可以通过一个主线程实例化的Handler对象发送Message,然后将其存储在MessageQueue中,接着通过Looper.loop()将消息轮训出来,最后还是通过发送消息时缓存的Handler再回传给本身所在线程,完成线程切换。

三、在子线程中可以实例化Handler,而它跟主线程中使用Handler的区别如下:

主线程:在主线程(UI线程)实例化Handle,其实是可以直接使用的,而不需要初始化Looper,因为在主线程启动时,就为自己创建好了一个LooperMessageQueue对象了,,所以可以直接发送消息,然后Main.Looper就会处理。

子线程:而在子线程实例化Handler,由于当前线程并没有初始化Looper,这样就需要手动创建一个looper对象了,方法为Looper.prepare(),这时Looper的构造方法时会自动创建一个MessageQueue对象,最后调用loop()方法,开始轮训,所以在子线程中使用Handler的步骤可以分为三步:

  • 调用Looper.prepare(),创建一个Looper对象,同时它的构造方法中,会自动调用方法创建一个MessageQueue对象。
  • 创建一个Handler对象,重写HandlerMessage()方法,接受Looper传递过来的Message对象。
  • 手动调用Looper.loop()方法,启动looper,管理MessageQueue
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值