使用handler最简单的方式:直接new一个Handler的对象
Handlerhandler=newHandler();
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
做了三件事
1、校验是否可能内存泄漏
2、初始化一个Looper mLooper
3、初始化一个MessageQueue mQueue
1、校验是否存在内存泄漏
Handler的构造函数中首先判断了FIND_POTENTIAL_LEAKS的值,为true时,会获取该对象的运行时类,如果是匿名类,成员类,局部类的时候判断修饰符是否为static,不是则提示可能会造成内存泄漏。
问:为什么匿名类,成员类,局部类的修饰符不是static的时候可能会导致内存泄漏呢?
答:因为,匿名类,成员类,局部类都是内部类,内部类持有外部类的引用,如果Activity销毁了,而Hanlder的任务还没有完成,那么Handler就会持有activity的引用,导致activity无法回收,则导致内存泄漏;静态内部类是外部类的一个静态成员,它不持有内部类的引用,故不会造成内存泄漏
这里我们可以思考为什么非静态类持有外部类的引用?为什么静态类不持有外部类的引用?
问:使用Handler如何避免内存泄漏呢?
答:使用静态内部类的方式
2、初始化初始化一个Looper mLooper
这里获得一个mLooper,如果为空则跑出异常:
"Can't create handler inside thread that has not called Looper.prepare() "
如果没有调用Looper.prepare()则不能再线程里创建handler!我们都知道,如果我们在UI线程创建handler,是不需要调用这个方法的,但是如果在其他线程创建handler的时候,则需要调用这个方法。那这个方法到底做了什么呢?我们去看看代码:
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
先取sThreadLocal.get()的值,结果判断不为空,则跑出异常“一个线程里只能创建一个Looper”,所以sThreadLocal里存的是Looper;如果结果为空,则创建一个Looper。那我们再看看,myLooper()这个方法的代码:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
总上我们得出一个结论:当我们在UI线程创建Handler的时候,sThreadLocal里已经存了一个Looper对象,所以有个疑问:
当我们在UI线程中创建Handler的时候sThreadLocal里的Looper从哪里来的?
我们知道,我们获取主线程的Looper需要调用getMainLooper()方法,代码如下:
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
所以我们跟踪一下这个变量的赋值,发现在方法prepareMainLooper()中有赋值,我们去看看代码:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
第一步调用了prepare(false),这个方法我们刚才已经看了,是创建一个Looper对象,然后存到sThreadLocal中;
然后判断sMainLooper是否为空,空则抛出异常。sMainLooper不为空,则sMainLooper = myLooper()。至此sMainLooper对象赋值成功,所以,我们需要知道prepareMainLooper()这个方法在哪调用的,跟一下代码,就发现在ActivityThread的main方法中调用了Looper.prepareMainLooper();。
现在真相大白:
当我们在UI线程中创建Handler的时候sThreadLocal里的Looper是在ActivityThread的main函数中调用了prepareMainLooper()方法时初始化的
ActivityThread是一个在一个应用进程中负责管理Android主线程的执行,包括活动,广播,和其他操作的类
3、初始化一个MessageQueue mQueue
从代码里我们看出这里直接调用了:mLooper.mQueue来获取这个对象,那这个对可能在Looper初始化的时候就产生了。我们去看看Looper的初始化代码:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
代码很简单,就是创建了MessageQueue的对象,并获得了当前的线程。
至此,Handler的创建已经完成了,本质上就是获得一个Looper对象和一个MessageQueue对象!
二、使用Handler发送消息
Handler的发送消息的方式有很多,我们跟踪一个方法sendMessage方法一直下去,发现最后竟然调用了enqueueMessage(queue, msg, uptimeMillis),那我们看看这个方法的代码:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这段代码做了三件事:
1、给msg.target赋值,也就是Handler对象
2、给消息设置是否是异步消息。
3、调用MessageQueue 的enqueueMessage(msg, uptimeMillis)方法
我们只关注第三步:这一步把Handler的发送消息转给了MessageQueue的添加消息的方法。
所以至此,Handler发送消息的任务也已经完成了,本质上就是调用MessageQueue自己的添加消息的方法!
三、MessageQueue添加消息
MessageQueue的构造函数代码如下:
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
也没做什么特别的事情。我们去看看enqueueMessage(msg, uptimeMillis)方法代码:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
代码很长,但是通过观察这段代码我们发现这个MessageQueue实际上是个链表,添加消息的过程实际上是一个单链表的插入过程。
所以我们知道了Handler发送消息的本质其实是把消息添加到MessageQueue中,而MessageQueue其实是一个单链表,添加消息的本质是单链表的插入
四、从消息队列里取出消息
我们已经知道消息如何存储的了,我们还需要知道消息是如何取出的。
所以我们要看一下Looper.loop();这个方法:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
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);
}
}
}
代码太长我删了部分代码。可以看出这个方法主要的功能是很简单的。
获取Looper对象,如果为空,抛异常。
获取消息队列MessageQueue queue
遍历循环从消息队列里取出消息,当消息为空时,循环结束,消息不为空时,分发出去!
但是实际上当没有消息的时候queue.next()方法会被阻塞,并标记mBlocked为true,并不会立刻返回null。而这个方法阻塞的原因是nativePollOnce(ptr, nextPollTimeoutMillis);方法阻塞。阻塞就是为了等待有消息的到来。那如果在有消息加入队列,loop()方法是如何继续取消息呢?
这得看消息加入队列的时候有什么操作,我们去看刚才的enqueueMessage(msg, uptimeMillis)方法,发现
if (needWake) {
nativeWake(mPtr);
}
当needWake的时候会调用一个本地方法唤醒读取消息。
所以这里看一下消息分发出去之后做了什么?
msg.target.dispatchMessage(msg);
上面讲过这个target其实就是个handler。所以我们取handler里面看一下这个方法代码
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
代码非常简单,当callback不为空的时候调用callback的handleMessage(msg)方法,当callback为空的时候调用自己的handleMessage(msg)。一般情况下我们不会传入callback,而是直接复写Handler的handleMessage(msg)方法来处理我们的消息。
[流程链接](163807cb38a2cda1 (1240×599) (xitu.io)
)
总结
Handler的整个工作流程
主要就是用于线程间通信,完成线程切换。需要四部分协作完成,分别是Handler(处理者),Message(消息载体),MessageQueue(消息队列),Looper(循环器)。
Message把需要在线程间发送的消息封装在Message对象中
MessageQueue为消息队列,内部其实是单向链表,根据时间维度内部有同步消息和异步消息和消息屏障的概念,读取列表中的消息,如果发现消息屏障,则跳过后面的同步消息,直接执行异步消息,然后才执行同步消息。handler默认发送消息是同步消息,当设置延迟时间就变成异步消息,优先执行。
3.如果没有符合条件的消息,会处理一些不紧急的任务(IdleHandler)
Looper是一个死循环驱动器类似传送带,在looper源码中就是通过for循环从MessageQueue取出消息,有就调用msg.target.dispatchMessage(msg)处理,而这个msg.target对象就是handler。若没有就进入休眠,但并不会阻塞主线程,因为这个MessageQueue没有消息时使用的是Linux的epoll机制,休眠状态下并不会消耗CPU资源。Looper如果不是死循环,我们的程序运行完就闪退结束了,因为实际上Activity的生命周期都是ActivityManagerService管理,就是依靠主线程的Looper.loop将不同生命周期的消息发送到主线程。
handler:一方面handler重写send Message或者post去将消息发送到messageQueue中,不管哪种最终都会调用enqueueMessage()方法。另一方面等looper处理完消息后返回给hanlder,handler通知主线程处理消息进行UI操作。
在handler的源码中做了三件事:
1、校验是否可能内存泄漏:内部类持有外部acticity的引用,这也是handler经常报黄提示泄漏的原因。所以我们避免的话一遍就是 <1> 有延时消息,要在Activity销毁的时候移除Messages。<2>. 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者Activity使用弱引用。
2、获取Looper对象。但是里面不需要执行不需要使用Looper.prepare()和Looper.loop()方法,因为在acitivity启动流程入口ActivityThread已经创建并开启了looper轮询。
3、获取MessageQueue对象,因为meassgaeQueue是在looper中就实例化好了。
通信准备时在主线程通过Looper.prepare实例化了Looper并保存在ThreadLocal中,threadLocal是线程存储变量的一个类,Looper。prepare源码中只能调用一次不然会抛异常所以messageQueue在线程中唯一存在。Looper.prepare执行完后会执行looper.loop()他源码里创建了,messageQueue对象并作为成员变量与looper关联接着for循环从messageQueue抽取消息回调msg.target.dispathmessage()方法。。,然后自动进入消息循环中,此时handler绑定了当前的looper,messageQueue。然后消息入队中handler发送message到消息队列messageQueue 中。looper按照先进先出的同步抽取方式将message分发给当前对应的handler,在消息循环中如果消息队列为空就进入阻塞状态。
在主线程创建的时候为主线程创建一个Looper,创建Looper的同时在Looper内部创建一个消息队列。而在创键Handler的时候取出当前线程的Looper,并通过该Looper对象获得消息队列,然后Handler在子线程中发送消息也就是在该消息队列中添加一条Message。最后通过Looper中的消息循环取得这条Message并且交由Handler处理。