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

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

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

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

时间: 2024-08-28 02:08:11

Android源码学习-----Handler机制的相关文章

Android源码学习之装饰模式应用

主要内容: 装饰模式定义 装饰模式优势 装饰模式在Android源码中的应用 一.装饰模式定义 装饰模式定义: Attach additional responsibilities to an object dynamically keeping the same interface. Decoators provide a flexible alternative to subclassing for extending functionality. 动态地给一个对象添加一些额外的职责.就增加

Android源码学习(一) 数据集观察者

查看Android源码发现这个,决定记下下来. 1.在android.database这个包下面,存在这样一个抽象类DataSetObserver,里面包括onChanged()和onInvalidated()这个两个方法,用来监听数据的改变,在方法内要执行的操作. /* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "Lic

Android源码学习-----HandlerThread

HandlerThread 1.run()方法 HandlerThread 从继承关系上看, 它继承Thread类, 由此可以得知这个类其实是一个线程类,既然是一个线程类, 那么肯定是要重写Thread中的run()方法, 所以可以浏览下run()方法 从红色箭头的三个方法中, 看到有三个方法, Looper.prepare(), Looper.myLooper(), Looper.loop(),  这三个方法其实是, 在子线程创建Handler时所要调用的三个方法, 在HandlerThrea

Android源码分析--Handler和Looper机制详解

在Android系统中的应用程序,与Java的应用程序相同,都是靠消息驱动,简单的说就是:有一个消息队列,我们可以不断的向这个消息队列中添加消息,并从中取出消息,处理消息.Android中与此工作相关的主要是由Handler,Looper以及Message来完成. Looper类:为一个线程运行着一个消息循环,内部有一个消息队列,每一个线程只允许最多存在一个Looper: Handler类:允许你向一个线程的消息队列中发送消息,处理消息: Message类:消息类. 使用样例 首先,我们通过一个

Android源码学习之ListView的复用回收机制剖析.

本博客参考了地址:点击打开链接 在刚开始接触学习Android基础的时候,ListView算是一个比较神奇的控件了,因为那时候好多效果都可以用它实现,而且用它就得用到一个设计模式,[适配器].结果昨天遗留下来一个bug,带这个解决这个Bug去翻看了5.0.1 API22的ListView部分源码分析复用. 复用到底有什么用.?简单的举个例子,假如你想要展示一万条item,作为手机不可能一下将一万条同时加载进去,这样肯定会OOM的,所以Google开发者想到了复用,也算是ListView高级的一个

android源码解析--Handler

转载自:http://blog.csdn.net/lilu_leo/article/details/8143205 开始,先看下android官方对于Handler的解释: [java] view plaincopy /** * A Handler allows you to send and process {@link Message} and Runnable * objects associated with a thread's {@link MessageQueue}.  Each 

【转】Android源码学习(2)使用Git和Repo进行版本管理

原文网址:http://blog.chinaunix.net/uid-26074270-id-2458828.html Android项目采用Git和Repo进行版本管理.在大多数情况下,Git都可以满足用户的需求.然而,由于Android项目过于庞大,想要简单.高效的管理这一百多个Git库,并不是一件容易的事情.Repo正是基于此需求,对Git命令部分封装,用来简化一些跨网络的操作. 安装Repo 创建repo所在的目录,并将目录加到PATH环境变量中 $ mkdir ~/bin $ PATH

Android源码学习初步

目前,互联网行业正在朝着移动互联网方向强劲地发展,而移动互联网的发展离不开背后的移动平台的支撑.众所周知,如今在移动平台市场上,苹果的iOS.谷歌的Android和微软的Windows Phone系统已经形成了三足鼎立的形势,而Android系统的市场占有率是最高的.Android系统之所以能够在市场上占据着第一的位置,一来是因为它依托着谷歌的品德效应和技术实力,二来是因为它是开放的,任何人都可以得到它的源代码,并且能够自由地使用它.既然Android系统是开放的,作为一个移动平台开发人员来说,

Android学习进阶路线Android源码分享)

毕业8月有余,同时伴随着从事的Android经验的提升.仔细思量过去的工作内容,掐指算来也是少得可怜---主要维护FM收 音机模块,间或看看Lancher模块的代码.尽管这样,总的来说,在这八个月事件的学习里,我自我感觉收获还是很大的---自学了很多 Android相关方面的知识.虽然里真正的大牛还有很远的一段路程要走,自己也会坚持学习.坚持走下去. 再次,把自己学习过程中总结的一些经验总结出来,帮助那些刚刚步入Android的网友以及有了取得了一些经验但不知如何 继续前行的朋友,希望你们能够早