最近经常使用Handler相关的一些东西。翻了下源码,总结一遍方便回顾。

Android消息机制就是Android的handler机制。

首先是为什么要使用handler?

子线程不能操作ui,因此需要将操作ui的消息传递到主线程,使主线程按照需求更新ui,避免操作不安全。

 

Handler的重要组成部分:Handler Looper MessageQueue ,其中Looper是Handler和messagequeue通信的桥梁。

Handler是消息处理器。MessageQueue是消息队列;Looper是消息循环器。

重要的方法:

Looper.prepare():为当前线程创建一个looper。在该方法内部,使用ThreadLocal进行实现。把new Looper()保存到ThreadLocal。在Looper的构造方法中创建了消息队列。并获取了对应的Thread。

Looper.loop();开启消息循环,在ActivityThread中,自动开启消息循环。

Looper.myLooper() :获取当前线程的looper

looper.mQueue:looper对应的消息队列。

在handler的构造方法中,先获取looper和messagequeue.实现了handler和looper和message的关联。如下:

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;    }

 

消息机制的理解可以分为以下三个部分

1、消息的插入

2、消息的循环

3、消息的处理

 

1 消息分发和入队

在了解消息消息机制前,先了解下消息获取。通常建议使用obtain进行消息的获取,而不是直接new Message,原始是使用obtain方法可以实现消息复用,里边使用了消息池。代码如下:

