Handler结合源码的总结

前言


一直以来对Handler的理解都迷迷糊糊的,今天好好总结下。想了半天不知道该从哪里开始写起,那就从最常用的用法开始吧!

代码解析


我们都知道以下Handler的用法会报错:RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        new Thread(new Runnable() {
            @Override
            public void run() {
                handler = new Handler();
                //do someThing...
                handler.sendEmptyMessage(0);
            }
        }).start();
我们找到源码中抛出异常的地方,源码如下:
    /**
     * ...
     * If this thread does not have a looper, this handler won't be able to receive messages
     * so an exception is thrown.
     */
    public Handler() {
        this(null, false);
    }

    public Handler(Handler.Callback callback, boolean async) {
        ......
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
        }
        ......
    }
很明显异常的产生是因为在Handler构造方法中,条件的判断Looper对象为null所导致,并且根据默认构造方法的注释可知:当使用Handler切换线程时,那么这个线程必须要有一个looper,否则Handler无法接收到消息。既然Looper如此重要,那么什么是Looper呢?来看下Looper类的注释:
/**
 * Class used to run a message loop for a thread.  Threads by default do
 * not have a message loop associated with them; to create one, call
 * {@link #prepare} in the thread that is to run the loop, and then
 * {@link #loop} to have it process messages until the loop is stopped.
 * ......
 */
大意是:线程在默认情况下没有与它们关联的消息循环,Looper类就是为线程运行消息循环的类。可以使用Looper.prepare()方法为线程创建一个消息循环,使用Looper.loop()方法开始处理消息。这里又牵扯出两个概念:消息(Message)和消息队列(MessageQueue)。Message是 一个实现了Parcelable接口可以被发送给Handler的数据载体,MessageQueue是一个内部由单链表实现的用于管理Message的列表。

知道了相关的概念,那么再来看正确的Handler的写法:
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                handler = new Handler();
                //do someThing...
                handler.sendEmptyMessage(0);
Looper.loop(); } }).start();


由此看来执行Looper.prepare()方法后,Looper.myLooper()方法可以获取到Looper对象,再来看看相关的源码:
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    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对象,并存储到ThreadLocal中,ThreadLocal网络上有人翻译为:线程局部变量,很贴切,它为使用该变量的线程提供独立的变量副本,能解决线程并发访问变量的问题。并且,根据这里的条件判断说明,一个线程中只能有一个Looper实例,因此Looper.prepare()方法在一个线程中也只能调用一次。第二部分源码是Looper.prepare()方法中创建的Looper的Looper构造器,可以看到在构造器中实例化了一个消息队列(MessageQueue),也就是说线程中的MessageQueue也是唯一的。这样Looper.prepare()执行后便创建了Looper对象,Looper.myLooper()方法也就是获取当前线程的Looper对象,而Looper中的Looper.getMainLooper()也就是获取主线程的Looper对象(实际上还是通过Looper.myLooper()方法获取的),这样对于上述的错误的解决过程我们也就能理解了。接下来我们就可以愉快的使用Handler切换线程,处理消息了。

上面多次提到了消息循环,那么怎么开启消息循环,消息又是怎么循环呢?由Looper的注释可知通过Looper.loop()方法可以开始处理消息,我们看Looper.loop()的关键源码:
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 (;;) {
        Message msg = queue.next(); // might block
        ......
        msg.target.dispatchMessage(msg);
        ......
    }
}
可以看到loop方法中先判断了当前线程是否存在Looper对象,接下来开启一个死循环,循环从消息队列(MessageQueue)中取出消息,交给Handler去处理。这里是Handler中最终消息处理的方法源码:
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
这里的callback实际上就是我们通过Handler.post传递的Runable对象,当callback为null时最终调用我们熟悉的handlerMessage(Message msg)方法,完成消息的处理,至此我们也就明白了消息处理的整个过程。最后因为这里的Looper是我们手动创建并开启消息循环的,再线程任务结束了,别忘了调用Looper.myLooper.quit()方法退出消息循环。

总结


Handler常用来更新UI和处理消息,通过Looper.prepare()方法创建一个Looper实例,并创建一个消息队列(MessageQueue),用来对消息的管理,通过Looper.loop()方法循环从消息队列中取出消息交给Handler去处理,如果没有消息则阻塞,在当前线程中只能存在唯一的Looper和消息队列的实例。

其实Handler的用处还有很多,比如使用Handler构成每隔一定时间的无限循环,用来更新UI,形成动画效果:
        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //do someThing...
                handler.sendEmptyMessageDelayed(1, 1000);
            }
        };
        handler.sendEmptyMessageDelayed(1, 1000);
        //exit
        handler.removeMessages(1);


疑问


这里还有一个疑问,View类的post方法和Handler的post有什么区别呢?为什么在View类的post方法中可以获取到View的宽高呢?疑问
先说简单的Handler的post方法,通过追踪源码可知,Handler的post方法其实就是就是发送了一条消息并将消息加入消息队列而已。而View的post方法的源码如下:
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }
这里对View的AttachInfo进行了非空判断,什么是AttachInfo呢?简单来说就是当前View在父容器中的各种信息的集合。然后这里最终还是调用了Handler的post方法,由此看来View的post方法还是利用Handler发送消息,只不过这里的消息是发送给主线程。至于为什么能在View类的post方法中可以获取到View的宽高,简单来说就是通过View的post发送的消息,等主线程的Looper处理的时候,View已经执行了测量(measure)和布局(layout),所以也就能获取到View的宽高了。

参考资料:《Android开发艺术探究》




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值