Handler的介绍
Handler多用于主线程与子线程之间的通信,在Android中子线程是不允许修改UI的,那么Android是如何进行子线程的加载数据后,在主线程更新界面呢?这里就使用到了handler,可以通过handler发送message(消息)的方式进行界面更新。handler整个机制的实现,还依赖于Looper(轮询器)及MessageQueue(消息队列),Looper的作用主要是不断的往消息队列中取出消息,MessageQueue用于存放handler发送的消息。
handler机制中四个重要的类型
Message(消息)
Handler
Loope(轮询器)
MessageQueue(消息队列)
消息的创建
1.Message msg = new Message(); //可以通过handler.sendMessage 方式发送消息
2.Message msg = Message.obtain(); 、、可以通过handler.sendMessage 方式发送消息
3.Message msg = handler.obtainMessage();
1.Message msg = new Message(); //可以通过handler.sendMessage 方式发送消息
2.Message msg = Message.obtain(); 、、可以通过handler.sendMessage 方式发送消息
3.Message msg = handler.obtainMessage();
发第三种方式或取消消息的方式比较特殊,通过hanler进行获取消息,说明该消息已经和handler进行绑定,如果是这种方式发送消息,可以通过msg.sendToTarget()方法进行消息的发送,因为它里面已经有handler了。
/*package*/ Message next;
private static Message sPool;
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();
}
Message中的Obtain方法,我们可以通过Message的静态方法Obtain获取消息,这里的sPool从字面上理解,是一个消息池,它实现的是一个单链表的形式,发送的很多消息,都会有一个先后顺序,因此它还有一个next属性。当消息为消息池为null时,它是创建了一个消息进行返回,不为null时,从消息池中获取了当前的消息进行返回。 Message m = sPool;
sPool用m这个临时变量记录 当前消息。
sPool = m.next;
sPool指向了当前消息的下一个消息,(数据链表。。)
m.next = null;
截断数据链表,这是的m 就只是第一个消息了。然后进行了返回了这个 m 。因此懂链表数据结构,就很容易看的懂这几行代码,(作者也是刚学习 记录一下)
Message这个类中还有很多重载的obtain方法
public static Message obtain(Handler h, int what) { //贴出其中的一个
Message m = obtain();
m.target = h;
m.what = what;
return m;
}
实现都是调用obtain上面的obtain方法获取一个消息,然后对传递进行的handler和消息进行了一个绑定,避免了可能同时很多个handler都在发送消息,轮询器取消息,却不知道是哪个handler发送的,当然在Looper的中也进行了handler的绑定,这些handler,都是同一个handler,形成了一个循环,始终作用于一个handler。
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;++; } } }
消息的回收方法,其实就是把消息的所以属性置为初始属性,
next = sPool;
当一个消息使用完了,sPool始终都是消息中的第一个消息,它的后面可能还有很多的消息,这里就是让当前消息的下一个消息给了sPool。
sPool = this;
sPool又等于了当前的消息,那么也可以说明,sPool前面的那个消息被抹杀掉了。
public void sendToTarget() {
target.sendMessage(this);
}
<span style="font-size:14px;">这个方法就可以解释msg.sendToTarget();方法为什么可以发送消息了,</span><pre name="code" class="html"><span style="font-size:14px;">它的本质其实还是通过handler发送消息。</span>
Message的介绍大致就那么多,更多的方法可以去了解下Message的源码。
Handler的初始化流程
public Handler(Callback callback, boolean async) { //部分重要源码
//获取了Looper轮询器
mLooper = Looper.myLooper();
//轮询器中获取消息队列
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
在handler的初始化过程中,获取了一个Looper轮询器,在从轮询器中获取消息队列,handler发送的消息都是发送到了消息队列中。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
通过THreadLocal.get()获取
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
它的作用就是保证每一个线程只初始化一个Looper轮询器。
它是在myLooper方法中进行get()获取Looper,那么又是在哪里进行了set调用呢?
其实在程序运行的时候,就已经初始化好了Looer轮询器,我们使用者只用创建handler,就可以进行消息的发送了,平时我们所接触的就是handler,和message,Looper和Messag都是系统帮我们初始化好的。
是在ActivityThread类中,它是程序的主入口,在main方法调用的
Looper.prepareMainLooper(); //ActivityThread main方法里 被调用
Looper.loop();
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()方法
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.set()方法,创建了一Looper对象,Looper的构造方法中初始化了消息队列MessageQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
这个过程是系统帮我们初始化好的。
因此当创建好handler后,在获取到Looper轮询器,从Looper轮询器中获取MessageQueue消息队列,消息队列是从Looper中获取给handler,可以说这个消息队列是handler和Looper共享的一个容器,handler是子线程发送,Looper是主线程执行的,而子线程和主线程就是靠这个消息独立来进行数据共享的。
Looper初始化完毕后,main方法里面还调用了一个loop()方法
Looper.loop(); // 开始轮询取消息的方法
public static void loop() { //loop()方法的重要实现
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
}
在loop方法中通过myLoop();获取当前的Looper对象,在从Looper中获取消息队列,这里的消息队列和handler中获取的消息队列是同一个消息队列,然后开启了一个for的死循环,重要的方法来了,那就是消息对了中的queue.next()方法,这里的注释了说了 会阻塞, 因此,当没有消息处理时,会阻塞,它是通过这个next方法来完成阻塞的, next()所做的工作就是从中获取一个消息,如果要是没有消息,会进行阻塞.
nativePollOnce(ptr, nextPollTimeoutMillis); //中断的方法
<pre name="code" class="java"> private native void nativePollOnce(long ptr, int timeoutMillis); //进入阻塞的方法
<pre name="code" class="java"> private native static void nativeWake(long ptr); //被唤醒的方法
都是native方法,说明这个获取消息,如果没有消息进入阻塞,有了消息则会被唤醒的实现是通过jni调用底层的实现的,是一个linux的进程间通信,管道通信.这个管道通信,这个管道其实就是一个特殊的文件,该文件有两个描述符,一个是读的描述符,一个是写的描述符,说白了就是文件的引用.
主线程拿着读的描述符等待读取数据,子线程拿着写的描述符开始写数据,写完之后通知拿着读的描述符的主线程,然后主线程被唤醒了,开始轮询取消息。处理消息,但是处理完消息后,接下去没有消息了,那么它又会进入阻塞。针对handler机制的原理,就是handler发送message,唤醒Looper,然后开始轮询遍历messageQueue,获取消息,处理消息。
Handler的发送消息流程
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
<pre name="code" class="java"> public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
几个发送消息的方法,最后都是调用了sendMessageDelayed,没有延迟的消息,默认的时间是0,但是delayMillis如果是小于0的时间 这里同时做的判断,避免时间是负数的情况的发生。
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);
}
发消息的过程又点漫长,调用了一个一个方法,重点是最后把消息对象放入到了消息队列中去,进行了顺序排序,它的排序方式是根据时间的先后顺序进行排序的,这里也把延迟的时间传递了过去,调用sendMessage时,默认就是0,否则是延迟的时间,时间先的消息先执行,延迟的等待时间延迟结束执行。
boolean enqueueMessage(Message msg, long when) {
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;
}
msg.when = when;
绑定消息的时间
Message p = mMessages;
mMessage是一个成语变量,这个的作用就类似Message类中 Message next的一个单链表的数据结构,消息队列也是一个单链表,因此在消息队列中有这么一个属性,通过p临时变量进行记录.
boolean needWake;
声明了一个boolean,这个的作用是,判断是否唤醒Looper进行消息处理,可以看到下面调用了
if (needWake) { //这里就是唤醒的方法了。
nativeWake(mPtr);
}
如果p == null,说明了并没有消息,而这个消息了成了一个消息头,或者 when == 0 ,消息并没有延迟,说明就算之前有消息,那么也处理完毕了,这个新来的消息成为了消息头,在或者 when < p.when, 当前消息的延迟时间,小于了p消息的延迟时间,说明比当前这个延迟的时间早,那么时间先结束的消息会先进行处理,因此这个消息成了第一个消息,也就成为了消息头。else 里,说明可能已经有很多消息了,又新来了一个消息,那么就需要将这个消息放入到消息队列中去,这里又声明了一个变量 Message prev,
<pre name="code" class="java"> prev = p;
进入一个for的无限循环,prev 指向了p,(p 是当前的消息,第一个消息)
p = p.next;
p 指向了它的下一个消息了,(它的下一个位置的消息)
if (p == null || when < p.when) {
break;
}
如果p == null 也就是 当前消息的下一个消息如果是 null,就退出for循环,或者是 当前消息的延迟时间,小于了 这个下一个消息的延迟时间,退出for循环,
msg.next = p; // invariant: p == prev.next
<pre name="code" class="java"> prev.next = msg;
当满足其中一个条件中,说明就找到了新来消息的一个位置,然后进行消息连接,插入到链表中去。进行了一个排序。msg.next指向p,一条链子连了过去,p又连上了之前记录的他的前一个消息,形成了一个链表,(可以自行画一下图,就很好的理解了)下面调用了唤醒方法。
取出消息,之前的next阻塞了消息,现在有了消息,next方法中,
synchronized (this) {
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;
}
mMessage跟Message类中的写法一样,定义个临时变量指向mMessage,最后return的还是个msg,从消息队列里把这个Message消息头返回回去,同过next方法获取到消息后,
for (;;) {
Message msg = queue.next(); // might block
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
然后就是通过handler的dispatchMessage()方法进行处理 消息,最后调用了消息的回收方法,回收当前消息,
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
handlerMessage,就是处理消息的方法了,熟悉的方法,熟悉的味道,这就是整个handler的消息机制了,
第一次写博文,肯定有很多的不足,这次的博文也是作者学习handler进行的学习记录,加深对handler机制的理解。