Android Handler机制详解

Android Handler机制详解

消息循环

要理解Handler机制就必须先理解什么是消息循环。初学编程一般从C语言开始,C语言程序从main函数开始,执行main函数的第一行代码开始,到main的最后一行代码结束,这时候程序就运行结束了,是一个线性的执行过程。我们从以前的算法,或者数据结构方面的小程序都是通过一个main函数来驱动测试的。这时候的程序比较简单,一般没有消息循环的概念,大部分都是程序运行完了就结束了,程序运行的结果显示在屏幕上。

我们后来学习了图形用户界面,VB,DELPHI或者JAVA Swing,在图形用户界面下,程序启动后,会弹出一个与用户交互的界面,用户不对界面作操作,程序不会作响应而且永远不会退出。我们都知道程序如果是从main开始,一定会执行到main函数的最后一行代码的。但是那是什么让程序可以等待用户操作,而不执行不到main函数的最后一行代码呢?

我们或许能够隐隐约约的感觉到程序执行到某一个地方启动了一个循环,一直在等待用户操作,用户不作操作则程序一直处于循环内部等待用户发送消息,并在循环内部循环处理用户发送的消息,这就是一个典型的消息循环机制。




消息循环也是一个典型的生产者-消费者模式,生产者不停的向消息队列中生产消息,消费中不停的从消息队列中取消息,并处理消息。生产者-消费者模式是一个很典型的多线程协调的例子,在对发送消息或者取消息的时候需要消息队列做同步处理,而且当消费者向队列取消息的时候,如果没有消息,取消息线程要进入等待状态,并等待被唤醒。当生产者线程想消息队列中发送消息的时候需要唤醒当前正在等待消息队列的线程。

生产者消费者模式最主要的问题就是线程安全和线程协调。

生产者-消费着模式和线程安全

我们可以举生活中的一个例子,桌子上有一个盘子,有一个人专门往盘子里放苹果,有一个人专门从盘子里拿苹果吃,我们称放苹果的人为生产者,我们暂时取代号A,吃苹果的人为消费者,我们取其代号为B。

我们首先看吃苹果例子中的线程安全问题

A往盘子中放苹果,B往盘子中拿苹果,他们各司其责,好像没什么问题,如果A在往盘子中放苹果的过程中,B这时正好也从盘子中拿苹果。如果A正在放苹果的同时,B来拿苹果,这时可能苹果会被掰开,因为可能A的苹果还没放入盘子,B已经从盘子里那苹果了,如果这个场景是程序在运行,就可能出现程序不一致的情况了。
为什么会出现这种情况呢,是因为A和B执行任务的时候出现的了公共资源,实例中的盘子,公共资源的访问没有做任何的控制。这就是所谓的线程安全问题。线程安全问题主要是由于不同任务之间资源共享引起的。比如说例子中的盘子便是两个任务的公共资源。
解决线程安全问题
如何规避这个问题呢?我们可以把盘子变成一个带锁的盒子。盒子的旁边放一个开锁的钥匙。如果A来放苹果,他拿桌子上的钥匙打开盒子,然后带着钥匙进入盒子放苹果。当放好苹果之后再把钥匙放回桌子上。B来拿苹果的时候,首先也要拿桌子上的钥匙打开盒子,如果A正在放苹果,B是拿不到钥匙的,他知道这时候已经有人在用盒子了,所以他在盒子边上等。等钥匙被还回来的时候他才能用钥匙打开盒子,然后在盒子里面拿苹果。这样就有效的解决了上面说的多任务共享资源的问题。一般我们把多个任务中共享资源的那段代码称为临界区。

我们把盘子换成盒子,并且在盒子上加一把带钥匙的锁,成功解决了两个人吃苹果时同时访问盘子的问题,这就是所谓线程安全问题,线程安全问题就是把要使用到公共内存的代码区域称为临界区,所有进入临界区的代码都要进行加锁处理,具体代码级别的例子参看后面章节。但是我们还有个新的问题。

如果B吃苹果的速度比A放苹果的速度要快。这时B拿到钥匙进入盒子的时候,如果发现盒子里没苹果,这时候该怎么办呢?有一点我们是可以肯定,就是B不能在盒子里等,如果B在盒子里等,那么A就永远不能进入盒子放苹果了,那么B也永远吃不到苹果了,因为他拿着盒子的钥匙在盒子里边等别人放苹果。

