Android源码分析(Handler跨线程通信)


 

 1.分析Looper——Thread——Handler和线程的关系

  • 这种机制,主要的解决安卓的线程之间的通讯,那他真正运行的机理是什么?

2.ThreadLocal(线程本地变量)初步介绍

  • ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
  • 提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程。

 

  • 执行ThreadLocal对象的set(Object obj)方法,将会向存入一个以当前线程为键的键值对。键就是你当前线程对象,值就是存在里面的数据(此处是Looper对象)。
  • 执行TheradLocal对象的get()方法,将会根据当前线程对象为键,取出与之对应的值,取出里面的内容(此处为Looper对象)。
  • 可以比作为一个特殊的HushMap:hushMap的键和值都是对象,但是对于ThreadLocal来讲,键是线程对象,值可以为任意对象。

Android源码分析(Handler跨线程通信)_第1张图片


回忆一下在Worker Thread使用looper的方法,首先调用Loop.prepare()方法,然后生成一个Handler对象,然后调用Looper.loop()方法,然乎Looper就从消息队列往外取出数据:

Looper——>Thread——>Handler

  • 一个Looper对象存在一个线程当中,——》Looper对象有与它对应的Handler——》Handler把消息发送到消息队列里面去——》Looper把消息从消息队列里面取出来——》把消息交给Handler去处理。想要研究他的工作原理,只能研究他的源代码才能分析出来。

在下面的代码当中,Looper和Handler好像没有什么关系,他们究竟是怎样建立起关系的呢?

Android源码分析(Handler跨线程通信)_第2张图片

 


Android源码位置:

安卓的SDK安装位置:然后接着找到此目录下的os文件夹,就是Android的源码

Android源码分析(Handler跨线程通信)_第3张图片

 

找到源代码,先找到Looper这个类:

Android源码分析(Handler跨线程通信)_第4张图片

打开Handler.java文件:

Android源码分析(Handler跨线程通信)_第5张图片

 

1.先看看Handler.java和Looper.java中的成员变量:

Android源码分析(Handler跨线程通信)_第6张图片

Android源码分析(Handler跨线程通信)_第7张图片

2.这个是Looper.java的类 

Android源码分析(Handler跨线程通信)_第8张图片

3.ThreadLocal分析:

Android源码分析(Handler跨线程通信)_第9张图片

4.根据Worker Thread的线程通信的基础代码来深入分析他们之间的关系:首先,说一说他们通信的原理,看下图:

Android源码分析(Handler跨线程通信)_第10张图片

1.Handler对象,负责把消息加入消息队列,首先会生成一个消息对象,调用语句Message mag=handler.obtainMessage();

为什么获取Message消息对象,是在handler对象的方法呢?疑惑1?调用handler.sendMessage(msg)方法,把消息发送到消息队列。

2.Looper是在等待消息的,只要一有消息,就会从消息队列取出来,这会有点疑惑了,这个Looper怎么知道在哪个消息队列去去消息,在下面的代码没有体现这一点啊?疑惑2?

3.Looper对象取到消息了,把它送给Handler对象的handleMessage()方法去处理,疑惑又来了,Looper对象怎么会知道送给哪个Handler对象去处理呢?疑惑3

其实多多少少看的出来,这三个其中的联系,想要分析出其中的关系,只有去看安卓源代码才会知道:

Android源码分析(Handler跨线程通信)_第11张图片

5.接着上面的思路,解除疑惑,我们从上图的调用Looper和Handler处理消息的机制去分析,用了Looper.prepare()方法去准备Looper对象,看看源码: 

Android源码分析(Handler跨线程通信)_第12张图片

6.看完下面的程序,我们就明白了,其实就是新建一个Looper对象,而Looper对象里面还有三个成员变量嘛:

Android源码分析(Handler跨线程通信)_第13张图片

7.在接着分析,handler = new Handler();做了什么?

Android源码分析(Handler跨线程通信)_第14张图片

8.生成Handler对象时,真正调用的函数,最重要的一点,其实就是调用了Looper中的myLooper方法,继续追踪,别的先不看:

Android源码分析(Handler跨线程通信)_第15张图片

9.一看,原来是取出本线程的ThreadLocal的值,返回值为Looper类型,那不就是上面新建的那个Looper对象嘛,再一想,那个Looper对象最重要的也就是三个变量而已,最重要的一个就是新生成了一个MessageQueue对象:

