Android的消息处理机制

简述

     Android使用Handler主要原因是为了解决在子线程中无法访问UI线程的矛盾。因为UI线程是不安全的,如果多线程并发访问会导致UI控件处于不可预期的状态。系统不对UI控件上锁是为了避免UI访问的逻辑变复杂,同时也为了避免降低UI访问的效率。
      Handler是Android消息机制的上层接口。Handler的运行需要依赖底层MessageQueue和Looper的支撑。
      MessageQueue:消息队列,以队列的形式对外提供插入和删除的工作,但其内部存储结构并不是真正的队列,而是采用 单链表的数据结构来存储消息列表。
      Looper Looper会以无限循环的形式去查找消息队列是否有新的消息,有的话就处理。除UI线程外,其他线程默认没有Looper,如果需要使用Handler就必须为其创建Looper。UI线程在被创建时就会初始化Looper。
      ThreadLocal:ThreadLocal的作用是可以在每个线程中互不干扰地存储与提供数据。

ThreadLocal

     ThreadLocal是一个线程内部数据存储的类,可以通过它在指定线程中存储数据,数据存储后,只有在这个线程中可以获取到。因此通过ThreadLocal可以很轻松地实现不同线程的Looper的管理。
     由ThreadLocal的set()方法可以知道,ThreadLocal实现在不同线程中独立存储与获取数据的原理是,先获取执行当前代码的线程,然后通过getMap()方法,获取这个线程的ThreadLocalMap(一个专门用来维护线程的数据的Hash map),再将数据放进去。由getMap()方法可以看出,每个线程的ThreadLocalMap是在各自线程内部维护的。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

   
       由get()方法也可以看出,是先取得当前线程内部的ThreadLocalMap,再来获取记录的,如下:

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

     
     上面get()方法中,先取得线程内部维护的map后,再从map中通过getEntry()方法取得记录,源码如下。有一点容易混淆的是,为什么取得线程内部维护的map后,还要通过getEntry来获取记录,难道是一个ThreadLocal对一个线程可以存储多个值(通过一些技巧是可以的,比如把Bundle对象存进去,不过不是真正意义上的存储多个值)。思索发现,map是属于线程的(是 属于线程,只不过由ThreadLocal来进行 维护),因此这个map保存的是这个线程通过各个ThreadLocal对象保存的数据。在getEntry()方法中,作为key的是ThreadLocal,通过它的哈希码与map中的table的(长度-1)进行位运算得到table[]的索引,进而得到 这个线程在这个ThreadLocal中保存的记录

private Entry getEntry(ThreadLocal key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}


Looper
    
     Looper类中定义了一个ThreadLocal:

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

final MessageQueue mQueue;
     可以看出sThreadLocal被static final修饰,意味着它是静态成员常量,是Looper类的全体对象所共同持有。Looper中还有MessageQueue对象,在new Looper()时初始化:
 
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
 

     在Looper.loop()方法中会无限循环地检查MessageQueue中是否有待处理的消息,直到Looper退出,因此在子线程中如果手动创建了Looper,应该在所有事情完成之后调用quit方法退出,否则线程会一直处于等待状态。在获取到消息之后,判断这个消息的target(即是handler)是否为null,是null的话意味着这是一个退出消息,执行return。否则将消息交给目标handler处理。如下:

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;
        }
     ... ...
        msg.target.dispatchMessage(msg);
     ... ...
    }
}


  MessageQueue

     在Looper.loop()的无限循环中,通过queue.next()来获取消息,源码如下。在next()方法中,定义了一个变能量nextPollTimeoutMillis,用来描述当消息队列中没有新的消息时,当前进程需要进入睡眠等待的时间。
     当有消息,且当前时间(now)大于等于执行时间(msg.when)时,将消息返回,next()方法执行结束。当有消息,但不是立即执行时(即msg.when>now),会将nextPollTimeoutMills设置为msg.when与now的差值绝对值,即意味着如果下次检查消息队列时没有新消息,则线程睡眠nextPollTimeoutMills的时间。当没有消息时(msg == null),会将nextPollTimeoutMillis设置为-1,表示如果下一次检查消息队列时没有新消息,那么线程将进入无限地休眠,直到被其它线程唤醒。
     next()方法中定义了一个pendingIdleHandlerCount变量(只在第一次循环为-1),描述IdleHandler的数量。当第一次空闲时(消息队列为空或消息执行时间未到),先获取注册到消息队列中的IdleHandler数量,然后线程会分发一个线程空闲消息给那些IdleHandler ,同时把nextPollTimeoutMills设置为0( )。同时把IdleHandler数量设置为0,下次就不会再给这些IdleHandler 发线程空闲的消息了。
     
      注:之前不理解为什么要将nextPollTimeoutMills置为0来让线程不进入睡眠状态,一般理解,检查是否有消息的函数nativePollOnce()是在置0之后执行的,就算在IdleHandler处理空闲消息期间,其它线程发来消息,检查函数应该也能检查到。后来看了JNI方法,函数nativePollOnce()函数是个JNI方法,由C++层的函数来实现,C++中通过函数调用,在pollInner函数(检查当前线程是否有新的消息需要处理)中调用了epoll_wait函数来监听注册在epoll实例中的文件描述符的IO读写事件,如果没有发生IO读写,那么当前线程就会在epoll_wait函数中睡眠等待JAVA层传入的时间参数。因而,在IdleHandler处理空闲消息时,可能有线程发来消息(发来消息时,C++层会向管道写入字符“W”,这样线程的这个管道就会发生IO写事件),但处理完空闲消息后,没有其它线程再发消息,导致在nativePollOnce()函数中线程进入睡眠,实际是有消息需要被处理的。因而需要将nextPollTimeoutMills置为0,让线程马上回来检查消息队列。

