知识的领域是无限的,我们的学习也是无限期的。这篇文章主要讲述Android源码学习-----Handler机制相关的知识,希望能为你提供帮助。
Handler
1.为什么要使用Handler
在android4.0之后,google公司为从系统使用及用户体验方面考虑,如果做一些比较耗时的操作,就不允许直接在主线程中进行,而是要通过handler发送Message对象的方法来修改主线程的UI界面
文章图片
2.Handler原理简介
在所有的UI操作界面中,都在执行一个死循环(Looper)在不断接收和监听用户发出的指令,一但接受到指令,就立即执行。
当子线程需要修改UI界面时,调用Handler的sendMessage()方法,向主线程发送消息(Message)
Handler
把消息放到死循环Looper的消息队列中(MessageQueue)
Looper
中还有一个死循环的方法,它会不停的从消息队列中取出消息,并将消息发送给handler
当handler接收到这个消息后就会执行修改UI界面的程序
3 Handler、Message、MessageQuene、Looper何时创建?
3.1 Message的创建
由于在子线程中无法再直接修改主界面,那么就需要通过创建Handler发送Message的方法来间接修改主界面。所以Message是由我们手动在需要修改主界面的时候创建的。
Message一般有三种创建方式,
(1) 使用new关键字创建
Message msg = new Message();
(2) 使用obtain()方法创建
Message msg = Message.obtain();
(3) 使用Handler对象通过obtainMessage()方法创建,此方法证明handler是比Message先创建的
Message msg = handler.obtainMessage();
通过观察Handler对象中的obtainMessage()的源码发现,这个方法内部调用的其实就是Message.obtain(Handler h)方法来创建的Message对象。 参数的this表示调用这个方法的Handler对象。
文章图片
进入obtain(Handler h)的源码中可以看到,在调用此方法时,将handler对象给了Message中的target,并返回一个Message对象,所以此方法最终还是调用了无参的obtain()方法
文章图片
在obtain()无参的方法中,内部有一个同步的代码块,在这个同步代码快中,通过new关键字创建了Message对象。不过在创建之前,它先做出了判断,如果在Message池中已经有Message对象了,那么就返回已经存在的对象,如果没Message池中为null,才new一个Message对象。(详解见3.1.2 Message如何存储消息对象)
3.2 Message如何存储消息对象
Message内部并不是以队列的方式存储,而是以消息池的方式存储消息
(1) mPool默认为消息池中的第一条消息,在调用obtain()方法存储消息对象时,首先用判断mPool是否为空,当sPool不为null时,就使用消息池中已有的Message对象,如果为null,则new一个Message对象
文章图片
(2)当消息池中的消息不为null时,即sPool不为null,则将sPool赋值给一个Messagee对象m,这时m对象就为消息1
文章图片
(3)Message对象m使用next属性指向下一条消息,这时的m.next相当于消息2,并将自身赋值给sPool,这时的sPool为消息2.
文章图片
(4) 在让m.next 为null,即让消息1不再指向下一条消息,断开消息1和消息2之间的联系关系。这时的sPool为消息2
文章图片
(5) 让消息池的长度减1,并将Message对象return出去,因为消息m即原来的消息对象已经取出,所以此时消息2即sPool就成为了消息1
文章图片
简而言之,obtain()方法中,就是当判断出消息池中已经有Message对象中,没调用一次该方法,就像第一个消息取出,将第二个消息变为第一个消息
3.3 Handler的创建过程
Handler在当我们需要修改UI界面时,需要我们自己通过new关键字创建,一般使用无参的构造方法。
Handler handler = new Handler(){};
文章图片
【Android源码学习-----Handler机制】
文章图片
3.4 Looper的创建过程
在Handler的无参构造方法中,调用了自身的一个双参数的构造方法,有一行代码为Looper调用了一个myLooper()方法,为变量mLooper赋值,这个变量mLooper是一个Looper对象
文章图片
文章图片
在myLooper()方法中,通过sThreadLocal调用了get()方法返回一个Looper对象,
文章图片
从此处可以推断出,一般有get()方法时,大多数会有一个相对应的set()方法,通过查找sThreadLocal找到set()方法,可以看到是在prepare()方法中,调用的set()方法。
文章图片
在prepare()方法中,仅仅是创建了一个Looper对象,并赋值给sThreadLooper,并没有调用加载,继续查找prepare()被调用的地方。
通过查找,找到prepare()方法在prepareMainLooper()方法中被调用。那么,只要找到prepareMainLooper()被调用的地方,就能找到Looper在什么时候被调用加载。
文章图片
在Android系统中,Android的UI线程,也就是主线程,实际上是ActivityThread.java,通过查看ActivityThread的源码,找到main()方法,找到了prepareMainLooper()方法被调用
文章图片
由此可以推断出,当应用程序的主线程启动的时候,就调用加载了prepareMainLooper()来创建了Looper。
3.5 MessageQueue的创建过程
在Handler的无参构造方法中,调用了自身的一个双参数的构造方法,有一行代码为
Looper对象mLooper调用mQueue,为MessageQueue赋值
文章图片
文章图片
所以要在Looper类中找到mQueue,可以找到MessageQueue在Looper的构造方法中被new出来,而此方法在prepare()方法中,通过sThreadLocal调用set()方法中,作为参数被传入,接着就类似于Looper的创建过程
文章图片
实际上MessageQueue是由Looper所创建的,由此,可以推断出来,MessageQueue和Looper一样,也是在主线程UI在启动的时候被加载调用。
文章图片
4 当Looper发现MessageQueue中没有消息了,会怎样执行?如果又有消息了,Looper又会怎样执行?
当Looper发现MessageQueue中没有消息了,就会进入阻塞状态,直到MessageQueue中再次有消息时,Looper才会醒来,继续执行循环
在此需要明白Linux中的进程间通信的机制。
4.1 Linux系统中的进程间通信机制
Linux系统中的进程间相互通信,是通过PIPE管道来完成的,Linux中有一个特殊文件叫做句柄,相当于一个变量对象。一个特殊文件有两个句柄,一个是写入句柄,另一个句柄是读取句柄。
而子进程对应写入句柄,通过写入句柄向这个特殊文件中写入数据,主进程对应读取句柄,不断的读取特殊文件中的数据。
当特殊文件中没有数据时,主进程就会进入阻塞状态,进入睡眠;当子进程开始向特殊文件中写入数据时,为写入一个特殊标记w(write),当特殊文件接收到这个特殊标记w时,就会去唤醒主进程。
4.2 Looper的阻塞和唤醒机制
Looper的阻塞和唤醒,就类似于Linux中的进程间同信机制,Handler就是子线程,MessageQueue就是特殊文件,Looper就是主线程。
当MessageQueue消息队列中如果没有消息时,Looper就会进入阻塞状态,而当Handler有数据写入MessageQueue消息队列中时,会写入特殊标记w(write),当MessageQueue接收到这个特殊标记w时,就会唤醒Looper继续读取消息。
文章图片
5 MessageQuene如何存储Handler发送的消息?
消息是由handler通过sendMessage()方法来发送给MessageQueue,当MessageQueue接收到后,将这个消息存储起来
在sendMessage()这个方法中,传入一个Message对象,内部调用sendMessageDelayed()方法并返回。
在sendMessageDelayed()这个方法中,接收两个参数,一个是Message对象msg,另一个是
文章图片
在这个方法中,调用sendMessageAtTime()方法,该方法中有两个参数,第一个参数为Message对象,即调用sendMessage()方法时传入的Message对象,在此继续传递。
另一个参数为延迟时间加上一个当前的时间,由于在sendMessageDelayed()方法中传入的延迟时间为0,所以可以认为这个参数就表示发送Message消息的时间。
文章图片
在sendMessageAtTime()方法中,返回enqueueMessage()方法,此方法接收到了传入的Message对象和Message的发送时间这两个参数,该方法表示当Handler发送一个消息时,通过这个方法将发送Handler发送的消息插入到MessageQueue中
文章图片
而enqueueMessage()方法调用的MessageQueue中的一个双参数的方法,
文章图片
查看MessageQueue的源码找到enqueueMessage()双参数的方法,这个方法接收一个Message对象和Message的发送时间。
在这个方法中,通过判断消息是否为空、消息的发送时间,将接收到的消息插入到MessageQueue中。
mMessage为消息队列中的第一条消息,并且赋值给一个Message对象p,所以此时mMessage和p都为第一条消息。
文章图片
5.1假设现在p不为null,也就是消息不为空时
假设现在p不为null,也就是消息不为空时,那么代码判断执行else中的语句。定义了一个新的Message对象prve
文章图片
(3) 在这个else语句中,有一个循环,在这个循环中将p赋值给了prev,并且p调用next指向第二条消息,直到p为null,或者这个即将发送的消息msg的时间(when)小于p的消息发送的时间(p.when),即when < p.when时,才break跳出循环。(假设此时msg的when值大于a小于b)
此时p为第二条消息,而prev为第一条消息
文章图片
(4) 当满足break的条件跳出循环时,将p赋值给msg.next,也就是msg的下一条消息为p,并将msg赋值给prev.next,也就是prev的下一条消息为msg
文章图片
此时prev为第一条消息,msg为第二条消息,p为第三条消息
经此过后,传入的msg参数就插入到了MessageQueue中
文章图片
5.2 假设消息p为null时
mMessage为第一条消息,并赋值给p,此时p为第一条消息
假设消息p为null(p == null)或者发送时间为0(when == 0)或者msg的发送时间小于p的发送时间时(when < p.when)
当p为null时,就是第一条消息为空,意味着消息队列为null,队列中没有消息。让p赋值给msg.next,就是让msg的下一条消息为null,再将msg赋值给消息队列中mMessage默认的第一条消息,这时mMessage就指向了msg,那么msg就为消息队列中的第一条消息。
文章图片
当when等于0时,就是当即将要插入的消息的时间为0时,意味着要立刻处理这条消息。当when < p.when时,就是将msg插入到p之前
6 如何将消息发送给指定的message的Handler?
在Looper的loop方法中,当MessageQueue对象queue在调用next方法赋值给Message对象msg时,有可能会阻塞
先通过myLooper()创建一个Looper对象me,再通过me创建一个MessageQueue对象queue,然后再通过queue的next()方法返回一个Message对象msg,并对msg进行判断,
当msg为null时,证明在消息队列中,后面已经没有消息,并return返回结束。
文章图片
当msg不为null时,会通过target调用handler的dispatchMessage()方法。
通过看Handler的源码找到target,可以知道target就是一个Handler对象,当Handler发送消息给MessageQueue时,MessageQueue就会将这个Handler保存起来存储到target中,这样当Looper从MessageQueue中取出消息时,就可以将消息发给所对应的Handler来处理。
文章图片
文章图片
在Handler中找到dispatchMessage()方法,在这个代码中,首先判断callback是否为null,callback其实是一个Runnable对象,在通过obtain()方法创建Message对象时,obtain()有个双参数的方法,可以传入一个Handler对象h和一个Runnable对象callback。就是在此回调
文章图片
假如callback为null时,调用handleMessage()方法,该方法在代码中由我们自己实现。
文章图片
假如callback不为null时,执行handleCallback()方法,这个方法内部通过传入的Message对象调用callback的run()方法
文章图片
简单来说,当我们通过obtain()方法穿件Message对象时,如果传入一个Runnable对象callback,那么就通过handleCallback()方法来分发消息;如果不传入该参数,那么就通过在代码中重写handleMessage()方法由我们自己实现分发消息
如有错误, 敬请指正, 感激不尽!
推荐阅读
- 在pycharm中调试ryu应用(How to debug Ryu applications in Pycharm or other IDEs)
- React 学习 ---- create-react-app
- android sdk环境变量的配置
- app后端开发系列文章文件夹
- 开发app组件
- Elasticsearch Reference6.1Mapping
- leetcode之Find All Numbers Disappeared in an Array
- android--------实现Activity和Fragment通信的面向对象的万能接口
- revolv怎样用?revolv自动选择家居系统运用图文详细教程