推广 热搜: page  使用  音视频  个数  搜索引擎  企业  选择  百度  可以  父亲 

Android 线程通信之handler机制

   日期:2024-12-31     作者:1h8ql    caijiyuan   评论:0    移动:http://ww.kub2b.com/mobile/news/17104.html
核心提示:1. Handler被设计出来的原因?有什么用? Handler机制主要为了解决以下2个问题 1). 不要阻塞UI线程; 2). 不

1. Handler被设计出来的原因?有什么用

Handler机制主要为了解决以下2个问题

1). 不要阻塞UI线程
2). 不要在UI线程之外访问UI组件,即不能在子线程访问UI组件,只能在UI线程访问。

2. Handler,Message,MessageQueue,Looper4大核心类功能

2.1 Looper

问题: Looper是怎么创建的

在线程里面创建的。然后保存在ThreadLocal中。

2.1.1 对于Looper主要是prepare()和loop()两个方法。

首先看prepare()方法:主要是通过ThreadLocal绑定Looper

 

问题: Looper怎么只有一个ThreadLocal保证了一个线程中只有一个Looper实例,ThreadLocal里面是一个map,存放key和value

2.1.2 ThreadLocal的作用:在线程里面保存数据
2.1.3 Looper.loop()来建立消息循环looper相当于事件驱动,心跳机制

Loop方法:里面一个For循环,阻塞队列,不断的取消息

总结: Looper主要作用

1)、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2)、 loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。

问题: 可以在子线程直接new 一个Handler 吗?怎么做?

主线程为什么不用

不可以,因为在主线程中,Activity 内部包含一个Looper 对象,它会自动管理Looper,处理子线程中发送过来的消息。而

对于子线程而言,没有任何对象帮助我们维护Looper 对象,所以需要我们自己手动维护。所以要在子线程开启Handler 要先创建Looper,并开启Looper 循环****

主线程的looper在哪里启动的?

ActivityThread的main()函数! ActivityThread 的动力是什么

2.3Message

 

问题: Message 可以如何创建?哪种效果更好,为什么?

直接生成实例Message m = new Message

通过Message m = Message.obtain

通过Message m = mHandler.obtainMessage()

后两者效果更好,因为Android 默认的消息池中消息数量是10,而后两者是直接在消息池中取出一个Message 实例,这样做就可以避免多生成Message 实例。

消息的享元模式,有一个消息池

Message是什么数据结构?链表,一直指向下一个。

消息机制里需要频繁创建消息对象(Message,因此消息对象需要使用享元模式来缓存,以避免重复分配 & 回收内存。

具体来说,Message 使用的是有容量限制的、无头节点的单链表的对象池

为啥:对象池就一个实体,不是一个集合

从代码中我们可以看到,Message的复用机制没有使用任何一种数据结构,如linkedArrayList,而是通过Message对象内部的spool和next字段,通过指针的方式来进行对象管理,不得不说,是一种非常巧妙的设计方式,一来降低了设计复杂度,而且由于没有创建额外的数据容器来管理对象,减轻了内存的压力,实现了轻量化的目的

 

2.2MessageQueue

2.3.1 问题: MessageQueue是怎么创建出来的

通过looper

 
2.3.2 MessagerQueue:单链表的数据结构
2.4 Handler: 4者的关联:

Handler是怎么创建的:手动创建,自己创建的

 

Handler的作用就是:调度消息和runnable对象去被执行;使动作在不同的线程中被执行。

handler不仅可以分发消息,还可以分发runable。把runable封装成消息

消息的存放: 把消息添加到了MessageQueue队列里面,把MSG添加到了MessagerQueue里面。所有的消息都是通过sendMessageAtTime();

 

存放消息总结

注释1:p是队列头部,满足3个条件则把消息放到队列头部

1.队列中没有消息,p==null 2.入队的消息没有延时 3.入队的消息的延时比队列头部的消息延时短

注释2:消息插入到链表中,需要移动链表,对比消息的延时,插入到合适的位置

消息的分发

 

looper方法:调用dispatchMessager方法:dispatchMessager方法再调用handlemessage方法

msg.target: 就是Handler对象。看到message,持有handler的引用

Message: 消息

2.5 4者的关系:

问题:Handler和Looper是什么关系

hander创建的时候需要传入一个looper,looper在哪,handler在哪。默认不传,在哪个线程new,就是哪个线程的Looper

handler构造方法里面的looper为什么不直接new? 不能保证唯一性

 

这几个角色是如何协同工作的呢?简单概括为下面四个步骤

  1. handler发送消息到message queue,这个消息可能是一个message,可能是一个runnable
  2. looper负责从message queue取消息
  3. looper把消息dispatch给handler
  4. handler处理消息(handleMessage或者执行runnable)

handler和looper的关系有点类似于生产者和消费者的关系,handler是生产者,生产消息然后添加到message queue;looper是消费者,从message queue取消息。(生产者消费者模式)

流程总结

初始化过程:prepare===创建Looper对象,创建messageQueque对象。

存放消息: 发送消息的流程:handler.sendmesssage()------messageque存放msg

取消息的流程:得到looper------得到messageque-------得到msg,然后通过handle调用dispatch方法。然后调用handler方法,我们需要重写的

4者的关联图:

问题:

一个线程有多少个handler

一个线程有多少个looper

如何保证只有一个looper

一个handler,一个messageque,可以对应多个线程

问题: 既然有多对一,或者一多的问题,多个handle可以发消息给msgqueue。怎么保证消息安全的

加了锁,synizch;存和取都加了。

问题: Handler如何保证线程安全的

:在Handler发送消息时,会将Message存入MessageQueue消息队列中,即enqueueMessage方法,这个方法中,有一个synchronized(this){}的方法块,同时在Looper.loop()方法中的MessageQueue.next()方法中也是使用synchronized加锁的方式来保证存取Message的线程安全的。

 

3. Handler:为什么能切换线程

1). 用了主线程都looper。 Handler 是在他关联的 Looper 对应的线程中处理消息的。(主线程的looper*

Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了。

因此 Looper 所处的线程也就决定了你 Handler 提交任务执行所在的线程。

2). 真正的原因:messagequeue的,队列可以实现内存共享。然后looper觉得是发哪个线程

