Android消息机制底层原理



Android线程间通信(目的)

主要方式handler消息机制和线程间同步

线程间可以共享地址空间,线程间通信主要使用共享变量, handler消息机制也是在两个线程间共享了消息队列,asyncTask内部实现也使用了Handler机制



ThreadLocal是Looper中的一个特殊概念。它并不是线程,它的作用是可以在每个线程中存储数据。我们知道Handler创建时会采用当前线程的Looper来构造消息循环系统。那么Handler内部如何获取当前线程的Looper呢?这就是使用了ThreadLocal了,ThreadLocal可以在不同的线程中互不干扰的存储并获取数据。通过ThreadLocal可以轻松获取每个线程的Looper。


接下来再来解释一下MessageQueue。为什么说它是单链表的数据结构?

MessageQueue主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作,

插入和读取操作分别对应的方法为:(Message msg, long when)和next();


Handler的原理

Android的消息机制主要指Handler的消息机制,Handler的运行主要依靠底层的Looper和Handler支撑

    MessageQueue:消息队列,并不是真正的队列,而是以单链表的数据结构来存储Handler发送过来的Message;

    Looper:Looper以无限循环的方式去查看消息队列中是否有新消息,线程默认是没有Looper的,如果需要使用就必须为线程创建Looper,主线程创建时会初始化Looper,这就是主线程可以使用Handler的的原因

    Handler:通过Handler从子线程发送消息到主线程的MessageQueue中,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,另外Handler通过ThreadLocal可以轻松的获取每一个线程的Looper,从而获取消息队列中的message

他们三者的关系是:MessageQueue和Looper是一一对应的,一个Looper可以对应多个Handler。


prepare()背后的工作方式一目了然,其核心就是将looper对象定义为ThreadLocal。使一个普通线程变成Looper线程。

具体过程:是在线程中创建一个Looper对象,这个Looper对象是存放在sThreadLocal成员变量里面的,

成员变量sThreadLocal的类型为ThreadLocal,表示这是一个线程局部变量,

即保证每一个调用了prepare()函数的线程里面都有一个独立的Looper对象。

在线程是创建Looper对象的工作是由prepare函数来完成的,而在创建Looper对象的时候,

会同时创建一个消息队列MessageQueue,保存在Looper的成员变量mQueue中,后续消息就是存放在这个队列中去



Looper有以下几个要点:

1)每个线程有且只能有一个Looper对象,它是一个ThreadLocal

2)Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行

3)Looper使一个线程变成Looper线程。


从消息循环、消息发送和消息处理三个部分分析完Android应用程序的消息处理机制了,为了更深理解,这里我们对其中的一些要点作一个总结:

         A. Android应用程序的消息处理机制由消息循环、消息发送和消息处理三个部分组成的。

         B. Android应用程序的主线程在进入消息循环过程前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。

         C. Android应用程序的主线程进入空闲等待状态的方式实际上就是在管道的读端等待管道中有新的内容可读,具体来说就是是通过Linux系统的Epoll机制中的epoll_wait函数进行的。

         D. 当往Android应用程序的消息队列中加入新的消息时,会同时往管道中的写端写入内容,通过这种方式就可以唤醒正在等待消息到来的应用程序主线程。

         E. 当应用程序主线程在进入空闲等待前,会认为当前线程处理空闲状态,于是就会调用那些已经注册了的IdleHandler接口,使得应用程序有机会在空闲的时候处理一些事情。

        F.主线程启动时会调用Looper.prepare()方法,会初始化一个Looper,放入Threadlocal中,接着调用Looper.loop()不断遍历Message Queue,

Handler的创建依赖与当前线程中的Looper,如果当前线程没有Looper则必须调用Looper.prepare()。Handler , sendMessage到MessageQueue,Looper不断

从MessageQueue中取出消息,回调handleMessage方法



