Android消息处理机制(Handler、Looper、MessageQueue与Message)

Android是消息驱动的,实现消息驱动有几个要素:

1、Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理

2、处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message

msg)方法来对特定的Message进行处理,例如更新UI等。

3、MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。

4、Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。

5、Thread:线程,负责调度整个消息循环,即消息循环的执行场所。获取loop

Looper.loop();

Public staticvoidloop(){

finalLooperme=myLooper();

}

//返回和线程相关的looper

Public staticLoopermyLooper(){

returnsThreadLocal.get();

}

Android系统的消息队列和消息循环都是针对具体线程的,一个线程可以存在(当然也可以不存在)一个消息队列和一个消息循环(Looper),特定线程的消息只能分发给本线程,不能进行跨线程,跨进程通讯。但是创建的工作线程默认是没有消息循环和消息队列的,如果想让该

线程具有消息队列和消息循环,需要在线程中首先调用Looper.prepare()来创建消息队列,然后调用Looper.loop()进入消息循环。 如下例所示:

class LooperThread

extends Thread {

public Handler mHandler;

public void run() {

Looper.prepare();

mHandler = new Handler() {

public void handleMessage(Messagemsg) {

// process incoming messageshere

}

};

Looper.loop();

}

}

这样你的线程就具有了消息处理机制了,在Handler中进行消息处理。

Activity是一个UI线程,运行于主线程中,Android系统在启动的时候会为Activity创建一个消息队列和消息循环(Looper)。详细实现请参考ActivityThread.java文件

Android应用程序进程在启动的时候,会在进程中加载ActivityThread类,并且执行这个类的main函数,应用程序的消息循环过程就是在这个main函数里面实现的

public final class

ActivityThread {

......

public

static final void main(String[] args) {

......

Looper.prepareMainLooper();

......

ActivityThread

thread = new ActivityThread();

thread.attach(false);

......

Looper.loop();

......

thread.detach();

......

}

}

这个函数做了两件事情,一是在主线程中创建了一个ActivityThread实例,二是通过Looper类使主线程进入消息循环中

初始化消息队列

class LooperThread

extends Thread {

public Handler mHandler;

public void run() {

Looper.prepare();

mHandler = new Handler() {

public void handleMessage(Messagemsg) {

// process incoming messageshere

}

};

Looper.loop();

}

}

主要是红色标明的两句,首先调用prepare初始化MessageQueue与Looper,然后调用loop进入消息循环。先看一下Looper.prepare。

public static void

prepare() {

prepare(true);

}

private static void

prepare(boolean quitAllowed) {

if (sThreadLocal.get() != null) {

throw new RuntimeException("Onlyone Looper may be created per thread");

}

sThreadLocal.set(new Looper(quitAllowed));

}

重载函数,quitAllowed默认为true,从名字可以看出来就是消息循环是否可以退出,默认是可退出的,Main线程(UI线程)初始化消息循环时会调用prepareMainLooper,传进去的是false。使用了ThreadLocal,每个线程可以初始化一个Looper。

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);

mThread = Thread.currentThread();

}

MessageQueue(boolean quitAllowed) {

mQuitAllowed = quitAllowed;

mPtr = nativeInit();//mPtr记录native消息队列的信息

}

在Looper初始化时,新建了一个MessageQueue的对象保存了在成员mQueue中。MessageQueue的构造函数是包可见性,所以我们是无法直接使用的,在MessageQueue初始化的时候调用了nativeInit,这是一个Native方法:

android_os_MessageQueue_nativeInit();

static jlong

android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {

NativeMessageQueue*nativeMessageQueue = new NativeMessageQueue();//初始化native消息队列

if (!nativeMessageQueue) {

jniThrowRuntimeException(env,"Unable to allocate native queue");

return 0;

}

nativeMessageQueue->incStrong(env);

returnreinterpret_cast(nativeMessageQueue);

}

在nativeInit中,new了一个Native层的MessageQueue的对象,并将其地址保存在了Java层MessageQueue的成员mPtr中,Android中有好多这样的实现,一个类在Java层与Native层都有实现,通过JNI的GetFieldID与SetIntField把Native层的类的实例地址保存到Java层类的实例的mPtr成员中,比如Parcel。

再看NativeMessageQueue的实现:

new

NativeMessageQueue();

NativeMessageQueue::NativeMessageQueue()

: mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {

mLooper =Looper::getForThread(); //获取TLS中的Looper对象

if (mLooper == NULL) {

mLooper = newLooper(false); //创建native层的Looper

Looper::setForThread(mLooper); //保存native层的Looper到TLS中

}

}