服务端开发里面的消息队列本身也是这个原理,队列对所有线程都是可见的,大家都可以往里面 enqueue 消息

问题: Android 线程A与线程B如何通信的

类似子线程往主线程发消息一样,其实android中线程通信无非就是handler和looper的操作。

需要注意的就是要loop.prapare()和looper.loop()。不调用Loop.loop()方法的话,是收不到消息的

问题: handler运行在哪个线程

看looper在哪个线程,handler用哪个?

因为:创建handler的时候传了一个looper

4. 为什么死循环不会ANR—Looper

要处理4大组件里面的事情,并不希望立马退出。

主线程的死循环一直运行是不是特别消耗CPU资源呢

1).并不是,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollonce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质是同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

2).耗时操作本身并不会导致主线程卡死, 导致主线程卡死的真正原因是耗时操作之后的触屏操作, 没有在规定的时间内被分发。

Looper 中的 loop()方法, 他的作用就是从消息队列MessageQueue 中不断地取消息(调用messageQue的next()方法), 然后将事件分发出去

3).ANR和死循环没有关系。2个完全不是个东西。

只要有消息处理,没有消息处理会阻塞。

问题:阻塞不会导致ANR吗

不会。ANR和阻塞2个事情,ANR是因为没用 消息及时处理。ANR有消息没用及时处理:因为埋炸弹的问题

5. 唤醒和阻塞,Epoll机制是如何

5.1 什么时候阻塞

looper方法里面 没有消息的时候,调用nativePollOnce,休眠,block阻塞 。直到添加新消息

当消息队列为空时,这里会导致阻塞,直到有消息加入消息队列,才会恢复//这里是native方法,利用的是Linux管道(Pipe)机制阻塞nativePollonce(ptr, nextPollTimeoutMillis);

 
5.2 什么时候唤醒

将Message添加到队列时,框架会调用enqueueMessage)方法,里面也有个死的for循环,该方法不仅会将消息插入队列,还会调用native static void nativeWake(long

消息来的时候,调用nativeWake方法唤醒next()方法。

 

问题: epoll机制是怎么样子的?

nativePollOnce和nativeWake的核心魔力发生在native(实际上是C ++)代码中

总结:存消息,需要唤醒,msgque中的方法 。取消息。looper。需要阻塞,休眠。他们都是通过native.

6. 延时消息如何处理的-------messageeQueue

6.1 问题: 最新的一条消息, 还没到时间如何触发

MessageQueue的next)方法

