多线程系列(五)Handler源码详细解析(上)

前言

今天为大家叙述Android中消息处理机制Handler,本篇文章内容属于Android范畴,所以只懂Java不懂Android的同学可以选择绕道。

1 Handler基本使用

1.1 Handler概念

Handler是Android中内置的一种消息处理的机制,那什么又是消息处理机制呢?一般来说在进行异步操作的时候会用到消息处理机制,打个比方:现在有个需求需要从网络上请求一张图片,然后将这张图片显示在屏幕上,在Android中网络请求需要放在子线程,而图片显示又必须在主线程中进行,这样就会产生一种冲突,怎样解决这种冲突呢?前两节我们讲的线程间通信是时候派上用场了,没错,通过线程间通信可以解决这个问题,但是,我每一次加载图片都要噼里啪啦自己去写线程间通信,一旦有特殊需求还要对其改东改西,是不是也太繁琐了?Google爸爸早就想到这一点了,所以给我们提供了Handler来解决这个问题,当从网络请求到数据后通过Handler将数据发送给主线程,主线程拿到数据后作出相应的操作即可,非常的方便。

1.2 Handler的基本使用

说完了Handler基本概念再来说一下Handler基本用法:

首先在需要接收消息的线程中创建一个Handler,此处我们在主线程中创建

private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    //obj即为另一个线程发送来的数据
                    Object obj = msg.obj;
                    Log.i("myHandler",  obj+"  threadName "+Thread.currentThread().getName());
                    break;
            }
        }
    };

发送消息

 new Thread(new Runnable() {
            @Override
            public void run() {
                //可进行耗时操作
                Message message = new Message();
                message.what = 1;
                message.obj= 1;
                handler.sendMessage(message);
            }
        }).start();

打印结果

I/myHandler: 消息  threadName main

最后接收数据是在主线程中进行的,完美实现线程间通信,操作也非常简单。

Handler还有很多用法,本篇文章侧重点为Hanlder源码部分,所以对Hnadler基本使用就不一一列举,感兴趣的同学可以自行了解。

2 Handler源码分析

2.1 Handler机制组成部分

Android中的Handler机制是由四部分组成的,分别是:

  • Handler:用来发送消息和处理消息
  • Message:消息的载体
  • MessageQueue:存储消息的队列
  • Lopper:用来衔接MessageQueue和Handler

下面我对这四部分源码逐个进行解析

2.2 Message

Message承载消息形式

Message是一个消息载体,在实际线程间通信中需要传递的数据类型五花八门,Android也考虑到了这一点,所以为我们提供了能够承载任意格式数据的Message。
消息类型大概有以下几种:

  • int what:一般用于设置消息码,用于区分不同的消息
  • Object obj:可以承载任意类型的对象
  • Bundle data:以键值对的形式进行存储数据

能够承载的类型非常丰富,基本可以满足我们实际开发需求

Message创建方式

在Message中可以通过直接new Message()来获取一个Message对象,但Message还给我们提供了另一种创建方式:通过调用obtain()
我们来看一下obtain()源码

  /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
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缓存

obtain()注释中提到了消息池的概念,也就是说通过obtain()获取Message可以对消息进行复用,那么它内部是这样对消息进行复用的呢?我们再来分析一下obtain()里面的内容:

