-
知道的都知道,不知道的慢慢了解
-
参考资料:
-
自己的资料:
一、是什么?
- Android消息机制就是Handler机制,也叫异步处理机制,用于解决子线程无法更新UI的问题。
二、组成
- 它有5部分组成:Handler Message MessageQueue Looper ThreadLocal
1、操作流程
Handler发送Message消息到MessageQueue消息队列中,Looper不断从消息队列中取出消息,交给Message.target,即Handler。Handler调用dispatchMessage方法分发消息,并调用handleMessage处理消息。
2、干什么的
①MessagQueue:底层数据结构是单链表
- MessageQueue#enqueueMessage()方法,向队列中插入消息。
- MessageQueue#next()方法,从队列中取出消息。
②Looper:
- Looper的loop()方法中有一个死循环,在使用Looper的时候有几个注意事项:
子线程使用Handler的三步骤
- 1’ Looper.prepare()创建一个Looper对象
- 2’ 创建Handler对象
- 3’ Looper.loop()开启循环
Q:死循环卡死主线程?
- A:Looper.loop()中虽然存在一个for死循环,但是当消息队列中没有消息时,会阻塞在loop()中的queue.next()中的nativePollOnce方法中,此时,主线程释放资源进行休眠。当有消息到达会有事务发生时,通过往pipe管道里写入数据唤醒主线程。这样一来,主线程大多时候都处于休眠状态,并不会消耗大量CPU资源,所以loop死循环不会阻塞主线程
Q:为什么主线程不用做以上操作?
- A:因为ActivityThread的main方法中已经做了这些操作
3、ThreadLocal:
ThreadLocalMap
- ThreadLocal有一个静态内部类ThreadLocalMap,以键值对的形式存储数据。ThreadLocalMap没有实现Map接口,而是通过一维数组去维护key-value的存储。
为什么一个Thread只有一个Looper?
- 1’ 每一个Thread类都有一个ThreadLocalMap对象
- 2’ Looper的prepare()方法中,初始化了Looper对象,调用ThreadLocal的set方法,并把Looper对象传进去
- 3’ ThreadLocal中的set方法,通过当前线程Thread获取到ThreadLocalMap对象,存储key-value,key是ThreadLocal对象,value是传进来的Looper对象
- 这样一来,一个线程持有一个Looper对象
线程和Handler、Looper、MessageQueue的关系
- 一个线程对应一个Looper,对应一个MessageQueue,对应多个Handler
4、IdelHandler:空闲机制
三、内存泄漏
Q:Handler为什么会导致内存泄漏?
- 非静态内部类Handler持有外部类Activity的引用,Handler被Message所持有(Message.target),Looper中的loop循环会无限的从MessageQueue中取Message消息。当Activity finish的时候,Activity 的引用被Handler所持有,无法回收,因此会导致内存谢啦
Q:使用Handler如何避免内存泄漏?
- A:要注意两点
- 在使用Handler的时候,要注意内存泄漏,因为非静态内部类会持有外部类的一个引用,从而导致无法被GC,所以要创建静态内部类,并且使用弱引用
- 子线程调用mHandler.sendMessage()方法时候,如果当前Activity已经被注销,但是子线程还在调用,这个时候必须中断子线程
相关延伸
Q:子线程如何正确的中断?
- (1)设置标志位,通过配合break实现线程中断
- (2)使用Thread#interrupt()方法,配合return实现线程中断
Q:四大引用?
- 强引用(StrongReference):不会被回收,只有JVM停止的时候才会终止
- 软引用(SoftReference):GC时并且内存临近阀值或不足的时候,才会被回收
- 弱引用(WeakReference):GC时,不管内存是否充足
- 虚引用(PhantomReference):随机于GC的回收。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收
Q:一般什么情况下会导致内存泄漏问题?
- 资源对象没关闭造成的内存泄漏(如: Cursor、File、Bitmap#recycle()等)
- 全局集合类强引用没清理造成的内存泄漏(特别是 static 修饰的集合)
- 接收器、监听器注册没取消造成的内存泄漏,如广播,eventsbus
- Activity的Context造成的泄漏,可以使用 ApplicationContext
单例中的static成员间接或直接持有了activity的引用
非静态内部类持有父类的引用,如非静态handler持有activity的引用
Q:如何检查和分析内存泄漏?
- 因为内存泄漏是在堆内存中,所以对我们来说并不是可见的。通常我们可以借助MAT、LeakCanary等工具来检测应用程序是否存在内存泄漏。
- 1、MAT是一款强大的内存分析工具,功能繁多而复杂。
- 2、LeakCanary则是由Square开源的一款轻量级的第三方内存泄漏检测工具,当检测到程序中产生内存泄漏时,它将以最直观的方式告诉我们哪里产生了内存泄漏和导致谁泄漏了而不能被回收
- AS自带的Profile
四、其他相关问题
Q:Handler的Delay的事件是按照什么时间计算的?
- A:Android中计算时间间隔Android系统提供了两种方法:
- SystemClock.uptimeMillis():从开机到现在的毫秒数(手机睡眠的时间不包括在内)
- System.currentTimeMillis():从1970年1月1日 UTC到现在的毫秒数;
- Handler的Delay使用的计算时间方法是SystemClock.uptimeMillis()
Q:假设先postDelay 10ms,再postDelay 1ms,你简单描述一下,怎么处理这2条消息?
- A:先传入一个延时为10ms的消息进入MessageQueue中,因为该消息延时,假设当前消息队列中没有消息,则会直接将消息放入队列,因为loop一直在取消息,但是这里有延时就会阻塞10ms,当然这不考虑代码执行的时间;
- 然后延时1ms的消息进入时,会和之前的10ms的消息进行比较,根据延时的大小进行排序插入,延时小的在前边,所以这时候就把1ms的消息放在10ms的前边,然后唤醒,不阻塞,继续执行取消息的操作。
- 发现还是有延时1ms,所以也会继续阻塞1ms,直到阻塞1ms之后或者又有新的消息进入队列唤醒,直到获取到1ms延时消息,在loop中,通过调用handler的dispatchMessage方法,判断消息的callback或者Handler的callback不为null就回调对应的callback,否则就执行handler的handleMessage方法,我们就可以根据情况处理消息了;10ms的延时消息的处理也是一致,延时的时间到了就交给返回给looper,然后给handler处理
Q:多个线程给 MessageQueue 发消息,如何保证线程安全?
- 通过对MessageQueue加锁来保证线程安全。
Q:非UI线程真的不能修改UI?
五、主线程消息循环机制(不在handler的知识面内)
- ActivityThread通过ApplicationThread和AMS进行进程间通讯,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向Handler发送消息,Handler收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环机制