next():
Message next() {
   ... ...
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        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.
               ... ...
            }
            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;
                    ... ...
                    msg.next = null;
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) 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 to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            ... ...
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}


IdleHandler:
     IdleHandler是空闲消息处理器,是MessageQueue类中的一个接口。通过MessageQueue中的addIdleHandler()和removeIdleHandler()方法来注册和注销一个空闲消息处理器(通过getLooper()方法获取线程的Looper,再用Looper.mQueue取得MessageQueue的引用)。
     IdleHandler中只有一个成员函数queueIdle,用来接收线程空闲消息。在线程空闲时(消息队列没有消息或消息执行时间没到),在MessageQueue的next()方法中,会给所有注册到消息队列的IdleHandler发送线程空闲的消息,即调用他们的queueIdle()方法,方法的返回值为false时,表示这个handler在执行完后会从MessageQueue中移除,否则返回true。
     在next()函数的一次调用中,只会发送一次线程空闲消息。可以把一些不重要的或不紧急的事情放在线程空闲时执行,可以充分利用时间。

public static interface IdleHandler {
    /**
    * Called when the message queue has run out of messages and will now
    * wait for more.  Return true to keep your idle handler active, false
    * to have it removed.  This may be called if there are still messages
    * pending in the queue, but they are all scheduled to be dispatched
    * after the current time.
    */
    boolean queueIdle();
}

enqueueMessage():

     在这个方法中,参数when的值是消息处理的绝对时间(从系统开机到当前时刻的毫秒数+希望延迟执行的毫秒数,即是执行的精确时间)。执行插入时,会进行插入位置的判断,有以下几种情况:
          ①、目标消息队列时一个空队列。
          ②、插入的消息处理时间等于0(即when==0,意味着有最高优先级,消息将被插在队列最前边。 调用Handler的sendMessageAtFrontOfQueue()方法,会将when设置为0)。
          ③、插入的消息的处理时间小于保存在目标消息队列头的消息的处理时间。
          ④、插入的消息的处理时间大于等于保存在目标消息队列头的消息的处理时间。
     消息队列的数据结构是链表,故消息的插入即是链表结点的插入。在以上四种情况中,前三种都应当将消息插入到队头,插入之后如果目标线程是睡眠状态,则需要将其唤醒,故needWake设置为mBlocked(指示线程是否睡眠的变量)。最后一种,根据消息的when的值来寻找合适的插入位置,即插到队列中部,无需将线程唤醒,故将needWake设置为false。
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        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 {
            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;
}
     以上代码就完成了消息入队的操作。


Handler
     
     Handler中定义了mLooper与mQueue两个变量,在创建handler的时候,构造器会对这两个变量初始化,这就是为什么在子线程没有创建Looper的时候(Looper.prepare()),创建Handler会报错。
     Handler的sendMessage()/sendMessageDelayed()最终都会跳到sendMessageAtTime()中。在sendMessageAtTime()方法中调用enqueueMessage()方法,再在其中调用queue.enqueueMessage()方法入队。从enqueueMessage()的第一句msg.target=this可以知道,首先将消息的target设置成了这个handler,以便消息在looper中通过这个handler进行处理。

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


dispatchMessage(Message):

     由dispatchMessage()方法中,先判断消息是否有回调(runnable对象),如果有,则执行消息的Runnable对象。否则判断handler的成员变量mCallback是否是null(创建handler时,可以传入一个实现了Callback接口的Callback对象,里面只有一个成员函数handleMessage()),不是得话执行回调接口的handleMessage()方法。最后以上条件都不满足,则执行handler的handleMessage()方法。
public interface Callback {
    public boolean handleMessage(Message msg);
}

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}






     
     
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值