Hanlder消息循环机制原理图

  • 主线程通过调用Looper.prepare()初始化Looper、MessageQueue、NativeMessageQueue(Native)和Looper(Native);

    Looper和MessageQueue是Java层的类,NativeMessageQueue(Native)和Looper(Native)是JNI层的类。Looper是在prepare()方法被调用的时候初始化的,初始化Looper的时候会跟着初始化MessageQueue,MessageQueue的初始化工作会通过JNI方法将NativeMessageQueue(Native)和MessageQueue关联起来,关联也就是在Native层初始化NativeMessageQueue,然后将NativeMessageQueue对象的收地址存放到MessageQueue对象中的mPtr变量中,NativeMessageQueue初始化的时候会跟着初始化Looper(Native)。

  • 主线程通过调用Looper.loop()进入无限消息循环,保证主线程一直存活;

    消息循环是通过Looper.loop()开始的,这个方法可以理解为是一个死循环的方法,可以通过阻塞和非阻塞两种方式去获取消息。当使用阻塞方式获取消息的时候跟Socket客户端的用法很相似。Looper(Native)是实现阻塞的地方,实现的原理是 I/O 多路复用中的epoll,这里我不详细讲它实现的原理,epoll你大致可以理解为它可以支持多个socket客户端连接,当某个socket客户端收到消息后阻塞过程被恢复,MessageQueue通过检测消息队列可以知道当前是不是有消息可以返回给loop方法。这个阻塞操作只是为了让线程空闲下来,没有其它的实际意义。


插播一、Linux中的通信机制

管道是Linux系统中的一种进程间通信机制,具体可以参考一本书《Linux内核源代码情景分析》中的第6章--传统的Uinx进程间通信。

简单来说,管道就是一个文件,在管道的两端,分别是两个打开文件文件描述符,这两个打开文件描述符都是对应同一个文件,

其中一个是用来读的,别一个是用来写的,一般的使用方式就是,一个线程通过读文件描述符中来读管道的内容,当管道没有内容时,

这个线程就会进入等待状态,而另外一个线程通过写文件描述符来向管道中写入内容,写入内容的时候,如果另一端正有线程正在等待管道中的内容,

那么这个线程就会被唤醒。这个等待和唤醒的操作是如何进行的呢,这就要借助Linux系统中的epoll机制了。

 Linux系统中的epoll机制为处理大批量句柄而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,

它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。但是这里我们其实只需要监控的IO接口只有mWakeReadPipeFd一个,

即前面我们所创建的管道的读端,为什么还需要用到epoll呢?有点用牛刀来杀鸡的味道。其实不然,这个Looper类是非常强大的,

它除了监控内部所创建的管道接口之外,还提供了addFd接口供外界面调用,外界可以通过这个接口把自己想要监控的IO事件一并加入到这个Looper对象中去,

当所有这些被监控的IO接口上面有事件发生时,就会唤醒相应的线程来处理,不过这里我们只关心刚才所创建的管道的IO事件的发生。



要使用Linux系统的epoll机制,首先要通过epoll_create来创建一个epoll专用的文件描述符:




A. 在Java层,创建了一个Looper对象,这个Looper对象是用来进入消息循环的,它的内部有一个消息队列MessageQueue对象mQueue;

B. 在JNI层,创建了一个NativeMessageQueue对象,这个NativeMessageQueue对象保存在Java层的消息队列对象mQueue的成员变量mPtr中;

C. 在C++层,创建了一个Looper对象,保存在JNI层的NativeMessageQueue对象的成员变量mLooper中,这个对象的作用是,当Java层的消息队列中没有消息时,就使Android应用程序主线程进入等待状态,而当Java层的消息队列中来了新的消息后,就唤醒Android应用程序的主线程来处理这个消息。




select、poll、epoll之间的区别总结[整理]

  select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。


总结:

(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。




http://www.cnblogs.com/Anker/p/3265058.html

http://www.2cto.com/kf/201410/339598.html

http://www.xuebuyuan.com/2669171.html

http://blog.csdn.net/shakdy/article/details/51207936


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值