常见面试题:
Handler相关 一.谈一谈你对handler的认识,例如:机制和实现等。 handler机制图解: handler常见使用过程:①.子线程向主线程发送消息:
- 在主线程中新建Handler实例,并重写handleMessage()方法用于处理子线程传过来的message;
- 通过Message.obtain()方法获取到消息msg,然后将需要传递给主线程的信息通过msg的what、obj等字段传递;
- 通过handler的sendMessage发送msg消息,然后在handleMessage中处理消息。
②.主线程向子线程发送消息:
- 主要多了第一步中在新建Handler实例之前需要调用Looper.prepare();新建实例之后需要调用Looper.loop();最后子线程应该主动安全退出调用looper.quitSafely()方法;其他步骤和上面一致。
①.第一步调用Looper.prepare()方法:
- 如果有Looper则直接报错,如果没有创建Looper并且存储在ThreadLocal中;
- 在Looper的构造方法中可以看见创建MessageQueue实例,此处可以看出一个线程对应一个Looper对应一个Message。
主线程中不需要执行此步骤是因为在ActivityThread.main中已经调用了此方法。
②.新建handler实例并且重写handleMessage()方法处理消息,创建Handler对象主要有以下几个内容:
- 创建Handler对象
- 得到当前线程的Looper对象,并判断是否为空
- 让创建的Handler对象持有Looper、MessageQueue、Callback的引用
③.调用Looper.loop()用于启动消息队列中的for轮询,消息队列中有消息就立马取出通过msg.target.dispatchMessage分发到Handler中处理消息,处理消息handleMessage方法优先级最低,如果没重写前面两个Callback就会走到handleMessage中处理消息。
④.通过Message.obtain()方法获取到消息msg,此处obtain()方法是尝试重消息池中拿消息,如果没有拿到就新建消息。
⑤.通过handler发送消息,不管是sendMessage()方法还是post()等方法本质都是:把Message加入到Handler中的MessageQueue中去。
二.一个线程中最多有多少个Handler,Looper,MessageQueue?可以很多个Handler,最多一个Looper,最多一个MessageQueue。
三.Looper死循环为什么不会导致应用卡死,会耗费大量资源吗?从前面的主线程、子线程的分析可以看出,Looper会在线程中不断的检索消息,如果是子线程的Looper死循环,一旦任务完成,用户应该手动退出,而不是让其一直休眠等待。(引用自Gityuan)线程其实就是一段可执行的代码,当可执行的代码执行完成后,线程的生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder 线程也是采用死循环的方法,通过循环方式不同与 Binder 驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。Android是基于消息处理机制的,用户的行为都在这个Looper循环中,我们在休眠时点击屏幕,便唤醒主线程继续进行工作。
主线程的死循环一直运行是不是特别消耗 CPU 资源呢?其实不然,这里就涉及到 Linux pipe/epoll机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollonce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
四.系统为什么不建议子线程中更新UI?这是因为 Android 的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那么为什么系统不对UI控件的访问加上锁机制呢?缺点有两个:
-
首先加上锁机制会让UI访问的逻辑变得复杂
-
锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
所以最简单且高效的方法就是采用单线程模型来处理UI操作。
五.如何处理Handler使用不当造成的内存泄漏?-
有延时消息,在界面关闭后及时移除Message/Runnable,调用handler.removeCallbacksAndMessages(null)
-
内部类导致的内存泄漏改为静态内部类,并对上下文或者Activity/Fragment使用弱引用。
同时还有一个很关键的点,如果有个延时消息,当界面关闭时,该Handler中的消息还没有处理完毕,那么最终这个消息是怎么处理的?经过测试,比如我打开界面后延迟10s发送消息,关闭界面,最终在Handler(匿名内部类创建的)的handMessage方法中还是会收到消息(打印日志)。因为会有一条MessageQueue -> Message -> Handler -> Activity的引用链,所以Handler不会被销毁,Activity也不会被销毁。
六.如何创建Message实例?-
通过 Message 的静态方法 Message.obtain() 获取;
-
通过 Handler 的公有方法 handler.obtainMessage();
所有的消息会被回收,放入sPool中,使用享元设计模式。
七.Handler的消息优先级,有什么应用场景?在Looper执行消息循环loop()时会执行下面这行代码,msg.targe就是这个Handler对象。msg.target.dispatchMessage(msg);
1.如果Message这个对象有CallBack回调的话,这个CallBack实际上是个Runnable,就只执行这个回调,然后就结束了,然后调用handleCallback,而handleCallback方法中调用的是Runnable的run方法。
2.如果Message对象没有CallBack回调,进入else分支判断Handler的CallBack是否为空,不为空执行CallBack的handleMessage方法,然后return。
3.最后才调用到Handler的handleMessage()函数,也就是我们经常去重写的函数,在该方法中做消息的处理。
使用场景
可以看到Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true),那么 Handler 的 handleMessage(msg) 方法就不会被调用了;如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。我们可以利用CallBack这个拦截来拦截Handler的消息。
场景:Hook ActivityThread.mH , 在 ActivityThread 中有个成员变量 mH ,它是个 Handler,又是个极其重要的类,几乎所有的插件化框架都使用了这个方法。
Handler.post(Runnable r)方法的执行逻辑
我们需要分析平时常用的Handler.post(Runnable r)方法是如何执行的,是否新创建了一个线程了呢,实际上并没有,这个Runnable对象只是被调用了它的run方法,根本并没有启动一个线程,
而是该Runnable对象被包装成一个Message对象,也就是这个Runnable对象就是该Message的CallBack对象了,有优先执行的权利了。
八.主线程的Looper何时退出?能否手动退出?在App退出时,ActivityThread中的mH(Handler)收到消息后,执行退出。不能手动退出,会抛出异常,因为手动退出意味着程序就挂了,退出应用程序也不应该是这种方式退出。
九.什么是Handler的同步屏障机制?同步屏障可以理解成拦截同步消息的执行,主线程的 Looper 会一直循环调用 MessageQueue 的 next() 来取出队头的 Message 执行,当 Message 执行完后再去取下一个。当 next() 方法在取 Message 时发现队头是一个同步屏障的消息时,就会去遍历整个队列,只寻找设置了异步标志的消息,如果有找到异步消息,那么就取出这个异步消息来执行,否则就让 next() 方法陷入阻塞状态。如果 next() 方法陷入阻塞状态,那么主线程此时就是处于空闲状态的,也就是没在干任何事。所以,如果队头是一个同步屏障的消息的话,那么在它后面的所有同步消息就都被拦截住了,直到这个同步屏障消息被移除出队列,否则主线程就一直不会去处理同步屏幕后面的同步消息。
十.既然可以存在多个Handler往MessageQueue中添加数据(发送消息时各个Handler可能处于不同线程),那它内部是如何确保线程安全的?handler往MessageQueue添加数据包括MessageQueue中取出数据都是通过synchronized保证线程安全的。
Handler在系统以及第三方框架的一些应用 一.什么是HandlerThread?HandlerThread继承于Thread,所以它本质就是个Thread。与普通Thread的差别就在于,然后在内部直接实现了Looper的实现,这是Handler消息机制必不可少的。有了自己的looper,可以让我们在自己的线程中分发和处理消息。如果不用HandlerThread的话,需要手动去调用Looper.prepare()和Looper.loop()这些方法。本质上HandlerThread=Looper+Thread。
二.IntentService怎么实现线程切换的?简单看一下源码就能看到Handler的应用,Handler的handMessage()最终会回调到onHandleIntent()方法。并且此处调用了关闭service的方法stopSelf()实现自动关闭service的功能。
三.谈一谈你对AsynTask的认识。思路:是什么?有什么用?怎么使用?原理本质?
1.AsyncTask是一个抽象类,它是由Android封装的一个轻量级异步类(轻量体现在使用方便、代码简洁),它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。
2.怎么使用:
创建一个类继承AsyncTask,在继承的时候指定三个泛型参数类型,分别是
①.Params:传入的参数类型,用于任务中使用的;
②.Progress:进度参数类型;
③.Result:任务执行完后返回的结果参数类型;
然后重写AsynTask的四个方法:
①.onPreExecute():在执行任务之前,可以做一些UI显示的初始化操作;
②.doInBackground(Params 参数):这个方法用于处理耗时的任务,因为该方法的代码会在子线程中运行。执行完任务后就返回结果,返不返回结果以及返回的结果类型跟继承时给AsynTask类指定的第三个参数Result有关。
③.onProgressUpdate(Progress 参数):这个方法用来对UI界面的进度进行操作的。该方法是调用publishProgress(Progress 参数)后触发,然后拿到Progress参数也就是进度参数。一般在doInBackground()方法中调用publishProgress()方法.
④.onPostExcute(Result 参数):当doInBackground()方法执行完成后该方法执行,而执行结果就会返回到该方法中,然后就可以在这个方法中对结果进行操作。
3.原理本质?
ThreadPool+Handler
Handler内功心法参考:
Handler的内功心法,值得拥有!夯实基础的神器,赶紧来看!https://mp.weixin.qq.com/s/36T03bTreDSAXBWo62gRvgHandler机制图解:
图解Handler机制 - 简书1. 前言 在Android开发中,Handler机制是一个很重要的知识点,主要作用是消息通信。下面是Handler机制的原理图,先不要急,等看完这篇文章,这个图就很简单了。...https://www.jianshu.com/p/592fb6bb69fa



