前言:基于Android 31阅读源码 @Deprecated
1:Handler的常见用法:
1.1:主线程实现方式
class MyStaticHandler(activity: HandlerActivity) : Handler() {
private var weakReference: WeakReference? = null
init {
weakReference = WeakReference(activity)
}
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
weakReference?.get()?.let {
Log.e(it.TAG, "handleMessage: what: ${msg.what}")
}
}
}
//调用实现发送消息:
//主线程发送消息
val myHandle: MyStaticHandler = MyStaticHandler(this@HandlerActivity)
val message = myHandle.obtainMessage(11)
myHandle.sendMessage(message)
1.2:协程内实现方式
var threadHandler: Handler? = null
//协程内Handler接收处理消息
CoroutineScope(Dispatchers.Default).launch {
Looper.prepare()
threadHandler = @SuppressLint("HandlerLeak")
object : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
Log.e(TAG, "handleMessage: .what==${msg.what} in Thread.name:${Thread.currentThread().name}")
Looper.myLooper()?.quitSafely()//消息处理完成后,退出Looper 不在堵塞子线程,释放
}
}
Looper.loop()
}
//协程内发送消息
CoroutineScope(Dispatchers.Default).launch {
threadHandler?.sendEmptyMessage(55)
}
2、Handler发送消息的几种方式,主要是 send 和 post 的api上
//主线程发送消息
val myHandle: MyStaticHandler = MyStaticHandler(this@HandlerActivity)
val message = myHandle.obtainMessage(11)
myHandle.sendMessage(message)
myHandle.sendMessage(Message().apply { what = 22 })
myHandle.sendEmptyMessage(33)
myHandle.sendEmptyMessageDelayed(44,3000)
myHandle.sendMessageAtFrontOfQueue(Message().apply { what = 55 })
myHandle.post {
Log.e(TAG, "callback-run: ", )
}
//等等其它几个api
3、Handler 三要素:Looper、Message、MessageQueue
3.1:Looper:
实例化 Handler 的时候 Handler 会去检查当前线程的 Looper 是否存在,如果不存在则会报异常,也就是说在创建 Handler 之前一定需要先创建 Looper 。源码如下
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
//TODO:如果looper 为null,则爆出异常Exception
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
在常规的主线程内使用 Handler 一般是看不见这个过程的,因为系统源码已经帮我们处理并实现了,Looper 提供了 Looper.prepare() 方法来创建 Looper ,并且会借助 sThreadLocal 来实现与当前线程的绑定功能。Looper.loop() 则会开始不断尝试从 MessageQueue 中获取 Message , 并分发给对应的 Handler
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
me.mInLoop = true;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
me.mSlowDeliveryDetected = false;
for (;;) {
if (!looponce(me, ident, thresholdOverride)) {
return;
}
}
}
扩展:可以看到 Looper是和当前线程绑定在一起,借此我们可以通过 Looper判断当前线程是否为主线程:Looper.getMainLooper() == Looper.myLooper()
3.2、Message,这是Handler要发送的消息实体,其中最关键的是 target 变量,他所保存的正式:发送消息的Handler
3.3、MessageQueue:管理Message 的队列
小结:
Handler:用来发送和接收处理消息;
Message:消息的实体,其中target保存发送消息的Handler;
MessageQueue:用于存储和管理Message的队列;
Looper: 用于和当前线程进行绑定,并通过Looper.loop() 开启无限循环的从MessageQueue队列内取消息,通过取出的Message 利用变量target 发送给Handler 接收处理消息
面试中常见的问题:
1、Handler 是如何与线程关联的?
Handler 创建之初都会去检查 Looper是否为null , 如果为null 则爆异常信息;Looper是通过Looper.prepare()和当前线程进行绑定关联,涉及到ThreadLocal
2、Handler 发出去的消息是谁管理的?
handler发送消息都是通过send 或 post 一系列api 进行消息处理,无论通过那个api最后都会调用
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) ,然后入队列:queue.enqueueMessage(msg, uptimeMillis);也就是由MessageQueue进行统一管理
3、消息又是怎么回到 handleMessage() 方法的?
问题 2 中在调用enqueueMessage 函数的时候,将this赋值给了Message.target变量,也就是每个Message都携带者发送者的信息,this; 那么通过 Looper.loop()取到Message后利用target 又送回到发送Handler 的回调函数handlerMessage()方法
4、线程的切换是怎么回事?
上面问题 1 说明了Handler是如何与线程A关联的,那么关联之后,Handler.handlerMessger就在此线程执行,也就是在 子线程B 内调用handler 发送消息,那么处理消息都会回到线程A内,也就是所谓的线程切换。
5、Handler引起的内存泄漏原因和最佳方案
Handler 允许我们发送延时信息,如果在延时期内关闭了Activity,此时会导致内存泄漏,最佳的处理方案是:采用静态内部类 + 弱引用的方式 如上 MyStaticHandler 实现,并且追加一下onDestory 的销毁操作:
override fun onDestroy() {
super.onDestroy()
myHandle?.removeCallbacksAndMessages(null)
}
6、Handler 里藏着的 Callback 能干什么?
这个涉及到 Looper.loop() 函数的无限循环取Message值,并处理分发这些消息,如下源码
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
源码可见,如果Message的callback不为null, 优先处理handlerCallback(msg)
示例:
CoroutineScope(Dispatchers.Default).launch {
threadHandler?.post(Runnable {
Log.e(TAG, "loaddata: callback.run")
})
}
//打印日志:E/HandlerActivity: loaddata: callback.run
说明,对于Message.callback优先处理,不在返回Handler.handlerMessage处理
7、创建Message的最佳实现:
由于 Handler 极为常用,所以为了节省开销,Android 给 Message 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗。
方法有二:
- 通过 Message 的静态方法 Message.obtain(); 获取;
- 通过 Handler 的公有方法 handler.obtainMessage(); 。
8、子线程里弹 Toast 的正确姿势
Thread() {
kotlin.run {
Looper.prepare()
Toast.makeText(this, "子线程 Toast", Toast.LENGTH_LONG).show()
Looper.loop()
}
}.start()
9、一个Thread可以有几个Looper?几个Handler
一个线程只能有一个Looper,可以有多个Handler,在线程中我们需要调用Looper.perpare,他会创建一个Looper并且将Looper保存在ThreadLocal中,每个线程都有一个LocalThreadMap,会将Looper保存在对应线程中的LocalThreadMap,key为ThreadLocal,value为Looper
10、可以在子线程直接new一个Handler吗?那该怎么做?
可以在子线程中创建Handler,我们需要调用Looper.perpare和Looper.loop方法。或者通过获取主线程的looper来创建Handler
11、Message可以如何创建?哪种效果更好,为什么?
Message.obtain来创建Message。这样会复用之前的Message的内存,不会频繁的创建对象,导致内存抖动。
12、使用Hanlder的postDealy()后消息队列会发生什么变化?
Handler发送消息到消息队列,消息队列是一个时间优先级队列,内部是一个单向链表。发动postDelay之后会将该消息进行时间排序存放到消息队列中
13、点击页面上的按钮后更新TextView的内容,谈谈你的理解?(阿里面试题)]
点击按钮的时候会发送消息到Handler,但是为了保证优先执行,会加一个标记异步,同时会发送一个target为null的消息,这样在使用消息队列的next获取消息的时候,如果发现消息的target为null,那么会遍历消息队列将有异步标记的消息获取出来优先执行,执行完之后会将target为null的消息移除。(同步屏障:Handler机制——同步屏障_start_mao的博客-CSDN博客_同步屏障)
14、Handler是如何完成子线程和主线程通信的?
在主线程中创建Handler,在子线程中发送消息,放入到MessageQueue中,通过Looper.loop取出消息进行执行handleMessage,由于looper我们是在主线程初始化的,在初始化looper的时候会创建消息队列,所以消息是在主线程被执行的。
15、关于ThreadLocal,谈谈你的理解?
ThreadLocal类似于每个线程有一个单独的内存空间,不共享,ThreadLocal在set的时候会将数据存入对应线程的ThreadLocalMap中,key=ThreadLocal,value=值
16、享元设计模式有用到吗?
享元设计模式就是重复利用内存空间,减少对象的创建,Message中使用到了享元设计模式。内部维护了一个链表,并且最大长度是50,当消息处理完之后会将消息内的属性设置为空,并且插入到链表的头部,使用obtain创建的Message会从头部获取空的Message
17、Handler异步消息处理(HandlerThread)
内部使用了Handler+Thread,并且处理了getLooper的并发问题。如果获取Looper的时候发现Looper还没创建,则wait,等待looper创建了之后在notify
18、子线程中维护的Looper,消息队列无消息的时候处理方案是什么?有什么用?
子线程中创建了Looper,当没有消息的时候子线程将会被block,无法被回收,所以我们需要手动调用quit方法将消息删除并且唤醒looper,然后next方法返回null退出loop
19、既然可以存在多个Handler往MessageQueue中添加数据(发消息是各个handler可能处于不同线程),那他内部是如何确保线程安全的?
在添加数据和执行next的时候都加了this锁,这样可以保证添加的位置是正确的,获取的也会是最前面的。
20、关于IntentService,谈谈你的理解
HandlerThread+Service实现,可以实现Service在子线程中执行耗时操作,并且执行完耗时操作时候会将自己stop。
21、Looper.loop()是一个无限循环,为什么主线不会被卡死
参考:Handler 都没搞懂,拿什么去跳槽啊? - 掘金
Android中为什么主线程不会因为Looper.loop()里的死循环卡死? - 知乎
Android消息机制1-Handler(Java层) - Gityuan博客 | 袁辉辉的技术博客
Android | 面试必问的 Handler,你确定不看看? - 简书
享元模式 | 菜鸟教程
Handler | Android Developers



