Handler结合源码的总结

来源:互联网 发布:大学生心理普查数据 编辑:程序博客网 时间:2024/05/21 09:03

前言


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

Begin


我们都知道以下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();                Looper.loop();                //do someThing...                handler.sendEmptyMessage(0);                Looper.myLooper().quit();            }        }).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开发艺术探究》
http://www.07net01.com/2016/09/1664014.html






0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 6岁半B超没子宫怎么办 学生学籍号和身份证号不一致怎么办 学生学籍号和身份证号不一样怎么办 领导交代的任务完不成怎么办 洗衣机里的衣服有味道怎么办 新买的洗衣机有味道怎么办 模拟工业装置没有数据验证怎么办 民办学校的指标生学费怎么办 孩子上初中后成绩不好怎么办 孩子考最后一名怎么办 数学大题不会做怎么办 小学六年级数学考30分怎么办 六年级数学考了30分怎么办 数学考了30分怎么办 没有给直接领导报到怎么办 小学二年级成绩不好怎么办 初中孩子上课注意力不集中怎么办 学生打架家长争吵老师怎么办 我和我老婆感情危机怎么办 数学作业做得慢怎么办 待转弯区变红灯怎么办 大班健康发生火灾怎么办教案 数字化审图图纸提交不了怎么办 监狱建筑师没电了怎么办 ios短信提示不弹出怎么办 ie游览器图标删了怎么办 电脑上ie卸载了怎么办 打开cad浏览器闪退怎么办 dnf进游戏闪退怎么办 苹果8出现闪退怎么办 手机浏览器老是自动打开软件怎么办 打开手机浏览器为什么是英文怎么办 ie浏览器删除掉了怎么办 手机360浏览器卸载不掉怎么办 大学素质拓展学分不够怎么办 专升本学分不够怎么办 电脑连接无线网络网关禁用怎么办 背部毛孔粗大有黑头怎么办 毛孔变粗大长痘怎么办 皮肤粗糙暗黄毛孔大怎么办 毛孔粗大还有痘印怎么办