看本篇文章的小伙伴们,先声明一点哈,原理为主,较少涉及用法
最近在面试,发现很多面试官都会让说一下Handler的原理。Handler你百度的话会发现有特别多的文章来讲这个东西,比我讲的详细的有太多太多。我也想写一篇文章研究下里面较为浅显的道道,加深下自己的印象,防止以后忘记了的话有熟悉的东西可看。
Handler在项目中经常会用到,关于它相信不会很陌生,不过依照惯例还是来介绍下它吧:
- Handler是什么?
它是安卓给我们提供用来更新UI的一套机制,也是一套消息处理机制,我们可以通过它发送消息,也可以通过它处理消息 - 为什么要是用Handler?
其实这个问题也可以换种问法: - 为什么要通过Handler机制更新UI?
最根本的原因是解决多线程并发的问题。
假如在一个Activity中,有多个线程去更新UI,会发生什么?肯定会发生UI界面错乱。好!接着按我们正常的思路来解决这个问题,我们给每个线程都加个锁,使得在同一时间,只有一个线程来更新UI界面,问题不就解决了吗?OK!这个思路是没有错的,也完全可以这么实现它,但是有个问题你考虑过没,你每个线程都加上锁,每次更新UI的操作都加锁处理是不是又会导致Android性能的下降?
所以,针对以上问题,Android为我们提供一套更新UI的机制,我们只要遵循这样的机制就可以了,不用再考虑多线程的问题。 - Hanlder怎么使用?
这个Handler较多的用法有如下几种:
//Handler.post(Runnable)用法
private Handler mHandler = new Handler();
···
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
mHandler.post(new Runnable(){
public void run(){
//UI操作TODO
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//Handler.postDelayed(Runnable, time)用法
private Handler mHandler = new Handler();
private MyRunnable runnable = new MyRunnable();
class MyRunnable implements Runnable {
public void run(){
//各种操作 TODO
mHandler.postDelayed(runnable, 1000);
}
}
//Handler.sendMessage(Message)用法
private Handler mHandler = new Handler(){
public void handleMessage(Message msg){
//UI操作TODO
}
};
····
new Thread(){
public void run(){
//两种方法创建Message都可以,推荐第二种
//Message msg = new Message();
Message msg = mHandler.obtainMessage();
msg.arg1 = 88;
msg.arg1 = 100;
msg.obj = mBeanOne;
//两种方法发送msg都可以
//mHandler.sendMessage(msg);
msg.sendToTarget();
}
}.start();
好,到这里,我们开始研究下它的源码。
obtainMessage()源码分析
最后一种方法中,我们看到Message的创建方法有两种,一种是最常规的(我也是经常这么用):
Message msg = new Message();
但是可以看到还有另一种方法去创建它:
Message msg = mHandler.obtainMessage();
我们点开Handler的obtainMessage()方法去追源码:
Handler类
/**
* Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
* creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
* If you don't want that facility, just call Message.obtain() instead.
*/
public final Message obtainMessage()
{
return Message.obtain(this);
}
ok!点开obtain(this)方法继续追!
注意一下这个方法的参数this,意思是把Handler对象传进去了。
Handler类
/**
* Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
* @param h Handler to assign to the returned Message object's <em>target</em> member.
* @return A Message object from the global pool.
*/
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
好,到这里为止,我们终于看到Message的赋值操作了,通过obtain()方法返回了一个Message,并把上面传进来的Handler参数赋值给了message的target。
这里插一句嘴,target其实就是handler自己,标识一个发送地址。因为发送message你必须得有一个地址吧,说明要发送到哪里,这个target你就可以把它理解为一个地址,即,把消息发送给handler自己。
OK,继续往下追obtain():
Message类
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
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();
}
sPool就是Message类里面的私有静态变量:
public final class Message implements Parcelable {
···
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
···
}
好了,现在就能看出来,obtain()的作用就是new出一个Message对象,不过它在new之前会检查Message里面有没有Message对象sPool,有的话就不用创建了。
sendToTarget()源码分析
最后一种方法中,我们看到Message的发送方法也有两种,一种是常规的:
mHandler.sendMessage(msg);
还有一种:
msg.sendToTarget();
往下追sendToTarget()吧:
/**
* Sends this Message to the Handler specified by {@link #getTarget}.
* Throws a null pointer exception if this field has not been set.
*/
public void sendToTarget() {
target.sendMessage(this);
}
不用往下追特别多,一层你就知道是啥意思了,还记得上面我们说的
“target其实就是handler自己,标识一个发送地址”吧,那不就是在调用
mHandler.sendMessage(msg);嘛,我去!
上面的方法出现的需要注意下的地方差不多就这俩了。
现在看看Handler真正核心的东西:
Handler Looper MessageQueue 三者的关系
- Handler
Handler是封装消息的发送(主要包括消息发送给谁)
Handler发送的消息就是上面的Message对象,可以看到,Message包含int型的arg1、arg2,一个Object的obj,还有一个Handler自身的target。一般对我们的日常开发来说,设置一下arg标识把自己的实体类赋值给obj,send发送一下就可以了,基本上都用熟了。 - Looper
Looper是消息封装的载体。
Looper有很多东西,我们说的多一点哈
Looper内部包含一个消息队列也就是MessageQueue(消息队列,它可以添加消息,也可以处理消息),所有的Handler发送的消息都走向这个消息队列MessageQueue。Looper里面有一个最关键的loop()方法,就是一个死循环,不断的从MessageQueue取消息,如有消息就处理消息(就是把Message发送给指定的target地址),没有消息就阻塞,它的源码如下:
Looper类
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the 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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
我把有用的代码抽取一部分,变成易懂的伪代码如下:
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next();
if (msg == null) {
return;
}
try {
msg.target.dispatchMessage(msg);
} finally {
}
}
}
这下子就简洁多了,咱们来细看下它的源码:
首先是通过myLooper()方法得到Looper对象,这里需要知道的是:这个Looper对象是UI线程也就是主线程里面的Looper,通过追myLooper()方法就能知道:
Looper类
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
这里留个谜,这个方法后面讲Handler关联Looper时会详细讲到。
继续看上面的Looper.loop(),拿到了Looper对象,也就拿到了Looper里面的MessageQueue消息队列:MessageQueue queue = me.mQueue;
接下来就是一个未指定条件终止语句的for循环了,就是无限循环。
它通过MessageQueue的next方法遍历消息队列,有的话就取出来:
msg.target.dispatchMessage(msg);
上面我们已经说了,Message的target就是Handler自己,所以,这个方法其实就是调用:
mHandler.dispatchMessage(msg);
OK,点进去看看吧:
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里面的方法。
在讲这里的源码逻辑之前,先说下三个东西
①Message的callback
Message类
/*package*/ Runnable callback;
public
interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
可以看到这个callback其实就是个Runable接口,它里面就是个待实现的run()方法
②Handler的mCallBack
Handler类
final Callback mCallback;
/**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public interface Callback {
public boolean handleMessage(Message msg);
}
一样!Handler也是个接口,它里面也有一个handleMessage待实现接口。
③handleMessage(msg);
Handler类
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
我去,这三兄弟一样的呀,都是实现方法去处理这个Message参数
OK,看到这里,dispatchMessage(msg)的源码逻辑就好懂了:
首先会判断Message自己携带的CallBack回调方法是不是null,如果不是null的话直接调用handleCallback(msg);
private static void handleCallback(Message message) {
message.callback.run();
}
如果是null的话就去判断Handler里面的CallBack对象mCallback,
不为null直接调mCallback.handleMessage(msg);
如果是null就调handleMessage(msg);
看到这里,是不是想到了点什么,当时我看到这里的时候,我想到了sendMessage的处理方式了,我在这里举个例子:
Message msg = mHandler.obtainMessage();
msg.arg1 = 88;
msg.arg1 = 100;
msg.obj = mPerson;
mHandler.sendMessage(msg);
//接受Message并处理:
private Handler mHandler = new Handler(){
public void handleMessage(Message msg){
//拿到了mPserson对象,随你处置
}
};
上面的代码不就是走的dispatchMessage(msg);的第三种逻辑,重写handleMessage方法嘛!
其实,从上面的代码中我们或多或少的能多出来点东西(Handler发消息,处理的过程却是在Looper里面):在Handler中发送消息,其实就是向Looper发送消息,具体点说就是向Looper里面的MessageQueue队列中发送消息
接下来,我们看看Handler怎么找到Looper给它发送消息的,也就是说,也Handler的内部怎么和Looper进行关联的?
我在这里简单的叙述下过程哈,在叙述的过程中贴上一下源码。
首先,我们先来看看咱们最最最常用的UI线程,每天都和它打交道:
Main线程(所有应用程序更新UI的线程:ActivityThread),在创建这个Main线程的过程中,Main线程中就会创建一个Looper,而创建Looper的过程中,会创建一个MessageQueue。所有的Hanlder进行关联取出的消息队列都是通过默认的UI线程的Looper当中进行关联的:
我们找到这个ActivityThread类,它里面有个main()方法
ActivityThread类
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我把它简化一下,只留便于理解的一些东西做成伪代码:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
....
Looper.loop();
....
}
我去,就两个东西呀!
其实这两个东西对我们理解Handler关联主线程的Looper已经足够了!
追一下prepareMainLooper()方法:
Looper类
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
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);
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对象的方法,咱们先略过这个方法,也留个谜(记录一下,留了两个谜了),后面再看它的源码,这里只需要知道在主线程里面创建Looper,你不得先判断了一下人家之前有没有Looper对象吗,有就复用,没有才给人家创建呗,就这逻辑。
OK,sThreadLocal.set(new Looper(quitAllowed));这里就是创建Looper对象了,看到了没,set参数里面直接调用new Looper(quitAllowed),奥,原来在这里创建的!
Looper类
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
是不是,和我们上面说的一模一样,创建Looper的时候就会创建一个MessageQueue,赋值给Looper类的mQueue变量,MessageQueue确实在Looper里面,嘿嘿。至于参数quitAllowed,它默认为true,从名字可以看出来就是消息循环是否可以退出,默认是可退出的。
然后接着看:
sThreadLocal.set(new Looper(quitAllowed));
主线程把刚创建的Looper对象给sThreadLocal.set()了:
ThreadLocal类
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
可以看到,这是个存储操作,把主线程创建的这个Looper存储到ThreadLocalMap里面了,key是当前的主线程,value就是Looper。
好了,这时候我们可以看sThreadLocal.get()这个方法了:
几乎可以不用看它就知道是从ThreadLocalMap里面通过主线程这个key来拿主线程的Looper对象:
ThreadLocal类
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
哈哈,看到没,还真是!
②其次sMainLooper = myLooper();:
sMainLooper = myLooper();
哈哈,这个myLooper();就是我上面留谜那个还没说的方法,这次让我们好好看看它:
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
没啥东西,接着就是sThreadLocal.get();
不用追了,就是上面的那个,从ThreadLocalMap里面通过主线程这个key来拿主线程的Looper对象,所以,sMainLooper = myLooper();就是把主线程的Looper对象赋值给sMainLooper变量。
OK,主线程里面的Looper我们看完了,我们再来看看Handler里面的一些东西,这个就很简单了。直接说一下吧:
首先看Handler的构造方法:
Handler类
/**
* Use the {@link Looper} for the current thread with the specified callback interface
* and set whether the handler should be asynchronous.
*
* Handlers are synchronous by default unless this constructor is used to make
* one that is strictly asynchronous.
*
* Asynchronous messages represent interrupts or events that do not require global ordering
* with respect to synchronous messages. Asynchronous messages are not subject to
* the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
*
* @param callback The callback interface in which to handle messages, or null.
* @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
* each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
*
* @hide
*/
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;
}
嘿嘿,简化一下:
public Handler(Callback callback, boolean async) {
....
mLooper = Looper.myLooper();
....
mQueue = mLooper.mQueue;
....
}
看到了吗,又是上面的myLooper方法,拿到了主线程的Looper对象,赋给了Handler的mLooper,然后调用mLooper.mQueue,又把主线程Looper里面的MessageQueue赋值给了Handler的mQueue。至此,Handler就已经和主线程的Looper关联起来了,通过send等方法给主线程Looper里面MessageQueue发消息,然后主线程Looper通过loop()方法取出MessageQueue里面的消息,通过你重写的各种回调方法callback来执行结果操作。
补充一下知识点
走到这里,Handler的原理基本差不多了
但是这里还要补充的两点是:
- Android是怎么样唤醒处于阻塞状态下的Looper的?
(被问到了这个东西。这缺德了,直接涉及到Linux内核层了,关键是它使用的是C语言,费劲吧啦的大学里的C知识全忘光光,看了资料也没大研究明白)
先总结下吧:
Looper中的睡眠和唤醒机制是通过pollOnce和wake函数提供的,它们又是利用操作系统(Linux内核)的epoll机制来完成的。
通过借助于Looper的wake和pollOnce函数,可以让别的消息队列(如Java层的消息队列)拥有睡眠唤醒机制:没有消息时pollOnce调用者将睡眠等待,有消息时让wake函数去唤醒睡眠等待。
大致原理就是:
主线程的MessageQueue没有消息时,阻塞!在哪里阻塞的?在loop()方法里面的queue.next()里面的nativePollOnce()方法里!这时候ActivityThread(主线程)会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,唤醒!怎么唤醒?通过往Pipe(Linux管道)写入数据来唤醒主线程工作。这就是Epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作。总的来说,就是和这个管道玩,使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。
我在知乎上看到过一个老兄对这题的解答,他说:
这一题是需要从消息循环、消息发送和消息处理三个部分理解Android应用程序的消息处理机制了,这里我对一些要点作一个总结:
A. Android应用程序的消息处理机制由消息循环、消息发送和消息处理三个部分组成的。
B. Android应用程序的主线程在进入消息循环过程前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。
C. Android应用程序的主线程进入空闲等待状态的方式实际上就是在管道的读端等待管道中有新的内容可读,具体来说就是是通过Linux系统的Epoll机制中的epoll_wait函数进行的。
D. 当往Android应用程序的消息队列中加入新的消息时,会同时往管道中的写端写入内容,通过这种方式就可以唤醒正在等待消息到来的应用程序主线程。
E. 当应用程序主线程在进入空闲等待前,会认为当前线程处理空闲状态,于是就会调用那些已经注册了的IdleHandler接口,使得应用程序有机会在空闲的时候处理一些事情。
打算抽点时间研究一下C,再来继续鼓捣这块东西,有兴趣的童鞋们可以去看看:
Looper中的睡眠等待与唤醒机制
- Looper.loop()他是一个死循环,没有消息时会处于阻塞状态,那为什么主线程不会因为loop()方法崩溃呢?
(也被问到了这个东西,这更缺德了,挖坑给你跳:
“你看Looper是在主线程中被定义的,像你说刚才说的,它的loop方法是一个死循环,没有消息的时候会阻塞,loop在主线程中进行阻塞,为什么不会崩溃呢?”
“.....我不说了还不行嘛,我收回刚说的话,looper阻不阻塞我不知道呀”)
其实这个问题,和上面的问题是相互关联的,并且还涉及到一个伪命题:主线程不能阻塞!
主线程是随着APP的启动而启动,随着APP的结束而结束,APP要一直运行直到用户退出,那么主线程就必然不能代码运行完毕而终止,所以需要进行阻塞,直到用户退出了APP,才能停止阻塞,让CPU执行完剩下的代码,而后代码执行完毕,主线程从而寿终正寝。你试想一下,如果主线程不是阻塞的,一条路执行到底,over!那我后续的触屏手势,键盘输出App要怎么响应?所以说主线程阻塞是一个伪命题。
OK,那既然主线程阻塞是正常的,阻塞状态下是怎么更新UI界面,响应生命周期的?你看,这就是我们上面的问题!
以上
网友评论