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
- Handler结合源码的总结
- 关于handler机制(结合源码及方法的调用去总结)
- [图解法结合源码]理解、记忆Handler、Looper、MessageQueue之间的关系
- Handler的源码分析
- handler的源码学习
- Handler的源码分析
- handler的源码分析
- Android的Handler总结
- Android的Handler总结
- Android的Handler总结
- Android 的 Handler 总结
- Android的Handler总结
- Android的Handler总结
- Android的Handler总结
- Android的Handler总结
- Android的Handler总结
- Android的Handler总结
- Android的Handler总结
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll、Epoll模型处理长连接性能比较
- 关于android 6.0手机权限动态请求
- Shell_Linux 将字符串分割为数组
- 【项目管理】软件行业作坊式管理的表现
- iptables详解
- Handler结合源码的总结
- Linux_ cut 指令
- 在oracle中创建指定时间范围的记录
- J2SE、J2EE区别
- AtCoder gc009 A Multiple Array
- Linux用户密码管理
- 明天就要回家了
- flex&bison范例初步学习
- 记那些年过后...