Handler
无论是在代码的使用中还是面试,应用的频率都非常高,这就要求我们将Handler
的使用及原理研究透彻。下面根据博客以及相关的一些资料,根据自己对Handler
的理解,写下这篇文章用于记录,并加深印象;如有错误之处,请谅解。
看下面这个Handler
简单使用的例子:
private Handler mHandler = new Handler(){//新建Handler并处理消息
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1000:
Log.e("LHC", "Handler Msg.");
break;
default:
break;
}
}
};
new Thread(new Runnable() {//建立子线程,并在线程中发送消息
@Override
public void run() {
mHandler.sendEmptyMessage(1000);
}
}).start();
这样就完成了一个Handler
的简单使用。
下面我们来一步步分析,这个过程是怎么实现的。先来看Handler
的创建,如下:
1 public Handler() {
2 this(null, false);
3 }
4 public Handler(Callback callback, boolean async) {
5 if (FIND_POTENTIAL_LEAKS) {
6 final Class<? extends Handler> klass = getClass();
7 if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
8 Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
9 }
10 }
11 mLooper = Looper.myLooper();
12 if (mLooper == null) {
13 throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
14 }
15 mQueue = mLooper.mQueue;
16 mCallback = callback;
17 mAsynchronous = async;
18 }
创建过程其实执行的是Handler(Callback callback, boolean async)
方法。来具体看这个方法。
- 第
5~10
行,执行了一个if语句,目的是为了判断新建的Handler
对象是否是非静态的,如果是就显示警告; - 第
11
行,生成了一个新的Looper
对象。 - 第
12~14
行,判断生成的mLooper
是否为空,为空是抛出异常,表示进行创建Handler
的线程,此线程还没有调用Looper.prepare()
进行Looper
初始化。 - 第
15
行,初始化MessageQueue
对象mQueue
。这个是将Looper
中的mQueue
赋值给它的。 - 第
16
行,初始化Callback
接口对象mCallback
。 - 第
17
行,初始化是否异步标记。
第11行代码,生成了一个新的Looper
对象,我们看看其内部实现,代码如下:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
sThreadLocal
是ThreadLocal<Looper>
对象,通过get
方法就可获取线程中对应的Looper
。如果返回值为null
,就表示线程还没有关联Looper
,也就是还没有初始化一个Looper
。初始化的方法如下:
1 public static void prepare() {
2 prepare(true);
3 }
4 private static void prepare(boolean quitAllowed) {
5 if (sThreadLocal.get() != null) {
6 throw new RuntimeException("Only one Looper may be created per thread");
7 }
8 sThreadLocal.set(new Looper(quitAllowed));
9 }
这就是Looper
的两个初始化方法,具体看下带参数的方法:
- 第
5~7
行,在if中判断获取的Looper
对象时否为空,不为空时,抛出异常,表示一个线程只能初始化一个Looper
。 - 第
8
行,调用Looper
的构造方法生成一个对象,并将其设置到sThreadLocal
中。
那么在构造方法中有进行了什么操作呢?看代码:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在这个构造方法中完成了MessageQueue
的创建,及当前线程的保存。这样在调用new Handler()
时,通过代码mQueue = mLooper.mQueue;
将此时新建的mQueue
赋值给了Handler
中的mQueue
了。
通过上面的解释,我们也就明白了为什么在子线程中不能直接new Handler()
,而是需要先进行调用Looper.prepare()
方法了。那么在UI线程中为什么有可以了呢?这是因为在ActivityThread
类中已经初始化了,具体下面在说。
Handler
建立后,就是消息的发送了,如例子代码:
mHandler.sendEmptyMessage(1000);
我们进入看看其具体的实现,代码如下:
public final boolean sendEmptyMessage(int what) {
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
在sendEmptyMessageDelayed(int what, long delayMillis)
方法中,将我们传入的what
封装成了一个Message
信息,然后在传给方法sendMessageDelayed(msg, delayMillis)
。我们继续深入,看代码:
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return 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);
}
封装的Message
经过传递到了Handler中的enqueueMessage(queue, msg, uptimeMillis)
方法,在方法中给target
进行了赋值(这里的target
就是Message
中的Handler
对象),并进行了异步判断,最终将Message
传递给了方法MessageQueue
类中的enqueueMessage(msg, uptimeMillis)
。那么在这个方法中做了哪些处理呢?看代码:
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
......
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 {
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;
......
}
return true;
}
这个方法就实现了将封装好的Message
消息,加入到了MessageQueue
消息队列当中。MessageQueue是按照Message触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序.
那么我们是在哪里进行取消息的呢?是在ActivityThread
之中,我们来看代码:
public static void main(String[] args) {
......
Looper.prepareMainLooper();
......
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
可以看到在ActivityThread
中,我们调用了Looper.prepareMainLooper();
进行了初始化,生成了主线程Looper
,在通过调用Looper.loop();
来进行消息获取。代码如下:
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);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
}
}
在方法中用for
生成了一个死循环,在里面循环调用queue.next()
获取Message
,当获取的消息为空时,就跳出死循环;不为空时,就通过代码msg.target.dispatchMessage(msg);
去分配消息。那么在next()
中是如何不断的获取消息的,看代码:
Message next() {
......
for (;;) {
......
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
......
}
......
}
}
这个方法中也是使用for
生成了一个死循环,用于获取MessageQueue
队列中的消息,消息获取成功后退出死循环,等待下次的调用。这样就完成了循环取消息的过程。
消息获取到之后,使用代码msg.target.dispatchMessage(msg);
来进行消息分配处理,看具体代码:
1 public void dispatchMessage(Message msg) {
2 if (msg.callback != null) {
3 handleCallback(msg);
4 } else {
5 if (mCallback != null) {
6 if (mCallback.handleMessage(msg)) {
7 return;
8 }
9 }
10 handleMessage(msg);
11 }
12 }
- 当
msg.callback
不为空时,调用的是静态方法handleCallback(msg)
; - 当
msg.callback
为空时且mCallback
不为空时,调用的是Callback
接口中的handleMessage(msg)
; - 当
msg.callback
为空时且mCallback
为空时,调用Handler
中的自己的handleMessage(msg)
;
我们通过上面的分析,可以得到这样的结论:
-
线程默认是没有Looper的,如果需要使用Handler,就必须先创建Looper。
-
ActivityThread被初始化的是时候就创建了Looper,并运行了loop()方法,这也就是UI线程中能直接使用Handler的原因。
-
Handler创建的时候会采用当前线程的Looper来构造消息循环系统,Looper在哪个线程创建,就跟哪个线程绑定,并且Handler是在他关联的Looper对应的线程中处理消息的。
我们调用post
方法来实现更新UI的操作,代码如下:
postHandler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
//在这里进行耗时操作...
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
postHandler.post(new Runnable() {
@Override
public void run() {//在这里进行UI更新
Log.e("LHC", "Handler Post...");
}
});
}
});
在这里需要注意的是:post
方法传入的是Runnable
,看着是新建了一个子线程,那么在子线程中怎么能进行UI更新呢?首先,我们要明确一点,一个线程的启动需要调用start()
方法,但是这里并没有调用。在JAVA
中,直接调用run()
方法相当于调用了一个普通方法,当然了还是当前线程执行。我们来看下post
里面代码的具体实现:
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
我们可以看到在post
方法之中,将参数r
传递给sendMessageDelayed
方法,然后在方法getPostMessage(r)
中,将r
赋值给了m.callback
并封装成了Message
返回。而sendMessageDelayed
方法以后的处理流程在上面已经讲过了,这里就不在重复了。
我们在来回过头来看看上面提到的方法dispatchMessage(msg)
,代码中当msg.callback不为空时,调用的是静态方法handleCallback(msg)
。那么这个静态方法中执行的是什么呢?看代码:
private static void handleCallback(Message message) {
message.callback.run();
}
这里调用的是Message
中callback
下的run()
方法,而这个callback
就是我们在调用post(Runnable r)
时的传入的参数r
。
所以post
方法中就不存在新建线程,方法post
运行在调用它的Handler
所在的线程中,而这个Handler
运行在主线程中,所以在主线程中更新UI肯定没有问题了。
那么系统为什么不允许在子线程访问UI呢?摘自<Android 开发艺术与探索>
这是因为Android的UI线程不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。
那么系统为什么不对UI控件加上锁机制呢?摘自<Android 开发艺术与探索>
缺点有两个:首先,加上锁机制会让UI访问的逻辑变的复杂;其次,锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
在看下面的代码:
Handler h = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.e("LHC", "Handler.Callback Msg:"+ msg.what);
return true;
}
});
new Thread(new Runnable() {
@Override
public void run() {
h.sendEmptyMessage(1);
}
}).start();
这里使用的是构造方法Handler(Callback callback)
进行生成Handler
,具体实现代码:
public interface Callback {
public boolean handleMessage(Message msg);
}
public Handler(Callback callback, boolean async) {
......
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;
}
将传入的Callback
接口对象参数callback
赋值给了Handler
中的成员变量mCallback
。在回过头来看处理消息方法dispatchMessage(msg)
,代码中当msg.callback为空时且mCallback不为空时,调用的是Callback接口中的handleMessage(msg)
。
到这里就将分发消息方法中的情况都介绍清楚了。
为什么Looper.loop()死循环不会导致APP ANR?
ActivityThread的main方法主要作用就是做消息循环,一旦退出消息循环,主线程运行完毕,那么你的应用也就退出了。
Android是事件驱动的,在Loop.loop()中不断接收事件、处理事件,而Activity的生命周期都依靠于主线程的Loop.loop()来调度,所以可想而知它的存活周期和Activity也是一致的
。当没有事件需要处理时,主线程就会阻塞;当子线程往消息队列发送消息,并且往管道文件写数据时,主线程就被唤醒。
真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。
上面我们基本讲解了Handler
的流程,下面我们整体上来总结一下:
Handler
机制主要涉及了这四个类:Handler
,Lopper
,Message
,MessageQueue
。
-
新建Handler,通过Handler的post或者sendMessage方法发送消息,Handler通过sendMessageDelayed方法,将Message交给MessageQueue.
-
MessageQueue通过调用enqueueMessage(msg, uptimeMillis)方法,将Message以链表的方式加入队列当中.
-
Looper类中的loop()方法循环调用MessageQueue.next()方法获取消息,并调用Handler的dispatchMessage(msg)方法处理消息.
-
在dispatchMessage(msg)方法中,根据判断msg.callback,mCallback是否为空来执行相对应的回调,如果都为空就执行Handler的自身handlerMessage(msg)回调.
在使用Handler
过程当中,我们还会遇到这种情况,看代码:
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//TODO 自己的代码逻辑
}
};
这次这样写的时候,代码new Handler()
中的Handler
报警告The following Handler class should be static or leaks might occur
,警告的意思也就是说Handler类应当是静态的,否则可能会造成内存泄露。
那么为什么会出现这个警告呢?Handler
会在一个后台线程中处理耗时操作,处理完成后将结果发送给UI线程进行保存或者显示。那么在后台线程执行的过程中,如果Activity关闭了,在正常情况下,Activity会在GC检查时被回收掉,但是由于后台线程还在执行,它持有Handler的引用,而Handler持有Activity的引用,这样造成Activity不会被回收掉,直到后台线程执行完成。这样就造成了内存泄露
。那么我们如何解决呢?看下面代码:
private static class MyHandler extends Handler{
private WeakReference<Activity> weakReference;
MyHandler(Activity activity){
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = weakReference.get();
if (activity == null || activity.isFinishing()){
return;
}
//TODO msg消息的处理
......
}
}
@Override
protected void onDestroy() {
super.onDestroy();
myHandler.removeCallbacksAndMessages(null);//Activity退出时,将还未执行的消息处理掉。免得引发异常
}
通过上面的这种写法,就解决了内存泄露的问题。