Looper::getForThread(),功能类比于Java层的Looper.myLooper();

Looper::setForThread(mLooper),功能类比于Java层的ThreadLocal.set();

NativeMessageQueue::NativeMessageQueue()

: mInCallback(false), mExceptionObj(NULL) {

mLooper = Looper::getForThread();

if (mLooper == NULL) {

mLooper = new Looper(false);

Looper::setForThread(mLooper);

}

}

在NativeMessageQueue的构造函数中获得了一个Native层的Looper对象,Native层的Looper也使用了线程本地存储

发送消息

通过Looper.prepare初始化好消息队列后就可以调用Looper.loop进入消息循环了,然后我们就可以向消息队列发送消息,消息循环就会取出消息进行处理,在看消息处理之前,先看一下消息是怎么被添加到消息队列的。

在Java层,Message类表示一个消息对象,要发送消息首先就要先获得一个消息对象,Message类的构造函数是public的,但是不建议直接new Message,Message内部保存了一个缓存的消息池,我们可以用obtain从缓存池获得一个消息,Message使用完后系统会调用recycle回收,如果自己new很多Message,每次使用完后系统放入缓存池,会占用很多内存的,如下所示:

public static

Message obtain() {

synchronized (sPoolSync) {

if (sPool != null) {

Message m = sPool;

sPool = m.next;

m.next = null;

sPoolSize--;

return m;

}

}

return new Message();

}

public void recycle() {

clearForRecycle();

synchronized (sPoolSync) {

if (sPoolSize < MAX_POOL_SIZE) {

next = sPool;

sPool = this;

sPoolSize++;

}

}

}

Message内部通过next成员实现了一个链表,这样sPool就了为了一个Messages的缓存链表。

消息对象获取到了怎么发送呢,大家都知道是通过Handler的post、sendMessage等方法,其实这些方法最终都是调用的同一个方法sendMessageAtTime:

public boolean

sendMessageAtTime(Message msg, long uptimeMillis) {

MessageQueue queue = mQueue;

if (queue == null) {

RuntimeException e = newRuntimeException(

this + "sendMessageAtTime() called with no mQueue");

Log.w("Looper",e.getMessage(), e);

return false;

}

return enqueueMessage(queue, msg,uptimeMillis);

}

sendMessageAtTime获取到消息队列然后调用enqueueMessage方法,消息队列mQueue是从与Handler关联的Looper获得的。

private boolean

enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

msg.target = this;

if (mAsynchronous) {

msg.setAsynchronous(true);

}

return queue.enqueueMessage(msg,uptimeMillis);

}

enqueueMessage

接下来看一下是怎么MessageQueue的enqueueMessage。

final boolean

enqueueMessage(Message msg, long when) {

if (msg.isInUse()) {

throw newAndroidRuntimeException(msg + " This message is already in use.");

}

if (msg.target == null) {

throw newAndroidRuntimeException("Message must have a target.");

}

boolean needWake;

synchronized (this) {

if (mQuiting) {

RuntimeException e = newRuntimeException(

msg.target + "sending message to a Handler on a dead thread");

Log.w("MessageQueue",e.getMessage(), e);

return false;

}

msg.when = when;

Message p = mMessages;

if (p == null || when == 0 || when< p.when) {

// New head, wake up the eventqueue if blocked.

msg.next = p;

mMessages = msg;

needWake = mBlocked;

} else {

// Inserted within the middleof the queue.Usually we don't have towake

// up the event queue unlessthere is a barrier at the head of the queue

// and the message is theearliest asynchronous message in the queue.

needWake = mBlocked &&p.target == null && msg.isAsynchronous();

Message prev;

for (;;) {

prev = p;

p = p.next;

if (p == null || when

break;

}

if (needWake &&p.isAsynchronous()) {

needWake = false;

}

}

msg.next = p; // invariant: p== prev.next

prev.next = msg;

}

}

if (needWake) {

nativeWake(mPtr);

}

return true;

}

在enqueueMessage中首先判断,如果当前的消息队列为空,或者新添加的消息的执行时间when是0,或者新添加的消息的执行时间比消息队列头的消息的执行时间还早,就把消息添加到消息队列头(消息队列按时间排序),否则就要找到合适的位置将当前消息添加到消息队列。

消息循环

消息队列初始化好了,也知道怎么发消息了,下面就是怎么处理消息了,看Handler.loop函数:

public static void

