• handler


    Handler消息管理机制

     引入:

    这是一张地铁站中电梯的照片,电梯上的人按照顺序排列成一个线性的队列(暂且不考虑混乱的局面和handler机制中的优先级队列,只是做出一个简单的比喻)。那么电梯就把在本来在地下运动的人搬运到了地面,或者把地面的人搬运到了地下。并且在搬运过程中,人们排成一个线性队列,这样就保证先到先处理的原则。

    那么handler机制与此情景类似,一个消息本来是在子线程中运行,但是要在主线程中去处理。那么Handler机制就把子线程的消息传送到主线程中去。对handler进行进一步的抽象,Handler就视为是一个消息管理机制,把消息从一个线程转运到另一个线程;而消息可以泛指Android系统中的消息。

    1.Hanler机制是一个消息传递机制

    作用:在多线程的应用场景中,将工作线程中需要更新UI的操作信息传递到UI主线程中,进而UI主线程实现对UI的更新处理,从而实现工作线程对于UI的更新处理,最终实现异步消息的处理。

    那么为什么要使用Handler消息传递机制呢?

    因为可以保证在多线程并发更新UI的同时保证线程是安全的。

    背景:在Android开发中,为了保证UI操作是线程安全的,规定了只允许UI线程更新Activity里的组件。所以有时也称UI线程为主线程。

    冲突:但是在实际开发中,往往多个线程需要并发操作UI组件,这样会导致UI操作的线程是不安全的。所以由此产生的需求:在多个线程并发操作UI组件的情况下保证线程安全。

    解决冲突的方案:使用Handler消息传递机制,即只允许UI线程更新UI组件,当工作线程需要更新UI时,通过Handler将更新UI的操作消息传递给主线程,从而主线程更新UI操作。

    首先在概念上做出一些解释

    2.handler又是一个类,提供了操作方法的接口。

    在Handler机制中,会包含handler类。首先,这个类中包含了一系列的函数,并且全部是纯虚函数和虚函数,在这个类中不做任何函数的实现,只是作为一个接口而已。在handler类的派生类中会对这些纯虚函数进行实现,对虚函数进行重写。纯虚函数和虚函数的目的就是为了多态,当声明一个handler类类型的对象后,就可以使用这个对象去调用handler派生类中重写或者实现的方法。又由于Handler是一个纯虚类,只能绑定了线程才能够使用。

    3.Handler消息传递机制逻辑运行过程

    在程序中,main函数是程序执行的入口,程序从main函数开始执行时就创建了一个主线程。在每个单个线程中,程序都是自上而下运行。在每个进程中,多个线程共享进程的内存空间等资源,但是每个线程又有自己独有的栈段。

    在main函数执行时,会启动一个主线程并初始化环境,然后主线程中执行完Looper.prepareMainLooper()之后会执行looper.loop()使主线程运行起来。Looper.loop()是一个死循环,就永远无法跳出死循环,并且在死循环之中并没有做什么太多的操作。那么想要执行操作就只能在死循环中进行,把要执行的操作和要做的事情以消息的方式分发出去,这就意味着所有的代码都要Handler机制来管理和运行的。从这个角度看,线程间通信只是Handler机制的一个附属功能而已,它的主要任务是所有代码都要在handler中运行,维护了android的维护之下运行。

    Handler四个大类:Message, Looper, Handler, MessageQueue。实际上还有一个线程类thread。                      

    Handler机制就像传动带机器一样,子线程从一端向传动带上放消息,主线程另一端从传送带上取消息。但是Handler机制又可理解为一个永动机模型,一旦主线程进入Looper.loop之后就相当于闭合了永动机的开关,使得程序进入了无限循环而使其成为永动机。MessageQueue就是永动机上的皮带,永动机开启后就会去不断轮询MessageQueue并从messageQueue中取消息。消息队列中的消息结构体会一直存在,但是其中存储的消息可能为null,也可能包含其它消息。当消息结构体中的消息为null时就执行return操作实现退出,就相当于下图中开关Looper.loop开关打开使永动机关闭,所以消息为null的消息结构体一般由quit等操作发出。那么也就是Looper.loop()无限循环函数不会主动退出,除非系统得到退出指令而使得从MessageQueue中取出的结构体为null。

    同时,当消息队列MessageQueue为空时,Looper.loop()无限循环就进入了blocked状态。

    handler中的部分函数调用关系如下图

    由图可知,无论什么函数,到最后都会去调用sendMessageAtTime函数。这个函数会进一步去调用Handler.enqueueMessage函数,再进一步去调用MessageQueue.enqueueMessage函数。

    1.把子线程的Message放入到主线程的MessageQueue中去

    首先在主线程中实例化一个handler对象,这只是一个类的接口,可以用它去调用其派生类中的方法。这个handler对象会与主线程中的Looper.loop和MessageQueue绑定。在工作进程中,会去调用主进程中这个handler类对象中的操作方法,无论调用任何方法去传递Message, 以sendMessage()函数为例,最后都会去调用sendMessageAtTime函数,经过层层调用最后都会去调用MessageQueue.enqueueMesage函数。消息队列MessageQueue只存在于主进程中,MessageQueue.enqueueMessage函数就是把子线程中的消息结构体Mssage插入到主进程的消息队列MessageQueue去。注意,这里是把Message插入到MssageQueue中,而不是从队列尾部执行入队操作。再回顾Handler的逻辑图中即可明白。所以无论是sendMessage,还是postMssage都是把消息放入到消息队列的传送带上去。

    值得注意的是,这里的消息队列是以单向链表的优先级队列来实现的,单向链表与消息队列的FIFO不同,它可以在队列中的任何位置执行插入。在此处,优先级队列的优先级标准是执行时间,如图所示。消息队列中已经有消息M1,M2,M3,现在插入消息M4,M4的时间小于M3。这样形成的消息队列中,队头的M1是最先执行的操作,队尾的M3是最晚的操作。

    那么怎么来定义执行时间呢?这sendMessage函数调用之后会去调用sendMessageDelayed函数。调用时若没有传入执行时间,则默认延迟时间为0,就会调用系统时间函数systemClock.uptimeMillis()传入执行;若传入了执行时间,则按照延时时间 + systemClock.uptimeMillis()插入到优先队列中

     2.那么消息队列中消息结构体是怎么出队列呢?

    由上可知,MessageQueue就是传动带中的皮带,那么皮带是怎么滚动呢?从逻辑图中可以得知,闭合开关Looper.loop即可,在程序中就是thread调用Looper.loop函数。Looper.loop函数是一个死循环,每次循环都会调用Message.next()函数,它的返回值是Message类结构体型的,这个函数实现的功能就是从MessageQueue中队头位置取出Message。取出Message后会使用msg结构体变量来接收取出的消息结构体(程序中原语句为 Message msg = queue.next())。在Looper.loop()函数中执行完取消息队列队头元素并赋给结构体变量之后会执行msg.target.dispatchMessage(msg)函数。这个函数会根据消息的归属者找到相应的Handler对象,调用该Handler对象的msg.dispatchMessage(msg)函数,进而去回调handleMessage(Msg)函数,这个函数就是我们要找的处理消息的终结。注意,最后是主线程去调用这个handl.Message(Msg)函数,所以是主线程来执行这个操作。

    上述描述的是整个流程正常运行的循环,但是MessageQueue可能为空,当MessageQueue为空时,整个循环过程就会block,进而使得Looper.loop()死循环函数睡眠。

    下面总结上述逻辑上的操作

    (子线程)handler->sendMessage ->messageQueue.enqueueMessage(把消息结构体放入消息队列中) 

    Looper.loop()->messageQueue.next() ---> handler.dispatchMessage() --->(主线程)handler.handleMessage。

    那么是怎么体现消息从子线程到另一个线程中呢?注意到,在同一进程中,多个线程共享进程的内存空间,message结构体就是保存在进程中的内存中。所以保存message的内存可以在进程中很轻松的从一个地方转移到另一个地方(如关键字new)。

    讲解完流程之后再借助网上大神画的流程图做出一个梳理

    注意:我们在这里讲的Handler是一整套消息传递机制。但在看这个消息流程图时,我们应该从另一种眼光去理解Handler。

    首先Handler应该为一个类,这个类可能只是提供一个接口,其派生出的子类对其中的函数虚函数进行了重写,从而定义了各种的方法。子线程调用handler中的handler.sendMessage(message)将数据传送给主线程中的消息队列messageQueue中,主线程在无限无限循环messageQueue时会去调用msg.target.dispatchMessage(msg)去调用Handler的handlerMessage(msg)方法,注意,此时调用这个方法的不再是子线程,而是主线程。这样就在主线程中实现了UI的操作。

    特别注意:

    线程、Looper、Handler之间的关系应该是:一个线程只能绑定一个Looper,但是可以有多个Handler;1个Looper可绑定多个Handler,一个Handler只能绑定一个Looper。

  • 相关阅读:
    socket:套接字
    hashlib 加密
    面向对象总结
    类的内置方法
    反射
    类中的三个装饰器方法
    text
    模块
    练习1
    内置函数
  • 原文地址:https://www.cnblogs.com/hxhlrq/p/14091034.html
Copyright © 2020-2023  润新知