我们考虑解决办法,B进入盒子之后发现里面没苹果。就出来把钥匙放回桌子,等别人来放苹果。这时候B该做些什么呢?B只能等待,因为B除了吃苹果已经没别的工作了。但是如果B在等待了,又有谁来通知他盒子里已经有苹果了呢?

我们可以在锁上加一个排队器,B进入盒子后,发现里面没苹果,B出来,把钥匙交还然后在锁后边排队等待有人来放苹果。A进入来放如苹果后,通知所有在锁后面排队的人去取苹果。

现在所有的流程就可以连接起来了,A每次来放苹果的时候首先拿盒子边上的钥匙,进入盒子放好苹果后,把钥匙放回原处,并通知所有正在排队的人,现在已经有苹果了。B每次来吃苹果的时候,首先拿到盒子的钥匙,然后进入盒子里拿苹果,拿到苹果后,把钥匙放回盒子边上,如果进盒子拿苹果的时候发现里面有苹果,则出来把钥匙放会盒子边上,并在 钥匙后面排队等候,等待放苹果的人来呼叫。 这种情况甚至可以是有多人同时放苹果和多个人同时吃苹果。这就是一个典型的生产者消费者模式的解决方案。

下面是一个JAVA的生产者¬-消费者的实例:
消息机制也是一个典型的生产者——消费者模式,发送消息的任务我们可以认为是上面放苹果的人A,处理消息的任务可以认为是吃苹果的人B。A每次都向制定的内存地址PUSH一个消息对象,B每次都从指定的内存获取一个消息进行处理。A和B是两个独立运行的线程。

下面开始分析android Handler机制

我们结合android源代码来详细分析android handler的实现原理和机制。Android中与handler机制相关的类有4个:Hanlder、Looper、Message、MessageQueue。

Message(消息对象,上面例子中的苹果)

Message表示handler一次要处理的的消息,Message一般聚合4个属性:what、arg1、arg2、obj。
What:用来标识消息,却分不同的消息参数
Arg1和arg2:附带的2个整型参数
Obj:如果需要附带更多更大的参数就可以放在这里

Message的对象创建非常频繁,为了节省对象的创建时间,这里使用了对象池模式来创建Message的对象。即Message提供了一个obtain()的静态方法来创建Message对象,abtain方法的内部实现的一般原理是:维护了一个固定大小的容器用来存放一些预先创建号的对象,用的时候直接返回一个对象就可以了,如果对象被使用完毕,再放会池子。一般使用recycle方法对不再使用的Message对象放会对象池。

Message对象本省也是一个链表结构。

MessageQueue(消息队列,上面例子中的盒子)

消息队列对象维护了一个线程安全的队列。有两个方法enqueueMessage和next,即向队列存入消息和从队列取出消息的方法。
这个方法可能会在不同的线程中调用,所以就会产生上面讲的吃苹果引起的并发问题(线程安全问题)。这里分析一下代码,看看JAVA是如何具体来实现生产者-消费者模式的。

在分析之前先诠释一个概念即synchronized关键字。在讲这个之前先讲讲锁的概念和条件变量的概念。

在上面吃苹果的的故事中,讲到一个临界区的概念,即一次只能有一个线程可以进入的代码区域,一般该代码区域包含了公共的资源。比如说上面吃苹果和放苹果的操作都是临界区的操作。我们讲解了加锁和加锁排队的实现。用伪代码实现如下:

放苹果:
1.给盒子开锁;(如果有人在吃苹果,是拿不到钥匙的,因为钥匙被人拿着进入盒子了,所以只能等待)
2.拿钥匙进入盒子吃苹果;
3.通知所有等待吃苹果的人可以从新取拿苹果了
4.出来把钥匙放回盒子旁边;

吃苹果:
1.给盒子开锁;(如果有人在放苹果,是拿不到钥匙的,因为钥匙别人拿了)
2.拿钥匙进入盒子吃苹果;
3.如果发现盒子里没苹果;
4.出来把钥匙放会盒子旁边,并在钥匙旁边排队,等待有人进入盒子放苹果。

JAVA代码的实现:

放苹果:
Try{
mLock.lock();
放苹果;
mLock.notifyAll();
} finally {
mLock.unlock();
}
吃苹果:
Try{
mLock.lock();
if(没苹果) {
mLock.wait();
}
吃苹果;
} finally {
mLock.unLock();
}