loop() {

final Looper me =myLooper();//从该线程中取出对应的looper对象

if (me == null) {

throw new RuntimeException("NoLooper; Looper.prepare() wasn't called on this thread.");

}

final MessageQueue queue = me.mQueue;//取消息队列对象...

// Make sure the identity of thisthread is that of the local process,

// and keep track of what that identitytoken actually is.

Binder.clearCallingIdentity();

final long ident =Binder.clearCallingIdentity();

for (;;) {

Message msg = queue.next();// might block取消息队列中的一个待处理消息..

if (msg == null) {

// No message indicates thatthe message queue is quitting.

return;

}

// This must be in a localvariable, in case a UI event sets the logger

Printer logging = me.mLogging;

if (logging != null) {

logging.println(">>>>> Dispatching to " +msg.target + " " +

msg.callback + ":" + msg.what);

}

msg.target.dispatchMessage(msg);

if (logging != null) {

logging.println("<<<<< Finished to " +msg.target + " " + msg.callback);

}

// Make sure that during the courseof dispatching the

// identity of the thread wasn'tcorrupted.

final long newIdent =Binder.clearCallingIdentity();

if (ident != newIdent) {

Log.wtf(TAG, "Threadidentity changed from 0x"

+Long.toHexString(ident) + " to 0x"

+Long.toHexString(newIdent) + " while dispatching to "

+msg.target.getClass().getName() + " "

+ msg.callback + "what=" + msg.what);

}

msg.recycle();

}

}

loop每次从MessageQueue取出一个Message,调用msg.target.dispatchMessage(msg),target就是发送message时跟message关联的handler,这样就调用到了熟悉的dispatchMessage,Message被处理后会被recycle。当queue.next返回null时会退出消息循环,接下来就看一下MessageQueue.next是怎么取出消息的,又会在什么时候返回null。

final Message next()

{

int pendingIdleHandlerCount = -1; // -1only during first iteration

int nextPollTimeoutMillis = 0;

for (;;) {

if (nextPollTimeoutMillis != 0) {

Binder.flushPendingCommands();

}

nativePollOnce(mPtr,nextPollTimeoutMillis);

synchronized (this) {

if (mQuiting) {

return null;

}

// Try to retrieve the nextmessage.Return if found.

final long now =SystemClock.uptimeMillis();

Message prevMsg = null;

Message msg = mMessages;

if (msg != null &&msg.target == null) {

// Stalled by abarrier.Find the next asynchronousmessage in the queue.

do {

prevMsg = msg;

msg = msg.next;

} while (msg != null&& !msg.isAsynchronous());

}

if (msg != null) {

if (now < msg.when) {

// Next message is notready.Set a timeout to wake up when itis 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 (false)Log.v("MessageQueue", "Returning message: " + msg);

msg.markInUse();

return msg;

}

} else {

// No more messages.

nextPollTimeoutMillis = -1;

}

// If first time idle, then getthe number of idlers to run.

// Idle handles only run if thequeue is empty or if the first message

// in the queue (possibly abarrier) is due to be handled in the future.

if (pendingIdleHandlerCount< 0

&& (mMessages== null || now < mMessages.when)) {

pendingIdleHandlerCount =mIdleHandlers.size();

}

if (pendingIdleHandlerCount<= 0) {

// No idle handlers torun.Loop and wait some more.

mBlocked = true;

continue;

}

if (mPendingIdleHandlers ==null) {

mPendingIdleHandlers = newIdleHandler[Math.max(pendingIdleHandlerCount, 4)];

}

mPendingIdleHandlers =mIdleHandlers.toArray(mPendingIdleHandlers);

}

// Run the idle handlers.

// We only ever reach this codeblock during the first iteration.

for (int i = 0; i

final IdleHandler idler =mPendingIdleHandlers[i];

mPendingIdleHandlers[i] = null;// release the reference to the handler

boolean keep = false;

try {

keep = idler.queueIdle();

} catch (Throwable t) {

Log.wtf("MessageQueue", "IdleHandler threwexception", t);

}

if (!keep) {

synchronized (this) {

mIdleHandlers.remove(idler);

}

}

}

// Reset the idle handler count to0 so we do not run them again.

pendingIdleHandlerCount = 0;

// While calling an idle handler, anew message could have been delivered

// so go back and look again for apending message without waiting.

nextPollTimeoutMillis = 0;

}

}

MessageQueue.next首先会调用nativePollOnce,然后如果mQuiting为true就返回null,Looper就会退出消息循环。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容