码迷,mamicode.com
首页 > 移动开发 > 详细

Android源码学习-----Handler机制

时间:2018-03-25 15:58:25      阅读:228      评论:0      收藏:0      [点我收藏+]

标签:原来   and   指正   local   AC   指定   并且   time()   标记   

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(){};

技术分享图片

技术分享图片

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()方法由我们自己实现分发消息

 

如有错误, 敬请指正, 感激不尽!

 

Android源码学习-----Handler机制

标签:原来   and   指正   local   AC   指定   并且   time()   标记   

原文地址:https://www.cnblogs.com/sweep/p/8644488.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!