Android Handler 从原理到面试题

温故而知新,可以为师矣。最近又重新复习了一遍Handler的知识点,同时也和一些小伙伴对Handler的常考题做了一些交流,在这个过程中不仅巩固了知识点,也有更为深刻的感悟。所以拿起这篇很久之前写过的帖子。

Handler是Android中非常非常核心的一个模块。简单来说Android一切的正常的业务流转都是基于Handler的,同时Handler也是一中非常好的线程间通信机制,可以用来解决线程间的通信问题。

再开始说面试题之前我们还是先将整个的Handler原理给展示出来,方便我们后面回答问题时作为引用。

https://zhuanlan.zhihu.com/p/133844483
https://blog.csdn.net/ttxs99989/article/details/81814037
https://blog.csdn.net/luoyingxing/article/details/86500542

下面我们会先抛出必考题,然后在阐明原理后,给大家一一解答。欢迎留言补充问题。

  • 子线程到主线程通信都有哪些方式?子线程到主线程的通信原理?
  • Handler内存泄漏的原因是是什么?
  • Looper什么时候进入循环?
  • 子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
  • Handler如何处理发送延迟消息
  • 我们使用Message时应该如何创建它?
  • Handler没有消息处理是阻塞的还是非阻塞的?为什么不会有ANR产生?
  • MessageQueue的内部实现是怎么样的?
  • Handler发消息时的延迟时间是以什么时间作为基准的?

为什么Android程序中的Looper.loop()不会造成ANR异常?

Android 中的Looper如何实现阻塞与唤醒的。
第一个先说阻塞很简单,Looper是一个不断遍历MessageQueue的死循环,当MessageQueue内没有新消息的时候,那么Looper就进入到了阻塞状态。
第二个再说唤醒,唤醒也很简单,我们向主线程的Looper发送一个Message就可以了,这个Message可以是点击,或者Activity声明周期的切换。

以上两个回答在应用层上来说基本没什么问题。不过这不太能体现出我们的对源码理解的深度,比如面试官继续问,为什么没有消息就会进入阻塞状态,这个阻塞状态时如何实现的;同理为什么向主线程的Looper发送一个Message就能唤醒,这个唤醒是如何实现的。

这个其实有点转变问题的意思,但是你不能保证你的面试官不刨根问底,当然他不刨根问底你给他讲解一下来体现自己的对知识理解的深度。来记住这句话:
C++类Looper中的睡眠和唤醒机制是通过pollOnce和wake函数提供的,它们又是利用操作系统(Linux内核)的epoll机制来完成的。

system\core\libutils\Looper.cpp
其中Looper的pollOnce()用于睡眠等待;其中Looper的wake() 用于唤醒。
在Android旧版本使用管道与epoll来完成Looper的休眠与唤醒的
在Android6新版本中使用的是eventfd与epoll来完成Looper的休眠与唤醒的

阻塞:

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData)

===》result = pollInner(timeoutMillis);

======》

//使用epoll进行监控eventfd返回的文件描述符,当有数据时则解除阻塞状态

======》 int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

唤醒:

void Looper::wake()

===》TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));//其中mWakeEventFd就是eventfd返回的文件描述符

在此我们就搞懂了Looper的休眠与唤醒机制。

在Android旧版本使用管道与epoll来完成Looper的休眠与唤醒的

在Android6新版本中使用的是eventfd与epoll来完成Looper的休眠与唤醒的

pipe机制,在没有消息时阻塞线程并进入休眠释放cpu资源,有消息时唤醒线程
Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce() 方法里

那么问题来了, 既然阻塞了主线程,那又是如何响应用户操作和回调 activity 的生命周期的方法的呢?
这里就涉及到 Android 中的 Handler 机制原理 和 IPC 机制, 在此简单概括一下:
首先说一下, 当我们启动了一个 application 时, 此时该 application 进程中并不只有主线程一个线程,还有其他两个 Binder 线程(用来和系统进程进行通信操作,接收系统进程发送的通知),可以用下图介绍:

在这里插入图片描述
当系统收到来自因用户操作而产生的通知时, 会通过 Binder 方式从系统进程跨进程的通知我们的 application 进程中的 ApplicationThread,
ApplicationThread又通过 Handler 机制往主线程的 messageQueue中插入消息
从而让主线程的loop(),Message msg = queue.next()这句代码可捕获一条 message ,然后通过 msg.target.dispatchMessage(msg)来处理消息,从而实现了整个 Android 程序能够响应用户交互和回调生命周期方法(具体实现ActivityThread 中的内部类H中有实现)

