Android消息机制之Handler

Handler是一个消息分发对象,主要作用是将一个任务切换到某个指定的线程中进行。如Android中操作UI必须在主线程中进行,此时就可以通过Handler实现。

系统为什么不允许在子线程中访问UI呢?这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可控状态(界面错乱)。那么为什么不对UI控件的访问加上锁机制呢?首先加上锁机制会让UI访问的逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。

Handler的使用

Handler常用的方法如下表所示:

方法描述
handleMessage(Message msg)处理消息的方法。该方法通常用于被重写。
sendEmptyMessage(int what)发送空消息。
sendEmptyMessageDelayed(int what, long delayMillis)指定多少毫秒后发送空消息。
sendMessage(Message msg)发送消息。
sendMessageDelayed(Message msg, long delayMillis)指定多少毫秒后发送消息。
post(Runnable r)将Runnable对象添加到MessageQueue中
postDelay(Runnable r, long delayMillis)指定多少毫秒后将Runnable对象添加到MessageQueue中
obtainMessage()生成Message对象,配合sendToTarget()方法将该消息发送到MessageQueue中
removeMessages(int what)移除指定消息
removeCallbacks(Runnable r)将Runnable对象从MessageQueue中移除
Android消息机制分析

Android消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程。为了更好的理解Looper的工作原理,我们需要先了解ThreadLocal的工作原理。

ThreadLocal工作原理

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程存储数据,数据存储后,只能在指定线程中获取存入的数据,其他线程无法获取数据。如对于Handler来说,它需要获取当前线程的Looper,这个时候通过ThreadLocal就可以轻松的实现Looper的存取。

ThreadLocal的核心知识点就是它的存取值得方法,对应set()与get()方法。set()方法源码如下(源码为jdk1.8版本):

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

