Handler 总结

Ophira ·
更新时间:2024-11-14
· 583 次阅读

每个线程最多只有一个Lopper,也最多只能有一个MessageQuque,当一个线程中有多个Handler时通过msg.target保证MissageQueue中的每个msg交由发送message的handler进行处理,msg有三个属性,msg.when msg.what msg.target

Handler机制原因,主线程looper.loop()为什么不阻塞主线程?

Handler,Message,looper 和 MessageQueue 构成了安卓的消息机制,handler创建后可以通过 sendMessage 将消息加入消息队列,然后 looper不断的将消息从 MessageQueue 中取出来,回调到 Hander 的 handleMessage方法,从而实现线程的通信。

从两种情况来说,第一在UI线程创建Handler,此时我们不需要手动开启looper,因为在应用启动时,在ActivityThread的main方法中就创建了一个当前主线程的looper,并开启了消息队列,消息队列是一个无限循环,为什么无限循环不会ANR?因为可以说,应用的整个生命周期就是运行在这个消息循环中的,安卓是由事件驱动的,Looper.loop不断的接收处理事件,每一个点击触摸或者Activity每一个生命周期都是在Looper.loop的控制之下的,looper.loop一旦结束,应用程序的生命周期也就结束了。我们可以想想什么情况下会发生ANR,第一,事件没有得到处理,第二,事件正在处理,但是没有及时完成,而对事件进行处理的就是looper,所以只能说事件的处理如果阻塞会导致ANR,而不能说looper的无限循环会ANR

另一种情况就是在子线程创建Handler,此时由于这个线程中没有默认开启的消息队列,所以我们需要手动调用looper.prepare(),并通过looper.loop开启消息

Loop.loop为什么设计成死循环?为什么没有阻塞UI?它死循环的话那cpu不始终被他消耗?

1)首先要明白,android是事件驱动的,所以设计在Loop.loop中不断接收事件,处理事件;没有退出之前一直要循环等待处理事件,而不是处理一次之后结束,那后面的事件就没法处理了。

2)阻塞UI是指事件没有及时处理,导致UI被阻塞没有及时更新;Loop.loop死循环是用来循环处理事件的,只有当事件来不及处理才可能导致UI阻塞,完全是两个不同的概念。

3)主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞睡眠。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

场景比喻:handler发送消息、处理消息,Loop.loop循环调用,可以类比去银行办业务

当我们点击界面或者子线程使用handler发送一个消息处理一件事,就相当于要去银行柜台办理存款业务

1)首先你需要排队,即消息进入messagequeue

2)银行柜台员(Loop)不断的(for循环)收取(loop)人民币(message),检查之后将你的余额刷新,那么你手机中就可以看到钱存进入了

3)如果存了一麻袋硬币,柜台员(Loop)就给人去数,太多了数不过来,排队的人的人民币(message)都等着得不到处理,这个时候就UI阻塞卡顿了

4)当排队的人都处理完时,柜台员(Loop)就会休眠,再有新信息就会被唤醒继续处理。

如果Message会阻塞MessageQueue的话,那么先postDelay10秒一个Runnable A,消息队列会一直阻塞,然后我再post一个Runnable B,B岂不是会等A执行完了再执行?正常使用时显然不是这样的,那么问题出在哪呢?

MessageQueue会根据post delay的时间排序放入到链表中,链表头的时间小,尾部时间最大。因此能保证时间Delay最长的不会block住时间短的。当每次post message的时候会进入到MessageQueue的next()方法,会根据其delay时间和链表头的比较,如果更短则,放入链表头,并且看时间是否有delay,如果有,则block,等待时间到来唤醒执行,否则将唤醒立即执行。

所以handler.postDelay并不是先等待一定的时间再放入到MessageQueue中,而是直接进入MessageQueue,以MessageQueue的时间顺序排列和唤醒的方式结合实现的。使用后者的方式,我认为是集中式的统一管理了所有message,而如果像前者的话,有多少个delay message,则需要起多少个定时器。前者由于有了排序,而且保存的每个message的执行时间,因此只需一个定时器按顺序next即可

1 、可以在子线程获取数据(长时间下载的图片)并把值赋给全局变量,然后发送消息Message通知handler,最后在handler中接收通知并显示使用这个全局变量。

