• Android - Handler


    一、概览

            类似于过安检,人(Handler)将行李(Message)放在安检机(MessageQueue)中,传送带(Looper)通过不断循环将行李从安检机中取出,最后由同一个人(Handler)取走处理。

            Handler通过 sendMessage() 方法发送消息 → MessageQueue通过 enqueueMessage() 方法将消息插入队列 → Looper通过 loop() 开启死循环不断轮询消息 → 通过 MessageQueue 的 next() 方法将消息取出 → 通过 Handler 的 dispatchMessage() 方法判断消息携带的是 callback 还是对象,是 callback 就执行,是对象就回调 handleMessage() 处理。

    二、Looper

    • 轮询器。每个线程中只有一个Looper,切换线程和消息分发,唤醒与挂起靠的是 Linux 中的 epoll 机制来实现。
    • handler构造时,可以选择是否传入looper对象,若不传则默认从当前线程获取,若取不到则会抛出异常。这就是为什么在子线程里不能简单用消息循环的原因,因为handler没有和looper建立起关联。

    2.1 prepareMainLooper( )

    AMS 调用 ActivityThread.main() 进而调用 Looper.prepareMianLooper() 进而调用 parper(),将主线程和 Looper 绑定,然后调用 loop() 跑起来。

    2.2 prepare( )

    用来将当前线程和Looper绑定。先通过 sThreadLocal.get() 看有没有 value(这里是主线程是否绑定过Looper),value不为 null 就报错不让再次赋值保证了 Looper 的唯一性,value为 null 就调用 sThreadLocal.set() 新建一个 Looper 对象(构造函数中会创建MessageQueue对象)传入赋值实现了 Looper 和主线程绑定

    2.3 sThreadLocal

    Looper 中创建了一个 static final 的 ThreadLocal 成员变量。

     

    2.3.1 Thread

    该类中有一个成员变量 threadLocals 它是一个空的 ThreadLocalMap。即每个线程都有自己的ThreadLocalMap,当不同线程访问代码被 ThreadLocal 操作的是它们各自持有的数据。

    2.3.2 ThreadLocal

    在一个线程中可以创建多个 ThreadLocal 对象(作用是当作key存入value),不管是调用 ThreadLocal 对象的 get() 还是 set() 内部都会先获取当前线程Thread(这里是主线程),然后获取 Thread 所持有的 ThreadLocalMap(即Thread的成员变量threadLocals),以该 ThreadLocal 对象(这里是sThreadLocal)为 key 往里面存取 value(这里是Looper)。由此通过 sThreadLocal 将 Looper 和主线程的绑定。

     

    2.3.3 ThreadLocalMap

    是 ThreadLocal 的静态内部类。Entry 继承于 WeakReference,key 是 ThreadLocal 类型,value是 Object 类型。

    2.4 myLooper( )

    返回当前线程绑定的 Looper 对象,如果未绑定过则返回null。

    2.5 loop( )

    拿到当前线程的 Looper 对象(此处为主线程),通过无限 for 循环保证了不会因为代码执行完而退出 main() 函数导致程序(进程)结束。loopOnce()中调用 MessageQueue.next() 从队列中取出 Message,再调用 Message.target.dispatchMessage() 分发给发送这个 Message 的 Handler 处理(即发送和处理该 Message 的是同一个 Handler)

     

    四、Handler

    负责发送和接收(处理)Message。Handler 在构造时可以选择是否传入 Looper 对象,若不传则默认从当前线程获取,若取不到则会抛出异常。这就是为什么在子线程里不能简单用消息循环的原因,因为需要 Handler 和 Looper 绑定。

    sendMessageAtTime( )发送消息的方法非常多  (post/send) 最终调用的都是这个,uptimeMillis即系统开机时间uptimeMillis和传入的延迟时间 delayMillis 相加。
    handlerMessage( )由外部来重写该方法,以此来处理收到的Message。
    dispatchMessage( )用于分发消息,判断Message的callback,不为null调用Runable(),为null调用handleMessage()。

    4.1 Handler( )

    Handler构造时需要传入一个 Looper 实现了 Handler 和 Looper 绑定,通过唯一的 Looper 拿到唯一的MessageQueue,使得 Handler 和 MessageQueue 绑定(即同一个 Handler 在不同线程中发送 Message 是发到同一个 MessageQueue 中),由于该 MessageQueue 所属的 Looper 所绑定的是主线程,因此子线程中发送的 Message 最终是在主线程中处理

    4.2 sendMessageAtTime( )

    发送消息的方法非常多(各种post/send)最终调用的都是这个,在确保存在 MessageQueue 后就调用 enqueueMessage() 将 Message 入列。

    4.3 enqueueMessage( )

    Handler先将自己赋值给 Messgae 的 target 实现了 Handler 和 Message 绑定(即发送和处理该 Message 的是同一个 Handler),然后调用 MessageQueue.enqueueMessage() 将 Message 存入队列中

    4.4 dispatchMessage( )

    判断 Message 携带的是 callback 还是对象,是 callback 就直接运行,是对象就回调到 handleMessage() 中让我们在创建 Handler 对象的地方重写该方法处理。

    五、MessageQueue

    消息队列。每个线程中只有一个MessageQueue,存放 Handler 发送来的Message。是单链表结构(非线性非顺序的物理结构。采用见缝插针的存储方式不要求内存连续,也不需要随机访问(只需要取头值),靠 Message中 的 next 指向下一个,随机存储顺序访问)。

    5.1 Looper.mQueue

    Looper构造是私有的,其中会创建 MessageQueue 并赋值给 final 修饰的成员变量mQueue,因此 MessageQueue 也是唯一。 

     

    5.2 enqueueMessage( )

    enqueueMessage()里的同步锁让 Handler 在不同线程中发送过来的 Message 同步入列,并通过形参 when 来对队列中的 Message 进行排序。形参 when 给 Message 加了时间戳排序。

    5.3 next( )

    next()里的同步锁与 enqueueMessage() 里的同步锁相互协作,即便是在子线程发消息在主线程取消息,实现了消息入列和出列的互斥性(不能同时进行),保证了有序性和线程安全

    • 什么消息都没有的时候,执行 nativePollOnce() 处于休眠状态节省CPU给其它地方。(早期使用的是 wait/notify,Android2.3后来使用 epoll 机制为了可以同时处理 native 消息。)
    •  if(msg != null && msg.target == null) 说明是消息屏障,立马轮询优先执行异步消息。
    • 当 if(msg != null) 为普通消息,根据时间戳取头部执行。
    • 当非延时的 Message 都执行完的时候(主线程没有重要的事情做的时候),通过 for 循环处理 idleMessage。

     六、Message

    可以携带需要的数据在线程间传递。根据优先级分为:异步消息 > 普通消息 > idleMessage。

    消息屏障判断依据 msg.target == null。发送屏障消息后会发送异步消息(判断依据msg.isAsynchronous),获取的是当前时间戳,会加入到队列头部,由于优先刷新UI。
    idleMessage(不重要消息)当所有延时为0的消息都处理完后,主线程没有重要的事情做的时候(即不影响主线程的时候),才会去执行idleMessage。例如GC、AMS管理Activity的 stop destroy。
    Message的字段说明
    what: Int唯一标识。
    target: Handler绑定Handler(该 Message 是由同一个 Handler 发送和处理的)。
    data: Bundle携带 Bundle 数据。

    arg1: Int

    arg2: Int

    携带 Int 数据。
    obj: Object携带可序列化数据。
    callback: Runnable携带 Runnable 数据(Handler的post()就是将一个Runnable对象复制过来封装为Message对象)。
    when:Long时间戳。
    next: Message指向下一个节点(Message 通过 next 字段指向下一个Message,从而串联成队列。enqueueMessage()、recycleUnchecked()、obtain() 操作的就是 next 的指向)。

    6.1 Message.sPool

    回收后用来复用的 Message,它是 static 的。MAX_POOL_SIZE = 50 缓存池最多存50个,超过就会 new 了。sPoolSize = 0 用来记录池中数量,缓存一个就+1取出一个就-1。spoolSync用来做同步锁。

    6.2 obtain( )

    构建 Message 实例,可以通过 new 关键字,推荐通过 obtain() 方法。APP中会有大量管理事件需要通过 Handler 来发送消息(60Hz屏幕光UI刷新每秒就有60个Message更别其它提系统服务了),使用缓存池为了避免频繁创建销毁 Message 对象内存抖动造成卡顿。

    6.3 recycleUnchecked( )

    使用完的 Message 会被回收,将字段携带的数据都清除掉后,通过 next 将下一个节点指向当前 sPool,再将自己赋值给sPool,这样就像该 Message 插入到了缓存池队列头部。

    七、使用步骤

    1. 在主线程里创建一个全局的 handler 对象并重写 handleMessage( ) 方法。
    2. 在子线程里构造 message,并使用 handler 将 message 发送出去。
    3. 在主线程里的 handleMessage( ) 方法中判断是哪个 message 然后做相应处理。
    1. Activity {
    2. private val updateText = 123 //定义Message唯一值
    3. //【第一步】创建一个全局的Handler并重写handleMessage()
    4. private val handler = object : Handler(Looper.getMainLooper()) {
    5. //【第三步】处理消息
    6. override fun handleMessage(msg: Message) {
    7. when (msg.what) { //判断是哪个Message
    8. updateText -> {
    9. msg.obj //获取携带的数据
    10. }
    11. }
    12. }
    13. }
    14. //【第二步】创建Messgae并发送
    15. fun show() {
    16. val str = "一条消息"
    17. //方式一:直接新建
    18. val msg1 = Message()
    19. msg1.what = updateText
    20. msg1.obj = str
    21. handler.sendMessage(msg1)
    22. //方式二:避免频繁创建销毁Message推荐使用obtain()
    23. val msg2 = Message.obtain()
    24. val msg3 = handler.obtainMessage() //相对于上一个,会自动将target字段设置为这个handler
    25. val msg4 = handler.obtainMessage(updateText, str) //相对于上一个,构造里就能携带what和obj字段
    26. handler.sendMessage(msg4)
    27. //方式三:利用post在子线程中更新UI,利用postDelayed延时
    28. handler.postDelayed({
    29. //此处执行在Runnable中
    30. }, 2000) //延迟2秒
    31. }
    32. }

    八、内存泄漏风险(Java)

    Kotlin无影响的原因详见闭包讲解

    8.1 原因

    • 非静态的内部类无法直接 new 对象,需要先创建外部类对象再创建内部类对象,因此内部类持有外部类的引用,匿名内部类(定义一个类的同时对其进行实例化)同理。
    • 跳转 Activity 给 Intent 赋值的时候,第一个参数会写外部类名.this ,这就是持有外部类引用的很好例子。创建 Handler 对象的时候就是通过匿名内部类的方式
    • 非静态的 Handler 内部类,无论是否是匿名创建,便会持有外部 Activity 的引用。在 Activity 销毁时,若此时消息队列中有未处理完的 Message(特别是带有延时的),那么 Handler 也仍然存在(在enqueueMessage()的时候Handler被赋值给了Message.target,所以Message持有Handler),由于 Handler 持有外部 Activity 的引用,那么Activity就无法正常回收(GC可达性分析),Activity中所有占内存的东西就成了内存泄露。

    8.2 解决

    • 方式一:被static修饰后调用便不需要外部类的实例,但是总不能把Activity中的控件全static才能操作,我们通过使Handler持有Activity的一个弱引用来解决这个问题,直接持有Activity的话,我们便与之前的匿名内部类直接持有外部类的引用没区别了,而持有了弱引用,在Activity有用的情况下,其会被AMS持有强引用,GC不会回收,而当其finish了,便没有强引用对象持有了,此时GC时便会回收该Activity。
    • 方式二:在 onDestroy() 中调用 handler.removeCallbackAndMessage() 也可以避免内存泄漏
    1. public class MainActivity extends Activity {
    2. private MyHandler mMyHandler;
    3. //自定义一个静态的Handler
    4. private static class MyHandler extends Handler {
    5. private WeakReference mActivityWeakReference;
    6. //构造中传入当前Activity并使用弱引用包装
    7. public void MyHandler(MainActivity activity) {
    8. mActivityWeakReference = new WeakReference<>(activity);
    9. }
    10. @Override
    11. public void handleMessage(@NonNull Message msg) {
    12. super.handleMessage(msg);
    13. MainActivity mainActivity = mActivityWeakReference.get();
    14. if (mainActivity != null) {
    15. switch (msg.what) {
    16. case 0:
    17. //TODO...
    18. break;
    19. }
    20. }
    21. }
    22. }
    23. onCreate() {
    24. mMyHandler = new MyHandler(this);
    25. }
    26. click() {
    27. mMyHandler.sendMessage();
    28. }
    29. }

    九、在子线程中创建Handler(HandlerThread)

            要在子线程中创建 Handler 就在构造的时候传入子线程中的Looper,进而需要拿到子线程的 ThreadLocal,进而需要拿到子线程对象,因此子线程不能写成匿名内部类,需要定义成一个类。

            新创建一个子线程并不会自动建立 Looper,需要在类中重写 run() 方法调用 Looper.prepare() 创建 Looper 和该线程绑定,再调用 looper.loop() 将循环跑起来,然后提供一个 getLooper() 方法供外部获取。外部先创建线程对象,调用 start() 跑起来就初始化了Looper,然后通过获取 getLooper() 的在创建 Handler 的时候传入。注意:子线程在 run() 中创建Looper,外部创建 Handler 时是在主线程中 getLooper() 传入的,可能为null,主线程拿子线程结果存在多线程并发问题。使用 sleep() 存在性能问题,要使用同步锁机制。

            实际开发中不会手写,而是使用HandlerThread。Looper在 run() 中创建在 getLooper() 中获取,两个方法中的同步代码块使得创建和获取互斥,保证了获取前 Looper 已经被初始化。

    9.1 getLooper( )

    获取 Looper。同步代码块中的判断用来确保获取前 Looper 已经被初始化,否则释放锁让线程进到 run( ) 方法的同步代码块中创建Looper。

    9.2 run( )

    创建 Looper 并循环起来。同步代码块保证了在 getLooper( ) 中获取的安全性。可能有很多地方等着获取该 Looper 来创建 Handler,因此要 notifyAll() 全部唤醒。

    9.3 使用

    1. val handlerThread = HandlerThread("demo") //创建HandlerThread子线程
    2. handlerThread.start() //启动线程
    3. val handler = Handler(handlerThread.looper) { msg -> //创建Handler
    4. //此处为handleMessage
    5. when (msg.what) {
    6. 123 -> {}
    7. else -> {}
    8. }
    9. false
    10. }
    11. handler.sendEmptyMessage(123) //发送消息

    面试

    .1 Handler、Loop、MessageQuee关系?

    一个线程对应一个 Looper 对应一个 MessageQueue 对应无数个 Handler。

    .2 非UI线程真的不能操作View吗?

            并发访问会造成安全问题,而上锁会变得复杂低效,因此 UI 体系采用单线程模型,系统只是限制了必须在同个线程内进行 ViewRootImpl 的创建和更新。

            更新 UI 会调用 View 的 requestLayout(),然后开始递归查找 ViewParent,最终找到顶层的DecorView。DecorView 的 ViewParent 是 ViewRootImpl 它的 requestLayout() 会调用 checkThread() 检查此线程是不是初始化自己的线程,不是就抛异常。ViewRootImpl 初始化的时候会绑定当前线程,而它是由在主线程的 ActivityThread 的 handleResumeActivity() 中初始化的。

            由调用链可知 ActivityThread 的 handleResumeActivity() 是在 Activity 生命周期 onResume() 之后执行的,因此在 onCreate()、onStart()、onResume() 时 ViewRootImpl 还未初始化,也就可以在子线程中更新了。

    1. public void click(View view) {
    2. new Thread(new Runnable() {
    3. public void run() {
    4. Looper.prepare();
    5. boolean b1 = Looper.myLooper() == Looper.getMainLooper(); //fale,当前线程不是主线程
    6. Toast.makeText(Drawer99_Test_Activity.this,b1+","+b2,Toast.LENGTH_LONG).show();
    7. Looper.loop();
    8. }
    9. }).start();
    10. }

    .3 为什么不用wait/notify而用epoll?

    早期使用的是 wait/notify,Android2.3后来使用 epoll 机制为了可以同时处理 native 消息。

    .4 View.post() 和 Handler.post()的区别?

    View.post() 最终调用的是 Handler.post()。

    .5 如何进行线程切换的?

    子线程持有的 Handler 如果绑定到的是主线程的 Looper (Handler创建的时候需要传入Looper) 的话,那么子线程发送的 Message 就可以由主线程来消费,以此来实现线程切换,执行 UI 更新操作等目的。

    .6 主线程 Looper.loop() 为什么不会导致 ANR?

    loop() 循环本身不会导致 ANR,会出现 ANR 是因为在 loop 循环之内 Message 处理时间过长,无法处理其他事件导致。让程序不退出的话,写一个死循环,那么main方法中的代码永远不会执行完,这样程序就不会自己退出了。正是因为Looper.loop方法这个死循环,它阻塞了主线程,所以我们的app才不会退出。本质上Android就是事件驱动的程序,界面刷新也好,交互也好,本质上都是事件,这些事件最后通通被作为了Message发送到了MessageQueue中。由Looper来进行分发,然后在进行处理。也就是我们的Android程序就是运行在这个死循环中的,一旦这个死循环结束,app也就结束了。

  • 相关阅读:
    一种改进Harris算子的角点特征检测研究-含Matlab代码
    最出名的那些日历APP,结果一点都不够好用...
    LeetCode链表集锦
    vue2的组件通信方式
    我经历过的职场故事
    Requests教程-17-请求代理设置
    17K薪资要什么水平?来看看95后测试工程师的面试全过程…
    LeetCode_位运算_中等_137.只出现一次的数字 II
    【Webpack5】Webpack学习笔记(一)
    python类
  • 原文地址:https://blog.csdn.net/HugMua/article/details/127706938