导图社区 Handler思维导图笔记
当你学习Android开发中的多线程时,Handler是一个必不可少的概念。这份“Handler”的思维导图详细地介绍了Handler的概念、原理和使用方法。它包含了Handler的基本定义、主要作用、使用时的注意事项等内容。如果你想深入理解Android中的多线程机制和Handler的使用,这份思维导图是一个非常好的参考资料。
编辑于2023-02-14 16:51:52 四川省这份思维导图主要按照《python从入门到实践》的大纲来做出来的,并在相关内容的解释处加入了相关代码,欢迎大家一起学习!
职能地图-Java,干货分享~Java语言技术,java技术扩展,数据结构,维优,个人职能,技术面试知识点总结。
当今大型软件系统的开发多采用企业级的开发模式,而Java语言也是目前较为流行的企业级开发语言之一。针对Java企业级开发,涉及的知识点和技术栈较为丰富,包括但不限于Java EE、Spring框架、Hibernate框架、Maven、Git、Jenkins等等。这份思维导图以Java企业级开发为主题,通过图解的形式将涉及的知识点进行了梳理和整理,从Java EE体系结构、Servlet、JSP、Spring框架、Hibernate框架、Maven等基础知识开始讲解,逐步深入到SpringMVC、
社区模板帮助中心,点此进入>>
这份思维导图主要按照《python从入门到实践》的大纲来做出来的,并在相关内容的解释处加入了相关代码,欢迎大家一起学习!
职能地图-Java,干货分享~Java语言技术,java技术扩展,数据结构,维优,个人职能,技术面试知识点总结。
当今大型软件系统的开发多采用企业级的开发模式,而Java语言也是目前较为流行的企业级开发语言之一。针对Java企业级开发,涉及的知识点和技术栈较为丰富,包括但不限于Java EE、Spring框架、Hibernate框架、Maven、Git、Jenkins等等。这份思维导图以Java企业级开发为主题,通过图解的形式将涉及的知识点进行了梳理和整理,从Java EE体系结构、Servlet、JSP、Spring框架、Hibernate框架、Maven等基础知识开始讲解,逐步深入到SpringMVC、
Handler
Handler 是 Android 提供的一套用于驱动应用程序运行、刷新UI的消息机制
ThreadLoacal线程隔离的基石
并不是线程,是用来存储与线程有绑定关系的数据地
数据的作用域仅为当前线程,其他线程修改不了也访问不到
实现了与自定线程绑定与其他线程隔离的目的
原理
每个线程都有一个 ThreadLocalMap 用于存储 ThreadLocal 实例
ThreadLoacal#get()/set() 使用委托模式获取了 Thread 中的 ThreadLocalMap 中的数据
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
ThreadLocalMap
线程(Thread) 内存储 ThreadLocal 的具体数据结构
与 Map 集合没有关系,是自定义的存储数据结构
每个线程都独占一个 ThreadLocalMap 所以不用有锁
存储单位:Entry
继承自弱引用 WeakReference<ThreadLocal<?>>
key 为 ThreadLocal<?>、value 为 T 的实例
Entry 的回收
回收 key
持有的是 TreadLocal 是弱引用,不用管
回收 value
ThreadLocalMap#set() 时遍历所有 Entry 将 key 为 null 的 Entry 值也置为 null,断掉对值对象的强引用
回收 Entry
ThreadLocalMap#get():将键和值都为 null 的 Entry 置 null 从而使得该 Entry 可被回收
内存问题
ThreadLocalMap#set()、get() 会清理无用对象,回收内存
不使用的时候一定要调用 ThreadLocal#remove 手动清理
在线程池中每次执行完任务后都会手动清理,否则就会造成错乱,所以要养成手动清理的习惯
Handler机制是和 Tread 相关的,根本原因是 Handler 必须和对应的 Looper 绑定,而 Looper 的创建和保存是跟 Thread 一一对象的
也就是说每个线程都可以创建唯一一个且互不相关的 Looper
这是通过ThreadLocal来实现的
MessageQueue消息的存储器
通过 Native 方法创建实例,将引用返回到 java 层保管。
使用单链表实现的优先级队列
模拟队列仅从一端(头部)读取数据
按照 message 的执行时间决定优先级
时间越小越排在头部,越先执行
入队:enqueueMessage
入队的时候使用 synchronized(this) 锁住了实例
Handler 中所有发送消息的 API 最终都调用了 MessageQueue.enqueueMessage
对消息按时间排序,时间越大的越往后面排,如果时间相同直接往头部插入
唤醒: 如果需要使用 nativeWake(mPtr) 唤醒线程(进入的 message 的需要立即执行)
出队:next()
出队的时候使用 synchronized(this) 锁住了实例
死循环
在碰到消息屏障后,为了寻找异步任务
消息屏障,这个屏障之后的所有同步消息都不会被执行,即使时间已经到了也不会执行
休眠nativePollOnce(long ptr, int timeoutMillis)
类似 Object.wait
没有数据就休眠
nextPollTimeoutMillis
-1:一直阻塞不会超时
0: 不会阻塞
> 0 定时阻塞单位毫秒,超时自动唤醒
message 的复用
作为消息驱动机制 message 对象会非常多,为了防止频繁创建短暂生命周期的对象,在内部使用链表构建了缓冲池。
Message.obtain() 尝试从对象池中获取对象复用,这是官方推荐的方式
享元设计模式
message 使用后 looper 会自动调用 recycleUnchecked 回收对象重置数据等待复用
同步:使用 synchronized 分别在 obtain 和 recycleUnchecked 处锁住了一个 Object 类型全局变量 sPoolSync,将锁的粒度进行了最小化,只达到了复用和回收互斥提高了性能
清空: quit
safe:不停止正在执行的,只清空队列
no safe 全部停止
主线程调用后会抛异常
清空队列回收 message 用 nativeWeak 唤醒 looper,此时 loop 内会得到一个 null 的 message
Looper 消息驱动器
构造函数中创建了 MessageQueue 实例
prepear()
主线程: prepareMainLooper
prepare(false) 不允许退出
在 ActivityThread 中自动构建
prepareMainLooper 重复调用会抛异常,意思是 mainLooper 已经创建过了
子线程
通过 prepare() 创建 Looper 和 MessageQueue 并与当前线程绑定,然后调用 loop() 方法启动。
一个线程只能创建一个 Looper,否则抛异常
loop内部是死循环所以排在他下面的代码不会得到执行
prepare 内部创建 Looper 实例并设置给 ThreadLocal,完成了线程、Looper、MessageQueue 的唯一绑定
loop()
创建之后需要启动 loop
消息驱动器内部有个死循,不停的调用 MessageQueue#next获取消息
没有消息会睡眠: nativePollOnece
有消息会被唤醒: nativeWeak
loop 内部是个死循环,启动后位于 loop() 后面的方法将得不到执行!
死循环为啥不卡死主线程
卡死对应的是 ANR 异常,只有主线程无法处理用户输入事件或绘制操作才会抛出
死循环:主线程就是依靠这个死循环驱动的,只有当某个任务执行太久,阻塞了用户事件才会 ANR。二者不是一个 level 的事情。也可以说是死循环驱动了 ANR
阻塞: looper 的阻塞是没有任务时的睡眠。和卡死更不是一个概念
退出:quite、quiteSafe
子线程使用完毕后必须手动退出,否则线程一直出去等待状态永不会停止,无法被 GC
主线程不能退出
当 loop 方法内获取到了一个为 null 的 message 时退出循环
Handler 面向使用者上层接口
子线程为什么不能直接new Handler()?
创建方式
不传 Looper
传入指定的 Looper
async参数
通过这个参数表明这个 Handler 发送的消息全都是异步消息,因为在把消息压入队列的时候,会把这个标志设置到message里.
这个标志是全局的
Linux pipe/epoll 机制
内存泄漏
匿名内部类 + 延时消息
Message 会持有一个 Handler 的引用
如果实现了 Handler 的匿名内部类则 Handler 将持有外部类的引用
如果发送了延时消息 message 一直存在于 MessageQueue 中会造成内存泄漏
子线程中没有 quite loop
常见问题
一个线程可以有几个 Handler
答:任意个
一个线程可以有几个 Looper? 如何保证?
答:一个线程只能有一个,通过 ThreadLocal 保证
如何创建 Handler
主线程
在 ActivityThread 的 main 函数中 prepare、loop 了 Looper 所有可以直接创建
子线程
先 prepare
创建 Handler
启动 loop
停止 loop
线程安全
MessageQueue#enqueueMessage 、next()使用了 synchronized(this)
Handler detaily 时间准确吗
为了保证线程安全,不一定准确
如何创建 message
使用 Message.obtain 复用对象,防止内存抖动
UI 更新必须在主线程
所有的 View 都不是线程安全的
加锁太重太复杂
Handler同步屏障
Handler 有三种 Message
普通消息
异步消息
屏障消息
通常我们使用 Handler 向队列添加 Message 都是同步的,如果想添加异步的 Message 有两个方式
Handler的构造方法有一个async参数,我们在new Handler的时候传为true就行了,后续使用该Handler添加的Message都是异步。
public Handler(Callback callback, boolean async) { ......省略代码 mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } async设置为true后,对全局的mAsynchronous设置为 true。然后在enqueueMessage()方法里,调用msg.setAsynchronous(true),将message设置为异步的。 private boolean enqueueMessage( MessageQueue queue, Message msg, long uptimeMillis ) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
在创建Message对象时,直接调用Message的setAsynchronous方法。
从代码层面上来讲,同步屏障就是一个Message,一个target字段为空的Message。
private int postSyncBarrier(long when) { // Enqueue a new sync barrier token. // We don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when prev = p; p = p.next; } } if (prev != null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } }
同步屏障可以通过MessageQueue.postSyncBarrier函数开启
往 MsgQueue 添加一个 target 为 null 的 msg
移除同步屏障 MessageQueue.removeSyncBarrier()方法。
public void removeSyncBarrier(int token) { // Remove a sync barrier token from the queue. // If the queue is no longer stalled by a barrier then wake it. synchronized (this) { Message prev = null; Message p = mMessages; // 循环遍历,直到遇到屏障消息时推退出循环 while (p != null && (p.target != null || p.arg1 != token)) { prev = p; p = p.next; } if (p == null) { throw new IllegalStateException("The specified message queue synchronization " + " barrier token has not been posted or has already been removed."); } final boolean needWake; if (prev != null) { // 删除屏障消息p prev.next = p.next; needWake = false; } else { mMessages = p.next; needWake = mMessages == null || mMessages.target != null; } p.recycleUnchecked(); // If the loop is quitting then it is already awake. // We can assume mPtr != 0 when mQuitting is false. if (needWake && !mQuitting) { nativeWake(mPtr); } } }
遍历 MsgQueue 寻找到屏障把他删除
通常异步消息和同步消息没有什么区别,但是一旦开启了同步屏障就有区别了。
一般来说 MessageQueue 里面的所有 Message 都是按照时间从前往后有排序的那么如何提升消息的优先级呢?答案就是消息屏障
此时 next 函数将会忽略所有的同步消息开始处理异步消息直到移除屏障
当 MsgQueue#next 发现一个消息屏障后会忽略所有的同步消息,然后遍历整个 MsgQueue 寻找最近的异步消息
Message next() { //... int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { //... synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) {//碰到同步屏障 // Stalled by a barrier. Find the next asynchronous message in the queue. // do while循环遍历消息链表 // 跳出循环时,msg指向离表头最近的一个异步消息 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now //... } else { // Got a message. mBlocked = false; if (prevMsg != null) { //将msg从消息链表中移除 prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); //返回异步消息 return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } //... } //... } }
在 MessageQueue#next() 中会判断 msg 的 target
Message next() { // ······ for (;;) { // ······ // 休眠指定时间 // ······ synchronized (this) { // ······ final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; // 获取异步消息 if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } //······ // 获取同步消息 //······ // 处理idle if (pendingIdleHandlerCount && (mMessages == null || now pendingIdleHandlerCount = mIdleHandlers.size(); } // ······· } // ······ } }
如果 target 为 null 则判定此消息为屏障
此时会循环遍历 MsgQueue,直到 msg 是异步msg 或到达尾部则退出循环,也就是说,循环的代码会过滤掉所有的同步消息,直到取出异步消息或遍历完成为止。
消息屏障一旦插入就不会消失,他不会被消费掉
如果遇到了异步屏障且后序没有异步消息了,那么 looper 将会进入无休止休眠。所以使用完毕一定要移除屏障
Android应用框架中为了更快的响应UI刷新事件在ViewRootImpl.scheduleTraversals中使用了同步屏障
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; //设置同步障碍,确保mTraversalRunnable优先被执行 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //内部通过Handler发送了一个异步消息 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
mTraversalRunnable调用了performTraversals执行measure、layout、draw
为了让mTraversalRunnable尽快被执行,在发消息之前调用MessageQueue.postSyncBarrier设置了同步屏障
总结
同步屏障为Handler消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息
设置了同步屏障之后,Handler只会处理异步消息
使用完毕一定要手动移除消息屏障,否则同步消息永远得不到执行
IdleHandler及应用
IdleHandler 表示当前主线程在不忙碌的时候会执行IdleHandler里面的任务
IdleHandler 是 MessageQueue 内部的一个接口我们可以通过 MessageQueue添加和删除 IdleHandler 不过它没有保存在 MsgQueue 里,而是保存在数组中
public static interface IdleHandler { boolean queueIdle(); } // MessageQueue 对于 IdleHandler 存储结构 private final ArrayList mIdleHandlers = new ArrayList(); ... public void addIdleHandler(@NonNull IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } synchronized (this) { mIdleHandlers.add(handler); } } public void removeIdleHandler(@NonNull IdleHandler handler) { synchronized (this) { mIdleHandlers.remove(handler); } }
怎么实现在消息队列空闲的间隙得到执行的呢
在 MsgQueue#next 中有判定消息队列是否空闲的逻辑
// pendingIdleHandlerCount // mMessages == null || now // 说明现在消息队列处于空闲。 if (pendingIdleHandlerCount && (mMessages == null || now pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; }
当判定消息队列空闲后,就会拿到存储 idleHandler 数组的大小,一次最多执行 4 个任务
// 一次最大执行四个 IdleHandler if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); // 遍历执行 for (int i = 0; i final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { // 如果queueIdle返回true,则该空闲消息不会被自动删除,在下次执行next的时候,如果还出现队列空闲,会再次执行。 // 如果返回false,则该空闲消息会在执行完后,被自动删除掉。 keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. // 这里把空闲消息标志置为0,而不置为-1,就是说本次已经处理完,防止for循环反复执行,影响其他消息的执行 pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0;
总结
如果本次循环拿到的消息为空,或者这个消息是一个延时的消息而且还没到指定的触发时间,那么,就认定当前的队列为空闲状态。
接着就会遍历数组来调用每一个IdleHandler实例的queueIdle方法, 如果这个方法返回false的话,那么这个实例就会从mIdleHandlers中移除,当下次队列空闲的时候,不会继续回调它的queueIdle方法了。
处理完IdleHandler后会将nextPollTimeoutMillis设置为0,也就是不阻塞消息队列
要注意这里执行的代码同样不能太耗时,因为它是同步执行的,如果太耗时肯定会影响后面的message执行。
应用
ActivityThread有个内部类 GcIdler
在主线程空闲的时候对内存进行强制GC
Glide
子主题 3
应用
HandlerThread
IntentService