Android源码分析(Handler跨线程通信)_第16张图片

10.接着分析Handler的构造函数,我们知道了mLooper,其实已经和Looper.java的那么新建的Looper对象是同一个了(因为是从ThreadLocal取出来的), 

Android源码分析(Handler跨线程通信)_第17张图片

11.为了看的清楚,那就再回顾一下,新建一个Looper的时候里面的东西吧:嗯,三个变量

 Android源码分析(Handler跨线程通信)_第18张图片

12.我们总结一下:

在Looper.java中新建一个Looper对象(其中还有一个MessageQueue对象)然后呢在Handler.java中,通过Looper.myLooper()通过ThreadLocal把这个Looper对象取出来,去把Handler对象和Looper对象,联系起来,而线程和Handler对象本来就通过ThreadLocal变量绑定成键值对了,这样三者就联系起来了,再者Looper对象和Handler对象,有的其实是一个MessageQueue对象,这样一来,四个就全部联系起来了。那关于上面的,他们到底是如何联系起来的一目了然了:

Android源码分析(Handler跨线程通信)_第19张图片

 小结:

1.首次调用Looper.java 的prepare() 方法,在这个方法中进去首先判断当前线程ThreadLocal 的值是否为空,不空则会报错(说明本线程已经有一个Looper对象了,因为一个线程只能有一个Looper对象),如果为空则会创建一个Looper对象(通过new Looper()创建),创建的Looper的对象(在Looper()函数中会生成一个MassageQueue对象赋值给mQueue变量,还会把当前的线程赋值给变量mThread(注意这两个变量都是Looper类的)然后返回Looper类型的返回值),接着会把这个返回值(Looper)存储到ThreadLocal当中。

2.然后再Handler.java中调用Handler的构造函数,在这个构造函数又调用了两个参数的构造函数,在这个构造函数中,主要是调用了Looper.java中的myLooper( )方法(这个方法其实就是得到刚刚存在ThreadLocal中的值,也就是上面返回值的Looper(因为在同一个线程中,所以是同一个Looper对象)),得到了这个Looper对象,再把它复制给Handler.java类的mLooper(Looper类型)变量,这样一来,这个线程体的Looper对象,就有两份,一份在Looper,一份在Handler,且两份一模一样,在Handler构造函数出了这一步,还把mLooper对象的mQueue取出来,赋值给Handler.java中mQueue(MessageQueue)的值,再这样一来,那么在Looper.java和Handler.java中的Looper对象和消息队列都是同一个就这样,通过ThreadLocal把Looper——Handler——MessageQuene——线程体,就联系了起来。


Android源码分析(Handler跨线程通信)_第20张图片

已经说完了第一和第二,第三接着说,Looper的loop()方法,如何不断往外取数据:

需要解决的问题:

1.Looper当中的loop()方法的作用

2.什么是Message对象的Target

3.处理Message的方法 

Android源码分析(Handler跨线程通信)_第21张图片

看一下queue.next()方法

Android源码分析(Handler跨线程通信)_第22张图片 

这句话的意思是,在队列中取出消息msg之后,就调用dispatchMessage(msg)把消息对象传进去: 

Android源码分析(Handler跨线程通信)_第23张图片 

先看看Message的tatget是什么东西,发现是Handler属性,的那么存放的势必是Handler类型的对象了 

Android源码分析(Handler跨线程通信)_第24张图片

想要弄清楚为什么是这样调用的,我们先要去回顾一下,我们在代码中是如何获取一个消息对象的,才能知道这个属性他真正的意思(这个Handler对象指的是哪个对象):

Android源码分析(Handler跨线程通信)_第25张图片

使用Message msg = handler.obtainMessage();去获取一个Message对象,看一下这个获取函数干了什么: 

Android源码分析(Handler跨线程通信)_第26张图片

上面的this是谁,就是调用这个函数的对象:因为

Message  msg = handler.botainMessage()

上面的this就是Handler对象

 Android源码分析(Handler跨线程通信)_第27张图片

找到Message这个类的obtain方法 ,最终就是获取一个消息对象,然后把这个生成这个Message对象的handler赋值给消息对象的target属性(可以赋值,因为在上面知道target是Handler类型的)。

下面一句话很重要:所以说:  msg.target  就是handler.obtainMessage();中的handler