熟悉数据结构的同学可能已经看出来,内部缓存是通过一个单向链表来实现的,

  • 首先取出链表的第一个节点也就是头节点mPool(Message m = sPool;)
  • 取出后将mPool指向下一节点 (sPool = m.next;)
  • 把取出的节点后继(next)置为空 ( m.next = null;)
  • 将flags置为0,用于更改缓存标识( m.flags = 0; // clear in-use flag)
  • 将链表的长度sPoolSize减1(sPoolSize--;)

下面我再来结合一张草图给大家分析Message从缓存取出过程:


message.PNG

取消息之前内部缓存为链表A,MessageA为头节点,执行取出操作后把MessageA取出,将其next置为null,然后将MessageB赋值给头结点sPool,链表B为取出Message后的内部缓存结构,整个流程就是这样,很简单。

说完了从缓存中取消息再来说一下将消息加入缓存
加入缓存是通过调用recycle()方法进行的,来看一下源代码:

public void recycle() {
        //判断当前是否处于可回收状态
        if (isInUse()) {
            //判断当前SDK版本
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }
//当前是否处于可回收状态
boolean isInUse() {
        return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
 }
//版本判断
public static void updateCheckRecycle(int targetSdkVersion) {
        if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
            gCheckRecycle = false;
        }
 }

首先判断当前是否处于可回收状态(Message正在被MessageQueue中或者正在被Looper处理是不可以进行回收的),如果不可回收再判断当前版本,版本大于等于LOLLIPOP就抛出异常,小于LOLLIPOP不做处理直接return。

如果当前处于可回收状态,执行recycleUnchecked()方法进行回收操作

在这不要纠结这个版本判断,Google只是在后来认为如果消息处于不可回收状态你偏要回收,这时就抛一个异常给你,让你知道你这样做是错误的,一般来说尽量不要手动调用recycle()进行回收,因为Looper会在内部在合适的时机调用recycleUnchecked()来对消息进行回收。

我们来看一下recycleUnchecked()源码:

void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            //判断缓存链表是否达到最大长度
            if (sPoolSize < MAX_POOL_SIZE) {
                //将后继指向之前的头节点
                next = sPool;
                //将当前消息节点置为头节点
                sPool = this;
                //链表长度加1
                sPoolSize++;
            }
        }
    }

首先将消息中的各种属性清空,然后再判断当前缓存链表是否小于最大缓存长度MAX_POOL_SIZE(默认为50),如果小于最大缓存长度,将当前消息节点置为头节点,最后把链表长度加1。
Message的源码差不多就是这个样子,可以直接通过new Message()获取一个消息载体,但如果频繁发送消息,建议使用obtain()获取Message。

2.3 Looper

在写这一小节之前我纠结了很久,因为Handler、MessageQueue、Looper三者关系犹如一组三角恋,互相都有着说不清的关系,到底先写哪一个呢?网络上大部分文章都是首先分析MessageQueue,我觉得这样不是很妥当,因为MessageQueue是依附于Looper存在的,并且Looper是 MessageQueue与Handler的桥梁,所以我决定首先分析Looper的源码。
讲源码之前先给分析一张图(该图摘自这篇文章),来排除大家的一些疑点

Handler机制流程图.png

首先Handler通过sendMessage()发送一个消息到消息队列MessageQueue中,然后Looper会从消息队列中取消息,如果取到就讲消息叫由Handler处理,整个流程就是这样。下满我们开始源码部分

