ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
### Handler #### 1、谈谈消息机制Handler作用 ?有哪些要素 ?流程是怎样的 ? > * 参考回答: > * 负责**跨线程通信**,这是因为**在主线程不能做耗时操作,而子线程不能更新UI**,所以当子线程中进行耗时操作后需要更新UI时,通过Handler将有关UI的操作切换到主线程中执行。 > * 具体分为四大要素 > * **Message(消息)**:需要被传递的消息,消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息。 > * **MessageQueue(消息队列)**:负责消息的存储与管理,负责管理由 Handler发送过来的Message。读取会自动删除消息,单链表维护,插入和删除上有优势。在其next()方法中会无限循环,不断判断是否有消息,有就返回这条消息并移除。 > * **Handler(消息处理器)**:负责Message的发送及处理。主要向消息池发送各种消息事件(Handler.sendMessage())和处理相应消息事件(Handler.handleMessage()),按照先进先出执行,内部使用的是单链表的结构。 > * **Looper(消息池)**:负责关联线程以及消息的分发,在该线程下从 MessageQueue获取 Message,分发给Handler,Looper创建的时候会创建一个 MessageQueue,调用loop()方法的时候消息循环开始,其中会不断调用messageQueue的next()方法,当有消息就处理,否则阻塞在messageQueue的next()方法中。当Looper的quit()被调用的时候会调用messageQueue的quit(),此时next()会返回null,然后loop()方法也就跟着退出。 > * 具体流程如下 > > ![](https://user-gold-cdn.xitu.io/2019/3/11/1696ad811957795d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) > > * 在主线程创建的时候会创建一个Looper,同时也会在在Looper内部创建一个消息队列。而在创键Handler的时候取出当前线程的Looper,并通过该Looper对象获得消息队列,然后Handler在子线程中通过**MessageQueue.enqueueMessage**在消息队列中添加一条Message。 > * 通过**Looper.loop()** 开启消息循环不断轮询调用 **MessageQueue.next()**,取得对应的Message并且通过**Handler.dispatchMessage**传递给Handler,最终调用**Handler.handlerMessage**处理消息。 #### 2、一个线程能否创建多个Handler,Handler跟Looper之间的对应关系 ? > * 参考回答: > * **一个Thread只能有一个Looper,一个MessageQueen,可以有多个Handler** > * **以一个线程为基准,他们的数量级关系是: Thread(1) : Looper(1) : MessageQueue(1) : Handler(N)** #### 3、软引用跟弱引用的区别 > * 参考回答: > * **软引用(SoftReference)**:如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以一直被程序使用。 > * **弱引用(WeakReference)**:如果一个对象只具有弱引用,那么在垃圾回收器线程扫描的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 > * 两者之间**根本区别**在于:只具有弱引用的对象拥有更短暂的生命周期,可能随时被回收。而只具有软引用的对象只有当内存不够的时候才被回收,在内存足够的时候,通常不被回收。 > > ![](https://user-gold-cdn.xitu.io/2019/3/11/1696c45ababaed39?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) > > * 推荐文章: [Java中的四种引用类型:强引用、软引用、弱引用和虚引用](https://segmentfault.com/a/1190000015282652) #### 4、Handler 引起的内存泄露原因以及最佳解决方案 > * 参考回答: > * 泄露原因: > * Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。 > * 解决方案: > * **将 Handler 定义成静态的内部类,在内部持有Activity的弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null)及时移除所有消息。** #### 5、为什么系统不建议在子线程访问UI? > * 参考回答: > * Android的UI控件不是**线程安全**的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态 > * 这时你可能会问为何系统不对UI控件的访问加上锁机制呢?因为 > * 加锁机制会让UI访问逻辑变的复杂 > * 加锁机制会降低UI的访问效率,因为加锁会阻塞某些线程的执行 > > ![](https://user-gold-cdn.xitu.io/2019/3/13/16975fdd364417e0?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) > #### 6、Looper死循环为什么不会导致应用卡死? > * 参考回答: > * 主线程的主要方法就是**消息循环**,一旦退出消息循环,那么你的应用也就退出了,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。 > * 造成**ANR**的不是主线程阻塞,而是主线程的Looper消息处理过程发生了**任务阻塞**,无法响应手势操作,不能及时刷新UI。 > * **阻塞与程序无响应**没有必然关系,虽然主线程在没有消息可处理的时候是阻塞的,但是只要保证有消息的时候能够立刻处理,程序是不会无响应的。 #### 7、使用Handler的postDealy后消息队列会有什么变化? > * 参考回答: > * 如果队列中只有这个消息,那么消息不会被发送,而是计算到时唤醒的时间,先将Looper阻塞,到时间就唤醒它。但如果此时要加入新消息,该消息队列的对头跟delay时间相比更长,则插入到头部,按照触发时间进行排序,队头的时间最小、队尾的时间最大 #### 8、可以在子线程直接new一个Handler吗?怎么做? > * 参考回答: > * 不可以,因为在**主线程**中,Activity内部包含一个Looper对象,它会自动管理Looper,处理子线程中发送过来的消息。而对于**子线程**而言,没有任何对象帮助我们维护Looper对象,所以需要我们自己手动维护。所以要在子线程开启Handler要先创建Looper,并开启Looper循环 > > ![](https://user-gold-cdn.xitu.io/2019/3/14/1697b3a7257d670a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) > > * 推荐文章:[Android异步消息处理机制完全解析,带你从源码的角度彻底理解](https://blog.csdn.net/guolin_blog/article/details/9991569) #### 9、Message可以如何创建?哪种效果更好,为什么? > * 参考回答:可以通过三种方法创建: > * 直接生成实例**Message m = new Message** > * 通过**Message m = Message.obtain** > * 通过**Message m = mHandler.obtainMessage()** > * 后两者效果更好,因为Android默认的消息池中消息数量是10,而后两者是直接在消息池中取出一个Message实例,这样做就可以避免多生成Message实例。