msg != null 我们看下这部分,如果当前时间小于头部时间(消息队列是按时间顺序排列的

那就更新等待时间nextPollTimeoutMillis,等下次再做比较

如果时间到了,就取这个消息并返回。

如果没有消息,nextPollTimeoutMillis被赋为-1,这个循环又执行到nativePollOnce继续阻塞

问题: 延时消息如何处理的-

handler.postDelay并不是先等待一定的时间再放入到MessageQueue中,而是直接进入MessageQueue,以MessageQueue的时间顺序排列和唤醒的方式结合实现的

按照触发时间进行排序,队头的时间最小、队尾的时间最大

6.2。问题: sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message? 如果最近的Message未到达发送的时间则阻塞。
 
6.3 如果在当前线程内使用Handler postdelayed 两个消息,一个延迟5s,一个延迟10s

问题1: 如果在当前线程内使用Handler postdelayed 两个消息,一个延迟5s,一个延迟10s

然后使当前线程sleep 5秒,以上消息的执行时间会如何变化

:照常执行

扩展:sleep时间<=5 对两个消息无影响,5< sleep时间 <=10 对第一个消息有影响,第一个消息会延迟到sleep后执行,sleep时间>10 对两个时间都有影响,都会延迟到sleep后执行。

总结:如果sleep的时间<=延时的时间,是没有影响的。大于就有影响

问题: 假如发了一个延时消息5s,然后这个时间没有其他消息了。怎么处理

通过延时,阻塞。

问题2: 聊下Handler postDelay的底层原理。如果灭屏前调用SystemClock.uptimeMillis然后灭屏,等10秒亮屏,在打印SystemClock.uptimeMillis,这两个的时间差是10s吗,为什么。

举例

1).先 一个消息sleep.3s, 然后另外一个消息postDelay1s

分析: 会先sleep3s 。然后再进行操作。发送延时1s的消息,但是messageque取的时候,有一个条件

假如开始开机时间是1s。

因为msg.when=2s

然后运行next的方法,开机时间是1+3=4s. 然后现在时间(now)>执行时间(msg.when)---->把开始没有执行完的消息,立马执行**

7. handler内存泄漏的链路

7.1 产生的原因

Handler造成Activity泄漏,用弱引用真的有用么?

handler造成内存泄漏是因为在Activity销毁的时候还有未执行完的任务

产生原因:第一内部类持有activity的引用

7.2 第二:引用链关系:ThreadLocal---->Looper------> messageque------->msg---------handler----activity
7.3. 解决办法

静态static可以解决内存泄漏

  • 使用弱引用也可以解决内存泄漏,但是需要等到handler的中任务都执行完,才会释放activity内存,不如直接static释放的快

问题: 为何handler要定义为static的同时,还要用WeakReference 包裹外部类的对象?**

这是因为我们需要使用外部类的成员,可以通过"activity. "获取变量方法等,如果直接使用强引用,显然会导致activity泄露。

handler造成内存泄漏有 两种方案:一种是业务逻辑上,在activity销毁的时候移除所有未执行的任务。

一种是从GC上,通过static的Handler或者弱引用解决。但是单独的使用弱引用性能不是太高

最好的办法:要把handler也置空,比如在线程延迟3000之后延时消息。

7.4 问题: 在子线程,如果消息轮询完了。线程处于什么状态?****Looper中的quitAllowed字段是啥?有什么用

要通过looper。quit)。清空消失,不然一直处于阻塞状态?looper有个for循环,处于block状态。然后子线程一直在运行,容易导致内存泄漏。

调用quitAllowed().唤醒。然后可以退出。

比如:ThreadHandler

  • 主线程中,一般情况下肯定不能退出,因为退出后主线程就停止了。因为系统AMS等等。要处理消息,不能停止。所以是当APP需要退出的时候,就会调用quit方法,涉及到的消息是EXIT_APPLICATION,大家可以搜索下。* 子线程中,如果消息都处理完了,就需要调用quit方法停止消息循环。**
8. handler消息屏障是干嘛的
8.1。屏障消息就是为了确保异步消息的优先级

同步屏障: 往消息队列插入一个同步屏障消息,这时候消息队列中的同步消息不会被处理,而是优先处理异步消息

同步屏障的处理代码在的方法

同步屏障和异步消息有具体的使用场景吗

8.2 消息屏障的应用

1).UI相关的操作优先级最高,比如消息队列有很多没处理完的任务,这时候启动一个Activity,当然要优先处理Activity启动,然后再去处理其他的消息,同步屏障的设计堪称一绝吧。

2).view刷新机制

3)关于Handler有一个需求,一个消息要立刻执行,要怎么做**

9. IdleHandler是啥?有什么使用场景
9.1 handler源码种的出现
 
9.2 IdleHandler应用场景:leakcanery,启动流程里面:ActivityThread里面的handlerResumeActivity()方法
 

当MessageQueue 阻塞时,即当前线程空闲时,会回调IdleHandler中的方法

:a,添加IdelHandler时,消息队列不为空,当消息处理完或者剩下消息还没到触发时间,会回调方法 b,当添加IdelHandler时,消息队列为空,则当时不会触发回调

当IdelHandler接口返回false时,表示该IdelHandler只执行一次