这就是一个典型的JAVA伪代码实现了吃苹果的场景。但是这和synchronized有什么关系呢?JAVA的所有类内部都有一个锁对象,如果一个方法被synchronized修饰,则可以认为代码如下:
Public synchronized void method() {
方法的实现;
}
等价与:
Public void method() {
Try {
对象内部锁.lock();

方法的实现;
} finally {
对象内部锁.unLock();
}
}
对象内部锁也有一个内部的条件变量,可以直接来调用对象的wait方法和notifyAll方法来使线程进入等待状态和唤醒所有等待的线程。

有了上面的分析,对MessageQueue的方法实现就很容易理解了。我们把放苹果的操作当作是向消息队列放消息,把吃苹果的操作当作消息处理。

下面贴出源代码:
放苹果,即向消息队列中放消息:

public boolean enqueueMessage(Message msg, long when) {
msg.when = when;
// 临界区
synchronized (this) {
Message p = mMessages;
printMessageQueue(p);

// 把消息插入消息队列中,并通知等待线程
if ( p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
while (p.next != null && p.when <= when) {
p = p.next;
}
msg.next = p.next;
p.next = msg;
this.notify();
}
}
return true;
}

吃苹果:

public Message next() {
while (true) {
long now = 0;
synchronized (this) {
now = SystemClock.uptimeMillis();

Message msg = pullNextLocked(now);
           if (msg != null) {
           return msg;
          }
}

// 临界区
synchronized (this) {
try {
if (mMessages != null) {
if (mMessages.when > now) {
this.wait(mMessages.when - now);
}


} else {
// 如果没有消息则使线程进入等待状态
this.wait();
}
} catch (InterruptedException e) {
// ignore
}
}
}
}

到这里为止就知道JAVA是如何实现生产者消费者模式了,而且如果在具体的代码中来应用。

Handler(在Android的消息机制里,放苹果和吃苹果都是同一人)

消息处理器和消息发送器
Handler即是消息的发送者,也是消息的处理者,Handler必须在消息循环线程中被创建,在创建的时候会拿到当前线程的Looper对象,这样就可以同时拿到MessageQueue对象。
public Handler() {
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
mCallback = null;
}

Handler发送消息即把Message放入其在创建时的MessageQueue对象。代码如下:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
boolean send = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
send = queue.enqueueMessage(msg, uptimeMillis);
}
return send;
}

Message在发送的时候注意上面标黄部分的代码,即个handler自身给了Message的target属性,即告诉Message由哪个handler来处理该消息。如何被handler调用见下面的Looper对象的实现。

Looper只管调用handler处理message的方法,具体如果来处理消息还是要handler自己来实现。Looper对象在loop()方法中会调用message.target.dispatchMessage(currentMsg)方法,即调用了Message对应的handler的dipatchMessage方法,具体的实现代码如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
handleMessage(msg);
}
}

Handler的handleMessage(Message msg)方法,我想就是大家很熟悉的代码了,我们一般在创建handler的时候一般都会调用该方法。


Looper

消息循环

这个类才是整个消息循环的主控制类,一般要想使得一个线程变成消息循环线程的代码是这样的:

Looper.prepare();
Looper.loop();

Prepare()方法创建一个线程私有(即ThreadLocal变量)的Looper对象,Looper内部聚合了一个MessageQueue,代码如下:
private Looper() {
mQueue = new MessageQueue();
}

public static void prepare() {
sThreadLocal.set(new Looper());
}
Loop()方法使得线程进入消息循环,并永远不退出,具体的实现原理也很简单,也就是不停的检测消息队列是否为空,如果不为空则处理消息,如果消息队列为空则线程睡眠,如果有消息本放入消息队列,则唤醒线程。

具体的实现代码如下:
public static void loop() {
Looper currentLooper = myLooper();
MessageQueue queue = currentLooper.mQueue;

while (true) {
// 从消息队列中取出一个消息
Message currentMsg = queue.next();

// 处理消息
if (currentMsg != null) {
if (currentMsg.target == null) {
return ;
}

//调用Message所对应的Handler对象分发消息
currentMsg.target.dispatchMessage(currentMsg);
currentMsg.recycle();
}
}
}

Loop方法首先拿到消息队列,然后进入一个死循环,代码不断的从消息队列中取出消息,然后处理消息。

猜你喜欢

转载自xiaofanqingzjj.iteye.com/blog/1764675