Handler消息机制超强源码解析

Handler消息机制在Android中占据重要的地位,主要使用场景就是线程间通讯。当我们在子线程执行完耗时操作后可以使用Handler通知UI线程去更新UI。对于一个有追求的开发者来说,我们不仅要会使用Handler,更要去深入探究其中的原理。本篇文章就来详细分析Handler的源码并从源码的角度来分析使用过程中经常出现的问题。

Handler消息机制的核心

在Handler机制中有三个重要的类:

  • 处理器 类(Handler)
  • 消息队列 类(MessageQueue)
  • 循环器 类(Looper)

我们来详细分析一下每个类的作用

Handler

Handler类主要作用是发送消息和处理消息。在Handler类当中有两个常用的构造方法

//方式1 无参构造
Handler handler = new Handler();

//方式2 根据已有线程的Looper对象构造Handler
Handler handler = new Handler(Looper.myLooper());

分别来看一下这两种方式的构造函数都做了什么事情,先看方式1,无参构造最终会调用下面的方法:

public Handler(Callback callback, boolean async) {
        
        //1.获取创建Handler的线程的Looper对象
        mLooper = Looper.myLooper();

        //2.如果创建Handler的线程没有Looper对象,直接抛出异常
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        
        //3.将Looper对象中MessageQueue对象赋值给Handler的成员mQueue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

可以看到在通过方式1来创建Handler对象的时候,首先会去获取创建Handler的线程的Looper对象,如果获取到的Looper对象为null,也就是说创建Handler的线程没有创建过Looper对象,那么就会抛出异常不允许创建Handler对象。如果不为null就会执行到注释3处的代码。平时我们在主线程通过方式1创建Handler的时候并没有创建过Looper对象,那为什么没有报错?因为主线程的Looper对象在App启动的时候就已经由系统创建完成,所以我们在主线程创建Handler对象的时候并不需要显式的去创建Looper对象。但是一旦我们在子线程通过方式1创建Handler对象,系统就会抛出异常。这是因为子线程的Looper的对象必须由开发者调用Looper.prepare()手动创建,然后才能去构造Handler对象。创建完Handler对象之后还要调用Looper.loop()开启消息循环,否则事件无法发送出去。

从构造函数可以看出,Handler对象持有一个Looper对象和一个MessageQueue对象,Looper对象也持有一个MessageQueue对象

再来分析一下方式2,经过层层调用最终会进入这个方法里面:

public Handler(Looper looper, Callback callback, boolean async) {
        //将开发者指定的Looper对象赋值给Handler的成员
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

这个方法并没有什么复杂的逻辑,只是进行简单的赋值操作。通过这种方式创建的Handler对象,可以指定一个已经存在的Looper对象,目的是为了让Handler在指定的线程中处理消息。这里需要注意的是,Handler的handleMessage()方法运行的线程与创建Looper的的线程是一致的。如果传入的Looper对象是子线程创建的,那么即使Handler对象是在主线程创建的,handleMessage()方法依旧会运行在子线程。

MessageQueue

MessageQueue是用来存放消息的消息队列,使用Handler发送的消息都会先存放到MessageQueue中等待被处理。MessageQueue从名字上看像是一个队列,但实际上内部是用链表实现。这一点面试的时候经常会被问到,所以需要注意。之所以使用链表,是因为存放消息和取出消息都会涉及频繁的插入、删除,对于插入、删除操作来说,链表的性能更高。

MessageQueue中有两个比较重要的方法:enqueueMessage()和next(),分别是添加消息和取出消息。先来分析一下enqueueMessage(),这个方法的主要功能是将消息插入到链表的合适位置:

 boolean enqueueMessage(Message msg, long when) {

        ...

        synchronized (this) {
            
        ...

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            //是否需要唤醒
            boolean needWake;
            //1.将新消息插入链表头部
            if (p == null || when == 0 || when < p.when) {
                
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;

                //2.根据新消息等待处理的时间,将新消息插入链表中合适位置
                //新消息等待处理的时间越小,插入的位置越靠近链表头部
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p;
                prev.next = msg;
            }

           //唤醒队列
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

 

注释1:当链表为空或者接收的的消息需要立即处理或者新消息等待处理的时间比链表头部的消息等待处理的时间要短就会将新消息插入到链表头部。

注释2:死循环,根据新消息等待处理的时间将消息插入到合适的位置

然后再来看一下next()方法,这个方法是用来从队列中取出消息:

Message next() {
        
        final long ptr = mPtr;
        //mPtr == 0说明消息队列被释放了
        if (ptr == 0) {
            return null;
        }
        // -1 only during first iteration
        int pendingIdleHandlerCount = -1; 
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            //1.阻塞操作,等待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) {
                    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;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                //如果消息队列正在处于退出状态返回null,调用dispose();释放该消息队列
                if (mQuitting) {
                    dispose();
                    return null;
                }

                
                .....

        }
    }

主要逻辑就是通过循环的方式根据等待消息处理的时间来取出消息,如果msg不为null,并且当前已经等待的时间间隔没有达到需要处理消息的时间间隔就会设置下一次查询消息需要等待的时长,否则就取出消息。

注释1是一个native方法,如果没有消息需要取出或者新的消息被插入就会通过nativePollOnce()方法阻塞nextPollTimeoutMillis的时间。在前面的enqueueMessage()方法中有一个唤醒队列的操作,唤醒的就是被nativePollOnce()阻塞的线程。这么做的好处就是可以节约cpu资源,如果没有插入新的消息或者取出队列中的消息,线程就会陷入睡眠,不再占用cpu资源,等到需要的时候再去唤醒线程。这个点面试的时候经常会被问到,所以需要特别留意。

Looper

Looper是一个消息轮训器,它负责创建消息队列MessageQueue和不断的从MessageQueue中取出消息并进行分发。每个线程只允许创建一个Looper。前面分析过想要创建Handler必须先创建Looper对象。通过调用Looper.prepare()就能创建一个Looper对象并与当前线程进行绑定。

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


    //最终会调用这个方法
    private static void prepare(boolean quitAllowed) {
        //1.如果当前线程已经存在一个Looper对象会抛出异常
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //2.new一个Looper对象,并且存进ThreadLocal中 完成与当前线程的绑定
        sThreadLocal.set(new Looper(quitAllowed));
    }

其中ThreadLocal的作用是不同的线程拥有该线程独立的变量,同名对象不会被受到不同线程间相互使用出现异常的情况。它能够保证每个线程都有一个独立的变量,并且不同线程之间的变量不会互相影响。对ThreadLocal不了解的同学可以自己查查资料。在Looper.prepare()中一旦一个Looper对象被保存到ThreadLocal中,就意味着这个Looper对象与当前线程完成了绑定,并且不同线程的Looper对象不会互相影响。

从注释2可以看到Looper对象是通过new方式创建的。一起来看看new Looper(quitAllowed)做了什么事情:

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

Looper对象在创建时候同时会创建MessageQueue消息队列,从这里可以看出Looper持有一个MessageQueue对象。

 

Looper对象创建完成之后再来看看是如何取出消息并进行分发的:

public static void loop() {
        //从当前线程中取出Looper
        final Looper me = myLooper();
        //如果Looper对象为null抛出异常    
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //从Looper中取出MessageQueue消息队列 可能会阻塞 
        final MessageQueue queue = me.mQueue;

       
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        //死循环从MessageQueue消息队列中取出消息并进行分发
        for (;;) {
            //1.从MessageQueue消息队列取出消息
            Message msg = queue.next();
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ...

            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                //2.取出msg后分发给handler进行处理
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
           

            ...

            msg.recycleUnchecked();
        }
    }

通过调用Looper.loop()就能将消息队列中的消息分发给对应的Handler对象,Looper.loop()是一个死循环。

在注释1中通过调用MessageQueue的next()方法取出需要分发的消息msg,这个方法前面已经分析过。

在注释2中,msg.target是一个Handler对象,也就是说最终会调用Handler的dispatchMessage()方法完成消息的分发。这个方法我们在后面分析。

简单的总结一下:一个线程只能存在一个Looper对象和一个MessageQueue对象,但是可以创建多个Handler对象。创建Looper对象的时会在Looper的构造方法中创建MessageQueue对象。Handler持有Looper,Handler和Looper又会持有MessageQueue。

Handler发送消息处理消息流程分析

分析完Handler消息机制的三个核心类之后再来看看Handler、MessageQueue和Looper是如何相互配合完成线程间通讯的。假设是在子线程中发送消息然后在主线程中处理消息。

使用Handler发送消息主要有两种方式:

        //在主线程创建Handler
        Handler handler = new Handler(){
		@Override
		public void handleMessage(Message msg) {
			//处理子线程发送的消息
	}
};


     //方式1 子线程发送消息
	Message msg = Message.obtain();
	handler.sendMessage(msg);


        //方式2 子线程发送消息
        handler.post(new Runnable() {
			@Override
			public void run() {
				doSomeThing();
			}
		});

 

handler.sendMessage()

通过handler.sendMessage()发送消息首先要获取一个Message对象,Message对象最好是通过Message.obtain()方法来获取,obtain()方法内部会从对象池中取出已经存在Message对象,避免多次创建Message对象造成不必要的性能消耗。

来看看handler.sendMessage()做了什么事:

public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 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) {
      
        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);
    }


private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {        
        //1.将当前的Handler对象赋值给msg.target
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //2.调用MessageQueue的enqueueMessage将消息存放到MessageQueue中
        return queue.enqueueMessage(msg, uptimeMillis);
    }

经过层层调用,最终会进入enqueueMessage()这个方法。

注释1中将当前Handler对象赋值给将当前的Handler对象赋值给msg.target,这个时候msg就与发送msg的Handler对象完成了绑定。

注释2是通过调用MessageQueue的enqueueMessage将消息存放到MessageQueue中,这个方法前面已经分析过。这个时候就将在子线程发送的消息放到消息队列中了。

消息存放到消息队列之后就会由主线程的Looper取出并进行分发,这个Looper是app启动后由系统在主线程创建的,并且loop()方法运行在主线程中:

public static void loop() {
        //从当前线程中取出Looper
        final Looper me = myLooper();
        //如果Looper对象为null抛出异常    
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //从Looper中取出MessageQueue消息队列 可能会阻塞 
        final MessageQueue queue = me.mQueue;

       
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        //死循环从MessageQueue消息队列中取出消息并进行分发
        for (;;) {
            //1.从MessageQueue消息队列取出消息
            Message msg = queue.next();
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ...

            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                //2.取出msg后分发给handler进行处理
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
           

            ...

            msg.recycleUnchecked();
        }
    }

直接看注释2,msg.target是一个Handler对象,在前面的sendMessage()方法中已经分析过,msg.target是在sendMessage()的时候进行赋值的,并将target指向主线程中创建的Handler ,最终会调用Handler的dispatchMessage()方法完成消息的分发。看一下dispatchMessage()方法:

public void dispatchMessage(Message msg) {
         //1
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //2
            if (mCallback != null) {
                
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //3.
            handleMessage(msg);
        }
    }

注释1:msg.callback是一个Runnable,默认为null,在前面分析sendMessage()方法的时候可以发现并没有对msg.callback进行赋值,所以代码不会进入if里面。

注释2:mCallback是Handler内置的一个接口,如果我们实现了这个接口,就会在这个接口中优先处理消息,不会走到我们创建的Handler中的handleMessage()方法。我们并没有实现这个接口,所以在这里mCallback肯定为null,代码会执行到注释3的handleMessage()方法。

注释3:这个handleMessage()方法就是我们创建Handler对象时实现的方法,代码执行到这一步 我们就能在主线程中接收到子线程发送的消息msg,完成线程间通讯。

handler.post()

再来看看子线程通过handler.post()是如何向主线程发送消息的:

 public final boolean post(Runnable r)
    {   
       //1.通过getPostMessage(r)封装一个Message对象
       return  sendMessageDelayed(getPostMessage(r), 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) {
        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);
    }

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //将当前的Handler对象赋值给msg.target
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
       //2.调用MessageQueue的enqueueMessage将消息存放到MessageQueue中
        return queue.enqueueMessage(msg, uptimeMillis);
    }