批量任务,任务密集,且只关注最终结果

9.3 具体使用场景: :启动优化。

例如,在开发一个IM类型的界面时,通常情况下,每次收到一个IM消息时,都会刷新一次界面,但是当短时间内, 收到多条消息时,就会刷新多次界面,容易造成卡顿,影响性能,此时就可以使用一个工作线程监听IM消息,在通过添加IdelHandler的方式通知界面刷新,避免短时间内多次刷新界面情况的发生。

ok,综上所述,IdleHandler就是当消息队列里面没有当前要处理的消息了,需要堵塞之前,可以做一些空闲任务的处理。**

10. handler.post(Runnable) runnable是如何执行的

开启的runnable会在这个handler所依附线程中运行,而这个handler是在UI线程中创建的,所以自然地依附在主线程中了。 postDelayed(new Runnable()) 而没有重新生成新的 New Thread) 问题: Handler 的 post(Runnable) 与 sendMessage 有什么区别

所以post(Runnable) 与 sendMessage的区别就在于后续消息的处理方式,是交给还是 或者问题: Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一样?为什么这么设计

  • 如果为true,则不再执行Handler.handleMessage
  • 如果为false,则两个方法都要执行。

handler的Callback和handlemessage都存在,但callback返回true handleMessage还会执行么

Dispathch方法

可以看到,除了在Handler#handleMessage(…)中处理消息外,Handler 机制还提供了两个 Callback 来增加消息处理的灵活性。具体来说,若设置了Message.Callback则优先执行,否则判断Handler.Callback的返回结果,如果返回false,则最后分发到Handler.handleMessage(…)

11. handler检测ANR原理

见卡顿优化文章:

2.手写可以跨进程的Handler通信方案,没错跨进程。既可以线程通信,又可以跨进程通信

13. 扩展之 TheadLocal

13.1 实现一个线程本地的存储,也就是说,每个线程都有自己的局部变量。所有线程都共享一个ThreadLocal对象,但是每个线程在访问这些变量的时候能得到不同的值,每个线程可以更改这些变量并且不会影响其他的线程,并且支持null值。
13.2 问题: 为什么要设计ThreadLocal?

ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制。

所以ThreadLocal既不是为了解决共享多线程的访问问题,更不是为了解决线程同步问题,ThreadLocal的设计初衷就是为了提供线程内部的局部变量,方便在本线程内随时随地的读取,并且与其他线程隔离。

13.3 使用场景(3处)

1).Android的消息机制主要是指Handler的运行机制

一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。比如对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper

2).开源框架EventBus存储当前线程下的发送事件队列状态也是采用ThreadLocal

3).ActivityThread以及AMS中都用到了ThreadLocal。

13.4 原理:

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本

14. 扩展之HandlerThead

作用:.HandlerThread本质上就是一个普通Thread,只不过内部建立了Looper.

里面做了线程优先级的处理,主要是封装了looper,不用自己调用Looper的方法

场景:用于做完一件事,做另外一件事。

问题:HandlerThread里面为什么要用同步锁。notify和wait到底什么时候执行

notify后不会里面执行wait后面的代码。而是线程就绪状态。要等synize执行完成才执行

 
15. 扩展之IntentService
15.1 IntentService的优点

一方面不需要自己去new Thread了

另一方面不需要考虑在什么时候关闭该Service了

缺点

从我们的示例和源码分析中可以看出来。对于通过IntentService来执行任务他是串行的。也就是说只有在上一个任务执行完以后才会执行下一个任务

因为Handler中将消息插入消息队列,而队列又是先进先出的数据结构。

所以只有在上个任务执行完成以后才能够获取到下一个任务进行操作。**在这里也就说明了对于高并发的任务同过IntentService是不合适(单线程)****

15.2 功能相当于:Service+Thread。内部实现handlerThread+service+Handler
1.53 原理

问题: . handleMessage中回调onHandleIntent(intent),onHandleIntent是运行在什么线程里面

onHandleIntent执行完成之后就结束Service自己,如下代码:在onHandleIntent调用之后执行StopSelf()

 
16. 扩展之 AsyncTask

在Android 1.6之前的版本,AsyncTask是串行的在1.6至2.3的版本,改成了并行的。

在2.3之后的版本又做了修改,可以支持并行和串行,当想要串行执行时,直接执行execute()方法,如果需要并行执行,则要执行executeonExecutor(Executor)。

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

本文地址:http://ww.kub2b.com/news/17104.html     企库往 http://ww.kub2b.com/ ,  查看更多

特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。

 
 
更多>同类最新文章
0相关评论

文章列表
相关文章
最新动态
推荐图文
最新文章
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号