/** * 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();}

接下来进入正题。我们通常使用sendMessage方法进行handler的消息发送(其他的如sendEmptyMessage、post等,大同小异,只是会对信息进行包装,最终调用的方法都是一样的)。这里先以sendMessage为例,后边顺带讲一下post的操作。

话不多说,上源码,注意里边添加的注释

public final boolean sendMessage(Message msg){    return sendMessageDelayed(msg, 0);}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);}

从这里可以看到消息发送最终就是调用了一个消息入队的方法,把message放到消息队列mQueue中,这个消息队列使用looper.mQueue取出,looper是Handler对应的消息循环器,它创建时会创建一个对应的消息队列。是不是很简单,发送消息就是让消息入队。那怎么入队呢?继续走。。。

boolean enqueueMessage(Message msg, long when) {    ....此处代码省略    synchronized (this) {        ...        msg.markInUse();        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循环吧消息插入到消息队列中            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;}

省略了方法中我认为不重要的代码,主要关注重点内容。相关的注释已经在代码中添加,可以看到,消息入队很简单,主要是通过时间比对,按照消息需要执行的时间点插入到队列的相应位置。里边️一个需要关注的点,代码中出现了一个方法nativeWake,这个方法通过native调用唤起被阻塞的方法,代码中的mBlocked表示消息循环是否处于阻塞状态。在if分支中,消息插入到队首,此时如果是被阻塞的状态, 则标记needWake为mBloacked(true)。如果是else分支,插入到队列中间,需要使用needWake和p是否是异步决定是否标记needWake为true。如果前边有消息是异步消息,那本次插入则不需要进行唤醒操作,因为前边异步消息插入的时候已经进行了标记。最后根据needWake的值来确认要不要调用nativeWake方法,该方法的作用是进行唤醒操作,唤醒被阻塞的消息获取过程。

插入队列的消息谁来处理呢?答案是Looper,消息循环器。从队列中去消息进行处理,可以想像成一个传送带,MessageQueue就是传送带,handler往上边放消息,Looper取下来处理。Looper通过loop方法进行消息获取,核心源码如下:

/** * 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();    ...    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 (;;) {        //next方法是获取消息的关键方法,是一个阻塞方法        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);            }        }    }

在loop方法中,实现主要功能的代码块就是一个无限for循环操作,用于不停地从队列中获取消息。获取消息是通过MessageQueue的next方法实现的。该方法是一个阻塞方法,且该方法获取的message正常不会为null,如果为null,loop方法return。next方法的核心源码如下。

Message next() {    // Return here if the message loop has already quit and been disposed.    // This can happen if the application tries to restart a looper after quit    // which is not supported.    final long ptr = mPtr;    if (ptr == 0) {        return null;    }    int pendingIdleHandlerCount = -1; // -1 only during first iteration    int nextPollTimeoutMillis = 0;    for (;;) {        if (nextPollTimeoutMillis != 0) {            Binder.flushPendingCommands();        }        //该方法的作用是通过natvie调用进行阻塞,如果nextPollTimeoutMillis==1,则进行阻塞        nativePollOnce(ptr, nextPollTimeoutMillis);        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;            }                ...代码省略    }}

next方法稍微有点长,主要看其中的注释部分。这里有两个关键点,nextPollTimeoutMillis变量和nativePollOnce方法,nextPollTimeoutMillis变量是一个时间标记量,nativePollOnce的形参,该方法中,如果nextPollTimeoutMillis如果值为-1,会进行阻塞,如果不是-1,正常往下走。真正取消息的地方是synchronized代码块,在这里,首先通过一个while循环找到需要处理的异步消息。这里涉及到了同步屏障机制,简单理解,就是为了保证异步消息优先处理,会插入一个同步屏障,同步屏障消息的特点是targe为null,这样的话,在while循环中就会不停往后寻找,直到找到一个异步消息,拿出来。接下来有两种情况:

1、消息拿到了,需要对比一下当前时间和消息中的时间信息,如果当前时间还没有达到消息要执行的时间点,直接使用时间差赋值给变量nextPollTimeoutMillis。直到时间满足了消息执行的要求。在else分支中,从队列中取出消息。返回给Looper。

2、拿到的消息是null,说明队列中没有异步消息,这时候标记nextPollTimeoutMillis为-1,在下一次循环中通过nativePollOnce方法进行阻塞,阻塞过程中不消耗cpu资源。直到有消息入队,进行阻塞唤醒的操作,还记得前边的方法吗?在MessageQueue的enqueueMessage方法中,消息入队后有个nativeWake方法,便是和这个阻塞方法相对应。即,没有消息时使用nativePollOnce阻塞,消息入队后使用nativeWake唤醒。

再次回到Looper的loop方法,在该方法中,如果取到消息,会调用msg.target.despatchMessage方法进行消息的处理。走到这里基本已经接近消息处理的尾声,这个方法中执行的就是消息对应的真正逻辑。

public void dispatchMessage(Message msg) {    //callback是通过post方法传过来的runnable,handleCallback方法中其实就是执行runnable的run方法。    if (msg.callback != null) {        handleCallback(msg);    } else {        //这个mCallback其实就是构造handler的时候作为参数传进来的handler        if (mCallback != null) {            if (mCallback.handleMessage(msg)) {                return;            }        }        //这里是调用构建handler时实现的handleMessage方法。        handleMessage(msg);    }}

通过上边的代码可以看到despatchMessage方法的执行主要分成三块:

1、执行调用message的callback执行逻辑;

2、调用mCallback执行处理逻辑;

3、调用handleMessage进行逻辑处理。

分别看下

(1)message的callback其实就是我们调用handler的post和postDelay方法中携带的runnable。代码如下:

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进行消息分发,也是把runnable包装成了一个message进行分发和处理,包装完后执行的也是sendMessage操作,后续流程和sendMessage流程一模一样。postDelay同样。这里不尽兴赘述。

(2)mCallback又是个什么东西呢?

这个是handler构造时带过来的,举个栗子:

private Handler testHandler = new Handler(new Handler.Callback() {        @Override        public boolean handleMessage(Message msg) {            return false;        }    });

构造参数中的callback就是对应的Handler中的mCallback。因此,如果我们不是使用post方法发送的事件,会优先判断该回调是否为空。

(3)如果上述两种情况都为空了,就会执行我们创建Handler时实现的handleMessage方法,这个方法是不是很熟悉。

总结一下,Android消息机制就是一个线程,使用指定线程的Handler把消息扔到消息队列,指定线程的Looper取出消息在对应线程进行处理。

更多相关文章

  1. Android 公共库的建立方法
  2. Android消息处理-概念普及篇
  3. Android文件系统的提取方法(一)
  4. Android获取webView快照与屏幕截屏的方法
  5. Android测量View宽和高的一般通用方法

随机推荐

  1. flash如何自动连接mysql数据库
  2. Windows下重置MySQL密码【MYSQL】
  3. MySQL5.7完全卸载
  4. MySQL数据导入到infobright中
  5. MySQL在C++中使用后务必释放 result,否则
  6. 利用keepalived构建高可用MySQL-HA
  7. MySQL表格查询基本语句2
  8. 组织这种结构的最佳方式?
  9. mysql添加外键语句
  10. lucene 4.10 检索mysql数据库