在注释1处会先通过getPostMessage(r)获取一个Message对象,看一下getPostMessage()这个方法:

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        //将Runnable赋值给msg.callback
        m.callback = r;
        return m;
    }

先从对象池中获取一个Message对象,然后将handler.post()传入的Runnable对象封装到msg.callback,此时msg.callback不为null。最后返回这个Message对象

在回头看看handler.post()中的代码,经过层层调用,最终会进入注释2的方法,这一步与前面handler.sendMessage()中分析的一样,调用MessageQueue的enqueueMessage将消息存放到MessageQueue中。

经过前面的分析已经知道,消息插入消息队列之后会通过Looper取出并调用msg.target.dispatchMessage()进行分发,在来看一下Handler的dispatchMessage()方法:

public void dispatchMessage(Message msg) {
         //1
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //2
            if (mCallback != null) {
                
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //3.
            handleMessage(msg);
        }
    }

注释1:前面在调用handler.post()方法时候,已经将Runnable对象赋值给msg.callback,此时msg.callback不为null,进入if语句里面执行handleCallback(),不会在执行注释2、注释3处的代码。再来看一下handleCallback():

private static void handleCallback(Message message) {
        //执行Runnable的run方法
        message.callback.run();
    }

逻辑很简单,就是回调了我们post的Runnable对象的run()方法,此时run()方法就会在主线程中运行。

 

本篇文章对Handler的工作机制做了一个详细的分析,希望能给大家带来帮助,如果有不对的地方,欢迎指正。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值