Android:Handler
基本流程
首先,系统通过调用 Looper.prepare()为线程准备Looper 和承接Message 的MessageQueue;
然后,系统再调用Looper.loop()函数,这个函数会开启一个死循环,在循环中不断的轮询MessageQueue消息队列,从消息队列中取出可以执行的Message消息,然后进行执行。
再然后,用户通过Handler.sendMessage 或 Handler.post(Runable)这一类的函数调用,向MessageQueue里面不断的发送消息。
最后,由于Looper 中的loop是在不断轮询MessageQueue的,一旦发现MessageQueue里面有可执行的消息,那么就会将消息取出来,然后通过消息所携带的handler (msg.target.dispathMessage(msg) msg.target 就是 发送信息的 handler )去执行。
主线程 Looper.prepareMainLooper(); Looper.loop()由系统自动执行
为什么 Looper.loop() 不会导致 ANR?
答:loop() 在 queue.next() 处阻塞。当队列没有消息时,next() 通过 nativePollOnce() 进入内核态的 epoll 休眠,不消耗 CPU。有新消息入队时,nativeWake() 唤醒。所以主线程一直在 loop 里"等着",但大部分时间是在休眠——直到有事件(点击、触摸、刷新)需要处理。
post(Runnable) 和 SendMessage(msg)
本质都是一样的, post 中的 Runnable 会被封装到 msg.callback 中,然后交给 SendMessage 的重载处理,最终会调用 msg.target.dispatchMessage(msg) 来处理。
执行的优先顺序:msg.callback > 构造函数的 callback > handleMessage(msg)
Hanlder 内存泄漏原因
调用Looper.prepare()时,会创建一个新的Looper,然后通过静态ThreadLocal<Looper>的set()方法,将该ThreadLocal作为键、Looper作为值,存入当前线程内部的ThreadLocalMap中。
需要注意,Looper不被回收的直接原因,是当前线程仍然存活,并通过自己的ThreadLocalMap强引用着Looper。对于主线程来说,它通常会在应用进程存活期间一直运行,因此主线程的Looper和MessageQueue也会长期存在。静态ThreadLocal主要用于提供一个长期有效的查询键,而不是单独决定Looper的生命周期。
每个Looper内部持有一个MessageQueue,队列中的Message通过target字段引用负责处理该消息的Handler。
当在Activity中使用非静态匿名内部类创建Handler时,该Handler对象通常会隐式持有外部Activity实例(如果使用了Activity 的变量或方法那么一定会有)。如果该Handler发送了延迟消息,而 Activity 已经销毁,但消息仍然保存在主线程的MessageQueue中,就会形成以下引用链:
主线程 → ThreadLocalMap → Looper → MessageQueue → Message → Handler → Activity
由于主线程属于 GC Root,并且上述引用链仍然存在,因此 Activity 暂时无法被垃圾回收,从而产生内存泄漏。
当消息被处理、从队列中移除,或者主动调用removeCallbacksAndMessages()清除消息后,这条引用链就会被断开,Activity 才可能被正常回收。
解决方法:
1: 将 Handler 声明为静态内部类,避免它隐式持有 Activity,再通过 WeakReference 访问 Activity;
2:在 Activity 的 onDestroy 中调用 removeCallbacksAndMessages(null),移除队列中未执行的延迟消息和任务,从而避免 MessageQueue 长时间间接引用 Activity。
publicclassMainActivityextendsAppCompatActivity{privateTextViewtextView;privatefinalMyHandlerhandler=newMyHandler(this);privatestaticclassMyHandlerextendsHandler{// 方案 一// 弱引用不会阻止 Activity 被 GCprivatefinalWeakReference<MainActivity>activityRef;publicMyHandler(MainActivityactivity){super(Looper.getMainLooper());activityRef=newWeakReference<>(activity);}@OverridepublicvoidhandleMessage(@NonNullMessagemsg){MainActivityactivity=activityRef.get();// Activity 可能已经被回收if(activity==null||activity.isFinishing()){return;}activity.textView.setText("消息已处理");}}@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);textView=newTextView(this);setContentView(textView);handler.sendEmptyMessageDelayed(1,10*60*1000);}@OverrideprotectedvoidonDestroy(){super.onDestroy();// 方案 二// 移除该 Handler 发送的所有 Message 和 Runnablehandler.removeCallbacksAndMessages(null);}}Messgae 对象池
Hanlder 对 Message 的复用使用了对象池,使用单链表最大为 50个实现
最佳实践: 始终用 Message.obtain() 或 Handler.obtainMessage() 获取 Message,避免 new Message()。差一个对象通常没啥,但在 ListView/RecyclerView 滑动场景下可能瞬间创建数千个 Message。
Handler 的其他用途
同步屏障
Android 的 UI 渲染依赖 VSync(垂直同步信号)。当屏幕需要刷新时,如果消息队列前面排了十几条普通消息,渲染动作就必须排队——这会导致掉帧。
同步屏障就是用来解决这个问题的:让异步的渲染消息绕过同步消息,插队执行。
MessageQueue 提供了 两个方法:
postSyncBarrier() 在 MessageQueue 的链表头 插入一个 屏障, msg.target = null 作为一个屏障
removeSyncBarrier() 将链表头的屏障删除
next() 中 处理屏障的代码
// ★ 遇到屏障(target == null),跳过所有同步消息if(msg!=null&&msg.target==null){do{prevMsg=msg;msg=msg.next;}while(msg!=null&&!msg.isAsynchronous());// ↑ 一直遍历,直到找到第一个异步消息}流程:
消息队列: [屏障] → [普通消息1] → [普通消息2] → [异步消息★] → [普通消息3] next() 发现头是屏障: → 跳过普通消息1、普通消息2 → 取出异步消息★ 返回给 Looper 分发 → 屏障继续留在队列中,直到 removeSyncBarrier() 移除知识点: ViewRootImpl.scheduleTraversals() 内部就是通过屏障 + 异步消息来实现 VSync 同步渲染的。每次 UI 刷新,先 post 屏障,再发异步渲染消息。
idleHandler
当 MessageQueue 当前没有消息或消息还没到执行时间时,会执行注册的 IdleHandler。
参考:https://juejin.cn/post/7668127355689287686