主线程子线程变量啥的都可以相互赋值,赋值是赋值,handler是发消息通知主线去操作,操作,操作!不是用它发送接收到的数据的。handler可以从子线程,也可以从主线程发送消息,本质并无区别,都是发送个消息,handler收到后执行。主线程发送handler情况,发送延时handler,代码继续往下一直执行,等到延时时间到了,强占cpu同时运行handler发送的信息和主线程代码,

2、handler.postDelayed(new Runnable()) ;这个Runnable()中运行的代码是在主线程中执行的,准确说这个开启的runnable会在这个handler所依附线程中运行,而这个handler是在UI线程中创建的,所以 自然地依附在主线程中了。

3、handler方法很多,核心就两个方法 1、sendMessageDelayed()和removeMessage();发送消息和移除消息

    post()、postDelay()removeCallbacks(Runnable)(移除Runnable)、sendMessage()、sendMessageDelayed()、sendEmptyMessage()removeMessage()底层均为sendMessageDelayed,如:post(Ruannabel)就是在底层将Ruannable包装为message,再掉调用sendMessageDelayed将参数传入,个人感觉执行单个任务或者重复执行单个任务用handler.post(Runnable ),执行多种不同任务,用message 。  https://blog.csdn.net/zhanglianyu00/article/details/70842494

4、Handler、Looper、Message

Looper:Handler在使用之前必须调用Looper.prepare(),使用之后必须调用Looper.loop();否则消息运行不起来,上图中Looper不loop起来,消息就无法到Handler

     依附主线程的Handler,底层已经调用了Looper.prepare()和Looper.loop(),无需额外调用,直接在主线程创建、使用就行,从主线程或者子线程发送消息,收到消息后可以处理消息或者更新UI

    依附子线程Handler,底层没有调用Looper.prepare()和Looper.loop(),所以在子线程中new Handler之前要调用Looper.prepare()之后调用Looper.loop(),通过子线程或主线程发送过来的消息,仅能处理逻辑,不能更新UI。因为这个Handler依附子线程,子线程不能更新UI,如果子线程中创建Handler没有调用Looper.prepare()。会导致crash。另外,主线程能弹Toast子线程不能弹的原因是主线程有一个looper对象和主线程绑定,现在子线程中创建了looper对象,也就能在子线程中弹Toast了,不过子线程中调用Looper.loop()之后会开启一个死循环对消息队列中的消息进行遍历,所以子线程永远不会退出,如果子线程有Toast这种持有MainActivity引用的代码,就会内存泄漏。解决办法:在prepare和loop调用之间,mLooper = Looper.myLopper();在Destory中将mLooper退出,调用mLooper.quit()和mLooper = null;没有looper之后,就没有就没有队列循环,子线程执行完run之后就自动退出(真实性待确认)https://www.cnblogs.com/kebibuluan/p/7209072.html?utm_source=itdadao&utm_medium=referral

    在子线程中创建依附于主线程的Handler,new Handler(Looper.getMainLooper()),也是可以从任意线程发送信息,因为此Handler依附于主线程,handleMessage中可以处理消息或更新UI。参考:https://blog.csdn.net/dfskhgalshgkajghljgh/article/details/52601802

    在主线程中创建依附于子线程的Handler;在子线程中获取到子线程looer,mLooper = Looper.myLopper(),子线程start(),主线程中new Handler(mThread.mLooper) ,但是这样会报空指针错,子线程start后,mLooper不一定马上运行,可能为空,那么这个问题如何避免,那么这个问题如何避免呢,就用到了HandlerThread

HandlerThread 

new Thread(){}.start();的方式来开辟一个新的线程。但是如果我们想要多次执行任务的时候,通过这种方式我就会创建多个线程,这样会使我们的程序运行起来越来越慢。所以必须用Thread加Handler,多次任务的时候,只需主线程向子线程发送消息就行,可以使用在子线程中创建依附于子线程的Handler,在主线程中创建依附于子线程的Handler及HanderThread。谷歌帮忙封装了HanderThread,这个应该用起来更简便,handlerThread = new HandlerThread(“子线程名”);handler = new Handler(handlerThread.getLooper());每次需要执行任务的时候就调用handler.post(Runnable {run .....}); 在run中执行子线程  https://www.jianshu.com/p/5b6c71a7e8d7

 

Handler 内存泄漏

见内存泄漏片

总参考:https://www.imooc.com/article/25134?block_id=tuijian_wz

zhangzhuo1024 原创文章 23获赞 8访问量 2万+ 关注 私信 展开阅读全文
作者:zhangzhuo1024



handler

需要 登录 后方可回复, 如果你还没有账号请 注册新账号