其中ApplicationThread 是ActivityThread 的内部类,通过如下代码注入,供系统调用
而至于为什么当主线程处于死循环的 Message msg = queue.next() 这句会阻塞线程的代码的时候不会产生 ANR 异常, 那是因为此时 messageQueue 中并没有消息,无需处理界面界面更新等操作。 因此主线程处于休眠状态,无需占用 cpu 资源, 而当 messageQueue 中有消息时,,系统会唤醒主线程,来处理这条消息。

1.Handler是什么

Handler是Android给我们提供用来更新UI的一套机制,也是一套消息处理机制,我们可以发送消息,也可以通过它处理消息。
如Activity的整个生命周期是由 Activity Message Service 来通知处理的。

2.为什么要使用Handler

Android在设计的时候,就封装了一套消息创建、传递、处理机制,如果不遵循这样的机制就没有办法更新UI信息了。
会抛出异常 信息。

3.Handler如何使用 常用方法

sendMessage(Message message)
Message message=new Message();
message.rag1  int 类型
message.rag2  int 类型
message.what  int 类型
message.object  object类型 使用时需要类型转换
message.setData(Bundle bundle) 传参的方法
同样我们可以复用系统的Message对象
Message message=handler.obtainMessage();
这个时候我们可以直接使用
message.sendToTarget();来发送消息 而target 是在obtainMessage的时候进行的绑定

sendEmptyMessage(int what)
post(Runnable runnable)
postDelayed(Runnable runnable)

new Handler(new Callback(){
public boolean handleMessage(Message msg)
{
		return false;
}
}){
public void handleMessage(Message msg)
{
}
}

添加Handler 执行消息的自定义callback ,返回是否继续执行 Handler 本身的 handleMessage
当然hanlder也可以移除当前的callBack handler.removeCallbacks(runnable);

小技巧我们可以在我们的Runnable中 handler再次条用Runnable从而形成循环调用的效果。类似于轮播的效果。

4.Android为什么只能通过Handler机制更新UI那

最根本的目的就是解决多线程并发的问题
假设如果一个Activity当中,有多个线程去更新UI,并且都没有加锁机制,那么可能会产生更新界面错乱
问题:如果对更新的操作都进行加锁处理的话,可以解决更新界面错乱的问题。
引入的新问题是性能下降和可能带来的死锁。
处于对以上问题的考虑,Android给我们提供了一套更新UI的机制,我们只需要醉熏这样的机制就可以了。
不需要担心多线程的问题,所有的UI更新操作,都在主线程的消息UI列当中去轮询处理。

5.Handler的原理

一Handler封装了消息的发送(主要包括消息发送给谁)
Looper 轮询
内部包含一个消息队列也就是MessageQueue,所有的Handler发送的消息都是走向合格消息队列
Looper .looper方法,就是一个死循环,不断的从MessageQueue取消息,如果有就处理消息,没有就阻塞
二 MessageQueue 就是一个消息队列可以添加消息,并处理消息
三 Handler也很简单,内部会跟Looper进行关联,也就是说在Handler的内部可以好到Looper,找到了Looper也就找到了MessageQueue,在Handler中发送消息,其实就是向MessageQueue队列中发送消息

总结:handler负责发送消息,Looper负责接收Handler发送的消息,并直接把消息回传给Handler自己
MessageQueue就是一个存储消息的容器。

ActivityThread负责创建Activity和管理Activity的声明周期。会默认创建一个线程也就是main线程;
同时会创建一个Looper 。
Threadlocal set把变量放入local中 get得到变量。

自定义与线程相关的Handler 这样的Handler就不在是主线程当中了

public class MyThread extends Thread{
	
	public Handler handler;	
	public Looper loop;
	public void run(){
		loop=Looper.prepare();
		handler=new Handler(){
			public void handlerMessage(Message msg){
			}
		}
		Looper.loop();
	}
}

6.HandlerThread是什么

防止多线程出错的Thread。里面自带Looper,在获取Looper的时候,如果Looper为空则进行等待,直到不空时返回。
防止在主线程处理耗时过长的任务。所以在子线程中进行处理,进行分发消息。

7.如何在主线程给子线程发送消息 去处理任务

新建自己的HandlerThread 使用自己的Looper 分发消息,在HandlerThread的Handler中,调用主线程完成子向主的
调用,在主线程的Handler中调用子线程的Handler完成主调子线程的实现。

8Android中更新UI的集中方式

runOnUiThread();
Handler.post;//其实现原理还是调用sendMessageDelayed方法
Handler.sendMessage;
View.post(Runnable)
归根还是使用Handler的原理进行跟新UI的。

9非UI线程真的不能更新UI吗?

非UI线程是可以更新UI的只是不能在onResume之前进行调用,onResume创建了 ViewRootImp、
但是十分不建议大家使用这种方式进行处理。

关于Can’t create handler inside thread that has not called Looper.prepare;
在我们自己的子线程Handler中没有绑定Looper所导致的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值