所以函数:msg.tatget.dispatchMessage(mag)就是找到,生成它这个Message对象(msg)

的Handler对象(handler)中的dispathMessage(msg)方法,这个dispatchMessage(msg)里面有一个handleMessage(msg),因为在Handler对象(handler)中复写了该方法,所以,消息(msg)在loop()方法中取出消息,是发给对应的handler.

总结:这样一来一执行Looper.java的Loop()方法,就会一直往外取出数据(msg),取出的数据发送给创建这个消息的Handler对象(handler),这样就解释了。为什么Looper知道从消息队列把消息取出来发给谁了。发给创建msg的那么handler嘛。

Android源码分析(Handler跨线程通信)_第28张图片

所以下面总结来说:

Android源码分析(Handler跨线程通信)_第29张图片

接下来去看看,mst.target的dispatchMessage(msg)方法,干了什么?其实是通过handleMessage函数去处理msg

Android源码分析(Handler跨线程通信)_第30张图片

此时再回忆一下,看下面这张图,在生成一以Handler对象的时候正是复写了handleMessage(Message msg)方法去处理Looper对象从消息队列取出来的Message对象。 

Android源码分析(Handler跨线程通信)_第31张图片

 小结:所以说他们的Handler和Looper的关系并没有那么复杂,只是通过消息队列进行数据的传输,可以这样去理解,当执行了

handler.sendMessage(msg);是在A这个线程,然后数据进入消息队列,B线程中,通过Looper中的loop( )方法,给取出来,找到msg.target属性(也就是生成msg的时候使用到的Handler对象)然后调用它的msg.targe.dispatchMessage(msg)方法在这个方法当中调用了我们复写的handleMessage()方法。




 内容

1.Handler的post(Runnable r)方法

2.Rnnnable的运行机制

3.post方法和sendMessage()方法的区别

Thread对象代表的是线程,Runnable对象代表的是线程体,只有线程才能运行,光有线程体是不能运行的。通过Java的线程机制我们可以得知。

Android源码分析(Handler跨线程通信)_第32张图片

post之后将会把Runnable对象加入消息队列,哪个消息队列我们不知道,但是肯定的是Looper会把它从消息队列中把Runnable对象取出来 ,然后怎么办呢?透过例子来分析下:

 

S17_Handler4

Android源码分析(Handler跨线程通信)_第33张图片

修改布局文件:

Android源码分析(Handler跨线程通信)_第34张图片 

为button绑定监听器,内容为启动一个线程:

Android源码分析(Handler跨线程通信)_第35张图片 

新建一个线程,名为:TestThread,去继承Thread类,然后复写Thread的run()方法,在run()新建一个Runnable接口,

在Runnable接口中,同样复写run()方法,然后打印当前线程明细。最后通过handler把Runnable对象r同过post(r)方法,post到消息队列中:

Android源码分析(Handler跨线程通信)_第36张图片

运行应用程序,咦,Runnable对象运行在主线程当中?

Android源码分析(Handler跨线程通信)_第37张图片 

提示:关于Runnable实现一个线程的方法:点我查看

分析一下程序的运行流程,程序运行起来肯定是有一个主线程(Main Thread),然后在主线程生成了一个Handler对象,那么与那么Handler对象所对应的Looper对象也一定运行在主线程当中,通过按钮启动TestThread线程,线程运行起来,我们使用handler

去post了一个Runnable对象,函数的解释,是把Runnable放进消息队列的,那么在主线程中的Looper机会从消息队列把Runnable对象取出来,可以看到Runnable已经运行了,按照线程运行机制,Runnable想要运行,肯定得有一个线程对象,就是说必须有(new Thread(Runnble r))把这个Runnable传递给这个Thread对象,再去调用这个Thread对象的start()方法就启动了一个线程,去运行这个线程体,那按照这样的思路,打印出来的线程的名字,一定不会是Main(主线程)

 

post(r)方法将Runnable对象r放置在消息队列中,Looper对象(运行在主线程),从消息队列中取出了对象r

我们猜测:取出Runnable对象之后,新建了一个线程对象

//生成一个线程对象,然后把Runnable对象作为参数传递进去

Thread  ft = new Thread(r);

//运行这个线程,那么Runnable对象这个线程体就运行了

ft.start();

