消息机制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()方法也就跟着退出
所以当子线程中进行耗时操作后需要更新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()方法也就跟着退出
流程:
在主线程创建的时候会创建一个Looper,同时也会在在Looper内部创建一个消息队列。而在创键Handler的时候取出当前线程的Looper,并通过该Looper对象获得消息队列,
然后Handler在子线程中通过MessageQueue.enqueueMessage在消息队列中添加一条Message。通过Looper.loop() 开启消息循环不断轮询调用 MessageQueue.next(),
取得对应的Message并且通过Handler.dispatchMessage传递给Handler,最终调用Handler.handlerMessage处理消息。
在主线程创建的时候会创建一个Looper,同时也会在在Looper内部创建一个消息队列。而在创键Handler的时候取出当前线程的Looper,并通过该Looper对象获得消息队列,
然后Handler在子线程中通过MessageQueue.enqueueMessage在消息队列中添加一条Message。通过Looper.loop() 开启消息循环不断轮询调用 MessageQueue.next(),
取得对应的Message并且通过Handler.dispatchMessage传递给Handler,最终调用Handler.handlerMessage处理消息。
2,子线程为什么不能更新UI
:android的UI访问是没有枷锁的,所以多个线程可以同时访问UI,这样会导致UI会存在不可控制的错误。handler相当于给UI访问加了一个伪锁。
2、一个线程能否创建多个Handler,Handler跟Looper之间的对应关系 ?
参考回答:一个Thread只能有一个Looper,一个MessageQueen,可以有多个Handler。
以一个线程为基准,他们的数量级关系是:Thread(1) : Looper(1) : MessageQueue(1) : Handler(N)。
参考回答:一个Thread只能有一个Looper,一个MessageQueen,可以有多个Handler。
以一个线程为基准,他们的数量级关系是:Thread(1) : Looper(1) : MessageQueue(1) : Handler(N)。
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的访问效率,因为加锁会阻塞某些线程的执行
加锁机制会让UI访问逻辑变的复杂
加锁机制会降低UI的访问效率,因为加锁会阻塞某些线程的执行
6、Looper死循环为什么不会导致应用卡死?
参考回答:
主线程的主要方法就是消息循环,一旦退出消息循环,那么你的应用也就退出了,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。
造成ANR的不是主线程阻塞,而是主线程的Looper消息处理过程发生了任务阻塞,无法响应手势操作,不能及时刷新UI。
阻塞与程序无响应没有必然关系,虽然主线程在没有消息可处理的时候是阻塞的,但是只要保证有消息的时候能够立刻处理,程序是不会无响应的。
主线程的主要方法就是消息循环,一旦退出消息循环,那么你的应用也就退出了,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。
造成ANR的不是主线程阻塞,而是主线程的Looper消息处理过程发生了任务阻塞,无法响应手势操作,不能及时刷新UI。
阻塞与程序无响应没有必然关系,虽然主线程在没有消息可处理的时候是阻塞的,但是只要保证有消息的时候能够立刻处理,程序是不会无响应的。
7、使用Handler的postDealy后消息队列会有什么变化?
参考回答:如果队列中只有这个消息,那么消息不会被发送,而是计算到时唤醒的时间,先将Looper阻塞,到时间就唤醒它。
但如果此时要加入新消息,该消息队列的对头跟delay时间相比更长,则插入到头部,按照触发时间进行排序,队头的时间最小、队尾的时间最大。
handler 导致内存泄漏的情况:
但如果此时要加入新消息,该消息队列的对头跟delay时间相比更长,则插入到头部,按照触发时间进行排序,队头的时间最小、队尾的时间最大。
handler 导致内存泄漏的情况:
public class MainActivity extends AppCompatActivity {
//private ArrayList<String> mList;
//private MyListAdapter mListAdapter;
//private ListView mLv;
private ImagerView iv;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case value:
iv.setImageResoure(...)
break;
}
}
};
此时就相当于 这个handler是一个内部类, 内部类会持有外部类的引用(iv).
如果此时activty的被退出了. handler持有他的引用,所有这个activity 并不会被销毁,其实还是在内存中.所有就造成的Context泄漏.
解决办法:
//使用静态的内部类 + 虚引用可以解决这个问题.
// 静态的内部类,是随着类的加载而加载,所以静态的内部类就只能访问静态的变量,所以就不就可以解决引用持有问题.
在activity 的ondestory()的时候,
调用mHandler.removeCallbacksAndMessages(null);
//使用静态的内部类 + 虚引用可以解决这个问题.
// 静态的内部类,是随着类的加载而加载,所以静态的内部类就只能访问静态的变量,所以就不就可以解决引用持有问题.
在activity 的ondestory()的时候,
调用mHandler.removeCallbacksAndMessages(null);
浙公网安备 33010602011771号