首先需要说明一点,一个线程只允许有一个Looper,而主线程中的Looper不需要我们自己创建,Android已经帮我们创建。下面来看一下Android程序的入口ActitivtyThread的main()方法的伪代码(只截取了部分与Looper有关的代码):

 public static void main(String[] args) {
        //为主线程创建一个Looper
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        //开启一个Looper
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

首先Looper调用了自身的静态方法prepareMainLooper(),我们看一下其源代码:

/**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        //为当前线程创建一个Looper
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //获取当前线程也就是主线程的Looper
            sMainLooper = myLooper();
        }
    }

注释大致意思:为主线程创建一个Looper,这个方法由Android系统调用,开发者永远不要手动调用这个方法。

在叙述这段代码之前我先为大家普及一段概念,前面我们也说到,Looper在每个线程是独立存在的,Looper的线程独立是怎么实现的呢?其实是通过ThreadLocal进行实现的,那什么是ThreadLocal呢?在这我跟大家简单描述一下,ThreadLocal有两个重要方法,一个set(obj)、一个get(),调用set(obj)方法可以往当前线程存入一个对象obj,而get可以获取到当前线程的obj对象,举个例子:

现在有线程A和线程B,线程A通过ThreadLocal调用了set(objA),线程B通过ThreadLocal调用了set(objB),在线程A调用ThreadLocal的get()方法会获取到objA,在线程B调用ThreadLocal的get()方法会获取到objB,就是这么简单。

讲完了线程独立我们接着往下分析,先来看prepare(false)源码:

 private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //创建一个Looper对象存入到当前线程中
        sThreadLocal.set(new Looper(quitAllowed));
    }

  private Looper(boolean quitAllowed) {
        //创建消息队列MessageQueue
        mQueue = new MessageQueue(quitAllowed);
        //获取当前线程
        mThread = Thread.currentThread();
    }

通过调用prepare()方法可以往当前线程存于一个Looper对象,如果Looper已在当前线程存在,再次调用会抛出异常。我们可以看到,消息队列MessageQueue是跟随Looper一同创建的,这样也就保证了Looper和MessageQueue线程中一对一的关系。

为主线程初始化Looper后紧接着会调用myLooper()获取到当前线程的Looper

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

最后将myLooper()返回的Looper对象赋值给sMainLooper主线程Looper就创建完毕。
如果我们需要在子线程中创建Looper步骤基本类似,可以调用prepare()方法初始化Looper,调用myLooper()获取到Looper即可。

前面我们有提到,Looper是Handler和MessageQueue的桥梁,负责从MessageQqueue中取消消息然后交由Handler处理,那么它是怎样实现这种操作的呢?
在ActivityThread的main()方法中调用了Looper的一个方法loop(),那这个方法是干什么用的呢?我们来看一下源代码:

 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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.");
        }
        //获取到当前线程的消息队列
        final MessageQueue queue = me.mQueue;

        // 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();
        //开启一个无限循环
        for (;;) {
            //从消息队列中取出消息,为阻塞式方法。
            Message msg = queue.next(); // might block
            //如果通过next()取出的结果为null代表消息队列已经退出则跳出循环
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // 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);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                //将消息交给Handler处理
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            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();
        }
    }

整体的流程就是,通过调用myLooper()方法获取到当前线程的Looper对象,然后通过me.mQueue获取到对应的消息队列MessageQueue,获取完毕后开启一个无限的for(;;)循环,每次循环都会从MessageQueue中取出一个消息,注意next()方法为阻塞式方法,如果取出的消息为空就代表消息队列已经结束跳出循环结束方法,如果取到了消息,通过msg.target获取到发送消息的Handler,单后将消息发送给Handler进行处理,最后会调用Message的recycleUnchecked()对消息进行回收。
处理消息流程差不多就是这个样子。在Looper中也给我们提供了停止Looper的方法,通过调用其quit() 方法

public void quit() {
        mQueue.quit(false);
    }

其实最终调用的还是MessageQueue的quit(),具体怎么退出我再MessageQueue中再详细讲述。

疑点

通过上面的叙述我们知道了一个Android程序启动的时候会在主线程创建一个Looper对象,并且会调用Looper的loop()方法,而loop()方法又是一个没有结束标记的死循环,按照我们常规的思维方式执行了loop()方法后主线程会被一直阻塞出现卡死现象,但事实上并不是这样的,我们Activity的启动View的渲染都是在主线程中进行的,说明主线程没有卡死,关于这点Android是怎么处理的呢?这牵扯到了Android Binder IPC和Linux pipe/epoll机制,由于我学艺不精就不在这误人子弟了,如果真的想了解可以去阅读这篇文章,这里会给你一个满意的答案。

本来是准备一篇文章把消息处理机制写完,写到这发现内容是有点多,由于篇幅原因MessageQueue和Handler源码我会在下篇文章进行描述,下篇文章,不见不散。

你可能感兴趣的:(多线程系列(五)Handler源码详细解析(上))