如源码所示,set方法首先会通过getMap()的方法获取当前线程的ThreadLocalMap对象,若此对象不为空,则直接调用此对象的set方法将数据存储起来,否则调用createMap()方法创建当前线程的ThreadLocalMap对象,并保存数据。其中ThreadLocalMap的set方法源码如下:

        private void set(ThreadLocal<?> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

上述代码中,会去判断Entry数组中是否存在Entry含有ThreadLocal的对象,若存在,则直接将值赋给对应的Entry对象的value,否则则新创建一个Entry对象,存入对应的ThreadLocal对象与值,并将此对象保存在Entry数组中。Entry继承了WeakReference类,并且在其内部定义了一个类型为Object的value对象。

看完set()方法后,接下来我们看一下get()方法的源码:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

get()方法也是先通过当前线程获取ThreadLocalMap 对象,对象不为空,则根据当前ThreadLocal对象去获取 ThreadLocalMap.Entry对象,如果对象Entry对象不为空,则返回Entry对象的value值;如果ThreadLocalMap对象为空,则直接返回setInitialValue()方法的返回值。我们来看一下这个方法的源码:

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

可以看到,返回的是initialValue()方法的返回值,这个方法的返回值是直接返回null对象的。在这个方法中会再次根据当前线程去获取ThreadLocalMap ,若对象不为空,则将ThreadLocal与value存进map中,否则创建map,并将ThreadLocal与value存进map,而此value值就是null。

从上述源码中可以看出,ThreadLocal的get与set方法操作的都是当前线程的ThreadLocalMap对象的Entry数组,因此在不同线程中访问同一个ThreadLocal的get与set方法,它们对ThreadLocal所做的操作仅限于各自线程内部,这就是为什么ThreadLocal可以在多个线程中互不干扰的存储和修改数据。

MessageQueue工作原理

MessageQueue主要包含两个操作:插入和读取,对应enqueueMessage()方法与next()方法,读取操作本身会伴随着删除操作。尽管MessageQueue叫做消息队列,但是它的内部实现并不是队列,它实际上是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势。下面我们先来看一下enqueueMessage()方法的源码:

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

从源码中可以看出,首先会判断MessageQueue是否绑定Handler(msg.target)和消息是否正在使用。若满足插入条件,则锁定MessageQueue对象,然后判断消息是否正在退出,是则回收消息并返回false;否则接着往下执行,将消息设置为正在使用中,并配置一些信息。当mMessages为null说明队列中没有等待处理的消息,when等于0时说明这个消息需要马上处理, when < p.when时表示这个消息要比之前的消息提前处理,这三种情况直接将消息插入到表头;否则就执行else的代码块,进入循环,将消息插入到链表中合适的位置,跳出循环。整个循环过程就是将消息插入到单链表中的过程。

接下来我们来看看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();
            }

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

                // 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++) {
                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(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

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

可以看出next()方法是一个无限循环的方法,如果消息队列中没有消息,那么next()方法会一直阻塞在这里。当有新消息到来时,next()方法会返回这条消息,并将其从链表中删除。

Looper的工作原理

Looper在Android的消息机制中扮演者消息循环的角色,具体来说就是它会不停的从MessageQueue中查看是否有新消息,如果有新消息则会立即处理,否则就一直阻塞在那里。在它的构造方法中会创建一个MessageQueue对象,然后将当前的线程对象保存起来:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

我们可以通过Looper.prepare()来为一个线程创建一个Looper对象,此方法会将Looper对象保存到ThreadLocal中;接着通过Looper.loop()来开启消息循环。Looper还提供了getMainLooper()方法以便在任何地方都可以获取到主线程的Looper。Looper提供了quit()和quitSafely()两个方法用于退出,区别在于:quit()方法会直接退出Looper,而quitSafely()只是设定一个退出标记,等把消息队列中的所有消息都处理完后才安全退出。

Looper最重要的一个方法就是loop()方法,只有调用loop()后消息循环系统才会真正起作用,其源码如下:

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

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

从源码可以看出loop()方法是一个死循环,唯一跳出循环的方式就是MessageQueue的next()方法返回null。当Looper的quit()方法被调用时,会调用MessageQueue的quit()或者quitSafely()方法,当消息队列被标记为退出状态时,它的next()方法返回null。loop()会调用MessageQueue的next()方法,而next()方法是个阻塞操作,当没有消息时,next()方法会一直阻塞在那里,从而导致loop()一直阻塞在那。若next()返回了新消息,则Looper会执行msg.target.dispatchMessage(msg),这里的msg.target就是发送此消息的Handler对象。这样,Handler的dispatchMessage()方法是在创建Handler时所使用的Looper中执行的,因此成功的将代码逻辑切换到Looper所在的线程中。

注:

  1. 当Looper退出后,通过Handler发送消息会失败,Handler的send方法返回false。
  2. 在一个子线程中,若手动为其创建了Looper,那么在所有事情完成后应调用quit方法中止循环,否则这个线程会一直处于等待状态。
Handler的工作原理

Handler的工作主要包含消息发送和接收过程。Handler发送消息是通过post的一系列方法及send的一系列方法来实现的,而post一系列的方法最终又是通过send的一系列方法来实现的,send方法最终都是调用sendMessageAtTime()这个方法发送消息,我们来看一下这个消息的源码:

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

由上面的源码可以看出,Handler发送消息的过程就是向消息队列中插入一条消息,MessageQueue的next()方法返回这条信息给Looper,在Looper的loop()方法中将消息交由Handler处理,即Handler的dispatchMessage()方法,其源码如下:

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

    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }

在上面的源码中,先检查Message的callback是否为null,不为null就通过handleCallback()方法处理消息。callback是一个Runnable对象,实际上就是post方法所传递的Runnable参数。其次就是检查mCallback是否为null,不为null就通过mCallback的handleMessage()方法处理消息,Callback就是以上源码中的那个接口。通过Callback可以采用如下方式来创建Handler对象:Handler Handler = new Handler(callback)。那么它的意义是什么呢?它提供了另外一种使用Handler的方式,当我们不想派生Handler的子类时,就可以通过Callback来实现。最后调用Handler的handleMessage()方法处理消息。

主线程的消息循环

Android的主线程就是ActivityThread,主线程入口为main()方法,源码如下所示:

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

源码中可以看出在main()方法中系统会通过Looper.prepareMainLooper()来创建主线程Looper及MessageQueue,并通过Looper.loop()开启主线程消息循环。主线程消息循环开始后,还需要一个Handler对象进行消息处理,这个Handler就是ActivityThread.H,它内部定义了一组消息类型,包括四大组件的启动和停止等过程。ActivityThread通过ApplicationThread和AMS进行进程通讯,AMS以进程间通讯的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法然后ApplicationThread会向H发送消息,H收到消息后将ApplicationThread中的逻辑切换到主线程中去执行,这个过程就是主线程的消息循环模型。

总结:
Android消息机制的整体流程就是:Handler发送一条消息通过MessageQueue的enqueueMessage()方法加入到消息队列中,Looper通过loop()方法去调用MessageQueue的next()方法检查是否有消息要处理,若有消息则调用Handler的dispatchMessage()方法将消息返回给Handler处理,同时把处理消息的方法切换至Looper所在的线程。

参考:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值