结论:如果是这个样子的话,运行在新的线程,现在是主线程,所以猜测   

现在我们需要看的就是Looper取出了Runnable对象r之后,干了些什么,让这个Runnable对象运行在Main Thread上?

源代码分析:

先看看post(Runnable r)的内容:

Android源码分析(Handler跨线程通信)_第38张图片

其中有一个参数是getPostMessage(Runnable r)的返回值,先看这个函数:

 Android源码分析(Handler跨线程通信)_第39张图片

看看Message的callback属性,发现是Runnable类型的,那就没问题:

 Android源码分析(Handler跨线程通信)_第40张图片

那 getPostMessage(Runnable r)做了些什么?完成了两个操作

1.生成了一个Message对象

2.将r对象赋值给Message对象的callback属性

Android源码分析(Handler跨线程通信)_第41张图片

简化一下post(Runnable r)函数如下:下面的mag为msg:

Android源码分析(Handler跨线程通信)_第42张图片

 在看看sendMessageDelayed(msg,0)函数?其实就是延迟发送,0为没有延迟

Android源码分析(Handler跨线程通信)_第43张图片

那么又可以简化post函数了,简化一下就剩两句:

Android源码分析(Handler跨线程通信)_第44张图片

小结:把Runnable对象,放到Message对象里面,然后把Message对象放到消息队列里面

第一个问题:如何把一个Runnable对象,放在消息队列当中?

答:实际是生成一个Message对象,并将r赋值给Message对象的callback属性,然后将Message对象放置在消息队列当中

第二个问题:Looper取出了携带有Runnable对象的Message对象之后干了什么?

取出了Message对象之后调用了dispatchMessage方法,然后判断Message对象的callback属性是否为空,此时不为空

就会去执行handleCallback(Message msg),在该方法中执行了msg.callback.run()(msg.callback 等价于Runnable对象)方法。注意:直接调用一个Runnable对象的run( )方法,是不会开辟新线程的,直接运行在主线程上

下面解答:

应为Looper中的loop( )方法还是一样,所以还是看回loop()方法:

Android源码分析(Handler跨线程通信)_第45张图片

此时的msg.callback的值不为空,就不执行handleMessage(msg)方法,而是执行了handlerCallback(msg);方法了 

Android源码分析(Handler跨线程通信)_第46张图片

 handlerCallback(msg);做了什么?直接运行的这个Runnable的run()方法

Android源码分析(Handler跨线程通信)_第47张图片


那为什么要去这样做呢?post(Runnable r)最后却不让它去开辟新的线程呢?其实这算是一种机制,把一段代码当成一个变量一样传输,比如上面的那么例子是:在Worker Thread输出了一个语句,而通过post(Runnable r)却把代码给传输到Main Thread中运行了。

线程通信Handler详细分析例子:点我查看

我们知道在Worker Thread中是不能去修改UI的属性的,按照正常的逻辑你只能在主线程新建一个Handler然后在Work Thread当中,同过handler.sendMessage(msg)方法,把worker Thread中的消息通过Looper取出来,然后交给主线程的Hanler的handleMessage(Message msg)方法去处理Worker Thread传回的数据更新UI。

那现在我们有了post(Runnable r)这个方法,却好像可以在Worker Thread当中去修改UI的属性了,因为从上面看到Runable对象时运行在Main Thread(主线程)当中的。看下代码

Android源码分析(Handler跨线程通信)_第48张图片

所以post(Runnable r)也为我们提供了另一种通信机制,我们可以通过sendMessage(msg)把消息发出去,给handleMessage()处理,也可以把一段代码从WorkThread搬到主线程去运行。这也得益于,直接执行Runnable的run()方法,系统不会开辟一个新的线程。


附录: 

本博文Demo下载:https://github.com/zGuangYuan/Android- 

github的用法:点我查看


 

附:2:

1.Java中final的作用:

Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面就从这三个方面来了解一下final关键字的基本用法。
  1.修饰类
  当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

  在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。
  2.修饰方法
  下面这段话摘自《Java编程思想》第四版第143页:
  “使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
  因此,如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。
  注:类的private方法会隐式地被指定为final方法。
  3.修饰变量
  修饰变量是final用得最多的地方,也是本文接下来要重点阐述的内容。首先了解一下final变量的基本语法:
  对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

 

你可能感兴趣的:(Android,学习笔记)