android开发我也是在学习阶段,由于喜欢看源码,所以顺便把经常用到的一些类都看看顺便分析一下他们的工作机制。
Handler在android开发中应该比较常用,主要用于接收异步处理结果的消息并更新UI,比如做了一个天气应用,用户点击刷新的按钮的时候,为了不让界面卡住,这时候应该起一个异步任务去获取数据,然后等数据获取完成之后才更新UI的结果。不同于js更新web上的元素,android的UI更新操作都不是线程安全的,所以需要在同一个线程下才能更新UI,而js是没有多线程的概念的,所有操作都在同一个线程中,所以不会有线程安全的问题。来看看Handler的官方注释:
简单翻译一下:一个Handler可以让你用来发送和处理消息(Message),以及消息上附带的Runnable对象,整个是跟消息队列(MessageQueue)一起使用的。每一个Handler实例会关联到一个唯一的线程和该线程的MessageQueue。如果你创建了一个Handler,他将会跟创建这个Handler的线程和该线程的消息队列绑定在一起。也就是说通过把消息发往这个队列和在出列的时候处理他们。Handler一般有两种应用产景,(1)就是调度消息和runnable对象在未来的某个时间点执行(归纳起来就是消息的发送);(2)可以把消息发送到其他线程里面。再简单概括一下就是Handler是跟创建他的线程绑定在一起的,然后通过消息队列方式,实现线程安全的操作。
1. Handler的创建
当你在一个Activity(或者其他)里面new一个Handler的时候,他最终会执行下面这个构造函数:
第一段if可以无视,是判断Handler是不是静态的,如果不是给你一个警告说可能会内存泄露。Looper.myLooper()其实就是在当前线程的ThreadLocal中获得Looper对象,同时从looper中获取MessageQueue。这里的callback其跟覆盖handleMessage的机制差不多,估计是为了某些地方使用了这种方式,然后一直兼容到现在。
2. 消息的发送机制
当我们调用handler的send()/post()系列的方法的时候(这些方法的调用一般发生在异步处理的任务完成之后,像触发线程的更新操作),handler做了什么操作?他们实际上殊途同归,最终都调用了这个方法:
第一句把msg.target设置为当前的handler本身,这一步很重要,因为在这之后,就跟handler没关系了,后面会分析;接着就调用消息队列的入列方法把消息体丢到队列里面排队等待执行。
可以看到,不管是什么方式,最终都是把消息(runnable最后也是包装成消息)丢到消息队列里面。
3. Looper的工作机制
Looper的工作机制就跟他的名字一样,就是一个循环器,作为MessageQueue的消费者,进行消息的出列和处理操作。其核心代码就在loop()方法中,来看看
看到for(;;)我们就能猜到这是一个典型的生产者消费者模型了,在for循环里面,looper不断的订阅消息队列的下一个元素(next()方法),然后调用Handler的dispatchMessage方式分发消息给handler进行处理。上面说到设置msg.target那一步很重要就重要在这里。消息处理完之后还可以回收再利用,这点不细说了(如果你的Message是new出来的,回收了也没用,科学的使用方式是使用Message.obtain() 系列的方法来创建消息。)这里消息一个一个处理完之后才会处理下一个,是单线程串行执行的,而且跟创建handler的线程是同一个线程,所以完美的避免的线程安全的问题。那么你的疑问会是,究竟是谁来调用这个loop方法的,不是会卡住吗?是的,这就是精髓所在,这里的looper是UI线程在初始化完所有的UI操作之后调用的,这样一来,就不会有卡住的问题了。
4. 消息分发机制
上面Looper拿到消息之后,会调用Handler的dispatchMessage进行消息的分发操作,这里分发不是简单的调用Handler中的handleMessage方法,而是有其他逻辑在里面的,来看
惊呆了有木有,handleMessage方法是优先级最低的!为什么呢,因为有Handler有个post方法呀。post方法的参数是一个Runnable对象,然后通过创建一个Message,再把message的callback设置为这个runnable,然后再发送到消息队列里面。post方法的场景是你更新UI的时候需要知道获取到了什么新的数据,然后直接更新。而handleMessage方法可以不需要知道更新了哪些数据,就仅仅更新UI就可以了(当然,神奇的Message里面还配置了bandle,可以传数据的,所以其实都差不多,哈哈)。至于Handler的mCallback成员则是回调函数的通用写法,跟handleMassage的方式差别也不大。到这里就能明白为什么handleMessage在定义的时候不是abstract protected void handleMessage()了吧,因为他确实不是必须的。。。
分析到这里,可以看出,handler之所以可以更新UI,不是系统做了什么神奇般的兼容,而是因为他跟UI线程使用的本来就是同一个线程,UI线程通过Looper.loop来等待消息的分发,handler发送消息后把消息放到消息队列里面,而Looper负责从消息队列里面拿数据,又交给handler进行处理,最终实现了UI的异步更新操作。这是个生产者消费者模型典型应用,其中消息队列的功劳巨大,我们来看看他有哪些功能。如果我们开发中也需要实现这种类似的生产者消费者模型,可以使用这一套机制。需要注意的是,MessageQueue我们不能单独定义来使用,因为其核心API的可访问修饰符都是包级别的,我们不能把代码定义到android.os这个包下面,所以他要通过配合Looper来使用。Looper的核心API都是开放的。
5. 消息队列
enqueueMessage入列,可以把消息放到队列里面,这里队列的底层是android的本地代码实现的,其实可以参照juc里面的DelayQueue的实现,机制差不多,都支持延时出列的,这不过实现方式不同罢了。
next()出列,消息出列,队列的出列优先级是入列的时候定义的时间来决定的,时间值越小(长整形)优先级越高。
你好非常高兴,能够再次和你探讨此问题,activityTread里的atttach是通过performlaunchActivity来启动acitivity并绘制窗体的,而这个函数也是在activityTread里的Handler里回调的,即使上并不是先绘制再启动loop的。绘制优先级高和postSyncBarrier同步分割栏有较大关系。
👍赞
“那么你的疑问会是,究竟是谁来调用这个loop方法的,不是会卡住吗?是的,这就是精髓所在,这里的looper是UI线程在初始化完所有的UI操作之后调用的,这样一来,就不会有卡住的问题了。”您好,我对这段话有些疑惑,loop不是在application创建时就已经创建了吗,不知道,是否可以摘出您说的这段的源码,或许可以让这个解释变的更明确。
UI线程的入口在ActivityThread,看其中的main方法,先attach(也就是初始化应用和加载各种配置),最后才启动looper的loop函数。虽然一开始就初始化了Looper,但并没有调用loop方法,也就不会卡住了。
你好,非常感谢你的回答,可能我的问题问的不够好,我想说的情况是这样的,比如在fragment里的onVisibleHint里实际上ui还没有绘制好,操作ui会空指针,但是加个handler,用handler来post,在操作里面的ui,就会规避这个问题,那系统是怎么做到的呢,不过您之前的回答,我也学习到了,再次感谢。
因为你这个时候post过去,looper还没初始化好,根本不会去操作UI线程,依然要等待所有UI初始化完毕之后,looper开始执行,才会处理消息队列里面的消息(也就是你post过去的操作),然后才会修改UI。
写的很好,”Looper的核心API都是开发的“,这句应该开放吧
嗯, 谢谢指出, 已修正.
Pingback: AsyncTask与Handler 的关系 | jiacheo杂谈