- 为什么引入Handler(说一下Handler的背景,UI线程不允许耗时操作和只能在UI线程操作UI的冲突)
- Handler的四大工作组件(Handler的工作模型)
- 为什么在主线程使用Handler不需要显式指定Looper(要讲到Looper的存储,ThreadLocal的工作原理,怎么存数据和取数据的)(Handler的无参构造函数自动绑定当前线程的Looper,Looper、Thread、MessageQueue的对应关系)
- Handler究竟是如何实现线程切换的(主要说到target对象,最后消息又作为参数传回给handler)
- Handler导致内存泄漏的原因(两条引用关系,会涉及到内部类的知识)
- 可以在子线程处理消息吗(在子线程创建Handler要具备哪些条件)
- handler在多个线程中发送消息和取出消息会出现死锁问题吗?(这里要讲清楚handler、looper和thread的对应关系,最终handler发送的消息其实都是放入了一个MessageQueue中,所以MessageQueue可以看作是一个共享资源,临界资源,要将到两个加了synchronized的两个方法)
- 如何创建Message(obtain()方法,内存抖动)
- Looper死循环为什么不会卡死(要讲到阻塞和ANR的区别)
什么是Handler
消息机制的模型: Message:需要传递的消息,可以传递数据; MessageQueue:消息队列,但是它的内部实现并不是用的队列,实际上是通过一个单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优势。主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next); Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage); Looper:不断循环执行(Looper.loop),从MessageQueue中读取消息,按分发机制将消息分发给目标处理者。
消息机制的架构 在子线程执行完耗时操作,当Handler发送消息时,将会调用 MessageQueue.enqueueMessage ,向消息队列中添加消息。 当通过 Looper.loop 开启循环后,会不断地从消息队列中读取消息,即调用 MessageQueue.next(这也是一个死循环),取出待处理的Message。 然后Message调用目标Handler(即发送该消息的Handler)的 dispatchMessage 方法传递消息,然后返回到Handler所在线程,目标Handler收到消息,调用 handleMessage 方法,接收消息,处理消息。
Looper、Thread、MessageQueue的联系
普通的线程是没有looper的,如果需要looper对象,那么必须要先调用Looper.prepare方法,而且一个线程只能有一个looper (为什么主线程没有调用Looper.prepare,为什么一个线程只能有一个Looper) Looper的构造函数是私有的,只能通过其prepare()方法构建出来,当调用了Looper的prepare()方法后,会调用**ThreadLocal中的get()**方法检查ThreadLocalMap中是否已经set过Looper。(关于ThreadLocal的介绍详看多线程-ThreadLocal)。 如果有,则会抛出异常,提示每个线程只能有一个Looper,如果没有,则会往ThreadLocalMap中set一个new出来的Looper对象。 这样可以保证ThreadLocalMap和Looper一一对应,即一个ThreadLocalMap只会对应一个Looper。而这里的ThreadLocalMap是在Thread中的一个全局变量,也只会有一个,所以就可以保证一个Thread中只有一个Looper。
Handler是如何实现线程间的切换的
在子线程中,通过handler.sendMessage(message)发送一条消息,最终会调用 MessageQueue的enqueueMessage(),成功将一条消息插入到消息队列中,并且会给 msg.target = this ,赋值为发送Message的handler自身。 而Loop死一个死循环不停的去消息队列中取消息,通过next()方法取到消息,然后会执行Message.target.dispatchMessage(msg);。其中Message.target就是一个Handler,准确的来说就是发送这条消息的Handler。 因此在处理消息的阶段,我们通过Message.target找到发送这条消息的handler。然后调用它的dispatchMessage(msg)方法,将消息本身作为参数又传回给了Handler。最终我们在主线程中执行handlerMessage的逻辑,实现线程的切换。 关键的源码:
//Handler的方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
public static void loop() {
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg);
}
}
}
/**
*handler的方法
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
Handler导致内存泄漏的原因
内存泄漏的定义:本该被回收的对象没有被回收而停留在堆内存中。 主线程的Looper对象的生命周期 = 改应用的生命周期 在Java中非静态内部类和匿名内部类默认持有外部类的引用(为什么详见Java基础-Java内部类)
- 消息队列中的消息通过target属性,持有handler的引用
- Handler是Activity的内部类,默认持有Activity的引用
因此形成了一条引用链。 如果MessageQueue中有消息,但是此时销毁Activity,由于上述的引用关系。GC不会回收Activity,从而导致Activity的内存泄漏。 造成内存泄漏的条件:
- 存在未处理的消息,msg引用handler
- handler的生命周期 = 外部类的生命周期,handler引用activity
解决方案:
- 将handler设置为静态内部类,并且采用弱引用的方式引用外部类
- 当外部类的类的生命周期结束时,清空MessageQueue,使得msg对handler的引用不复存在。
可以在子线程处理消息吗,可以在子线程new Handler()吗
通常我们是在子线程发送消息,主线程处理消息,因为在ActivityThread中的main()已经对Looper进行了prepare()操作,所以可以直接在主线程new Handler,Handler的构造方法中会自动去绑定当前线程的Looper。 main方法是整个android应用的入口,在主线程中调用Looper.prepare()是为了创建一个Looper对象,并将该对象存储在当前线程的ThreadLocal中,每个线程都会有一个ThreadLocal,它为每个线程提供了一个本地的副本变量机制,实现了和其它线程隔离,并且这种变量只在本线程的生命周期内起作用,可以减少同一个线程内多个方法之间的公共变量传递的复杂度。 Looper.loop()方法是为了取出消息队列中的消息并将消息发送给指定的handler,通过**msg.target.dispatchMassage()**方法(因此主线程的Looper的生命周期其实就是整个应用的生命周期) 当然我们也可以在子线程处理消息,但是在子线程new Handler()之前,我们需要先调用Looper的prepare()方法初始化Looper,再调用Looper的loop()方法使Looper运转。
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare(); //初始化Looper
new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Looper.loop();//开启消息循环
}
})
多个Handler往MessageQueue中添加数据,其内部是如何保证线程安全的?
一个线程只有一个Looper,但是一个Looper可以绑定多个Handler,也就是说不同的Handler发送的消息最终在同一个MessageQueue,而这些消息可能来自不同的子线程。 因此MessageQueue作为一个共享资源,可以由多个子线程共享,那么当多个线程同时访问共享资源时,就会出现线程安全问题,例如数据竞争、死锁等。 MessageQueue内部会使用锁来保证同一时刻只有一个线程可以访问它,从而避免多个线程同时修改MessageQueue的数据结构。 添加消息的方法enqueueMessage()中有synchronize修饰,取消息的方法next()中也有synchronize修饰。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//同步锁机制保证MessageQueue的线程安全
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
·····
}
return true;
}
使用Message时应该如何创建它?
使用Message的obtain()方法创建,直接new出来容易造成内存抖动。 内存抖动是由于频繁new对象,gc频繁回收导致,而且由于可能被别的地方持有导致无法及时回收所以会导致内存占用越来越高。 使用obtain()对内存复用,可以避免内存抖动的发生。其内部维护了一个Message池,其是一个链表结构,当调用obtain()的时候会复用表头的Message,然后会指向下一个。如果表头没有可复用的message则会创建一个新的对象,这个对象池的最大长度是50。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
Looper死循环为什么不会导致应用卡死?
https://blog.csdn.net/jb_home/article/details/113822835 卡死就是ANR,产生的原因有2个: 1、在5s内没有响应输入的事件(例如按键,触摸等), 2、BroadcastReceiver在10s内没有执行完毕。 事实上我们所有的Activity是在主线程的消息循环中处理消息和事件。在没有消息产生的时候,主线程的looper会被block(阻塞),主线程会进入休眠,一旦有输入事件或者Looper添加消息的操作后主线程就会被唤醒,从而对事件进行响应,所以不会导致ANR 简单来说Looper的阻塞表明没有事件输入,而ANR是由于有事件没响应导致,所以looper的死循环并不会导致应用卡死。 虽然Looper的死循环并不会导致应用卡死,但为什么主线程需要设置一个Looper呢,仅仅是为了方便创建Handler吗? 这里其实就是涉及线程的知识,线程是CPU进行资源调度的最小的单位。线程的实体是一段可执行的程序,如果程序执行完成会退出,线程的生命周期也就结束了。但对于Android的主线程,我们要求它一直运行,保证APP的存活。所以通过死循环让程序一直运行下去不会退出,既然是死循环,那主线程如何去处理其他任务呢? 问题二:既然是死循环,那是不是特别消耗CPU的资源? 其实并不是特别消耗CPU的资源,涉及Liunx中的pipe和epoll机制。在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的**nativePollOnce()**
方法里。此时主线程会释放CPU资源进入休眠状态,直到下一个消息到达,通过往pipe管道写端写入数据来唤醒主线程工作。 具体采用的是epoll机制,一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或者写就绪),则立即通知相应程序进行读或者写操作。所以主线程大多时候处于休眠状态,并不消耗CPU资源。
Handler是如何与Looper关联的
在Handler中有两个全局变量mLooper和mQueue代表当前Handler关联的Looper和消息队列,并在构造函数中进行了初始化,重要的就是调用了:Looper.myLooper():
public static @Nullable Looper myLooper() {
return sThreadLocal.get();//返回当前线程的Looper
}
其实还是调用的线程局部变量sThreadLocal,获取当前线程的Looper,这里需要注意的是,如果当前线程没有关联的Looper,这个方法会返回null。 注意:Handler在哪个线程创建的,就跟哪个线程的Looper关联,也可以在Handler的构造方法中传入指定的Looper
Handler对象属于哪一个线程
在主线程中我们一般只需要new Handler()即可使用异步消息机制,并没有带参数,因为Handler的构造函数会自动去绑定当前线程的Looper,而Looper又会去创建MessageQueue,因此在这种情形下handler是运行在主线程,这也是为什么,我们在子线程发送消息,其实是发送到主线程下的Looper管理的MessageQueue中。不管消息是从哪一个线程发送过来的,最终又都会通过msg.target字段交回给Handler出handlerMessage()。所以最终消息在主线程执行,实现跨线程通信。 我们直到Handler还有一个构造方法,可以指定特定线程的Looper,这时的Handler无论在哪一个线程创建,它所持有的Looper和MessageQueue都是构造方法传入的那一个Looper。而这个Looper与指定线程绑定,此时相当于在当前线程下构造了一个与指定线程绑定的Handler对象,可以通过该Handler对象向指定线程发送消息,当然该Handler对象的handlerMessage也是运行在指定线程上的。 handler对象所绑定的线程其实并不取决于该handler对象由哪个线程构建,而是取决于该handler对象所绑定的Looper属于哪个线程。Android Handler机制 - handleMessage究竟在哪个线程执行
使用Hanlder的postDealy()后消息队列会发生什么变化?
Handler发送消息到消息队列,消息队列是一个时间优先级队列,内部是一个单向链表。发动postDelay之后会将该消息进行时间排序存放到消息队列中 如果队列中只有这个消息,那么消息不会被发送,而是计算到时唤醒的时间,先将Looper阻塞,到时间就唤醒它。 但如果此时要加入新消息,该消息队列的对头跟delay时间相比更长,则插入到头部,按照触发时间进行排序,队头的时间最小、队尾的时间最大