美文网首页
手把手带你解析Handler源码

手把手带你解析Handler源码

作者: 陈呆贼 | 来源:发表于2019-04-01 15:09 被阅读0次

    Handler概述

    关于Handler的使用无非就是在线程1中发送一个message,线程2接收到到这个消息便会在线程2里执行任务代码。而我们使用Handler最主要的目的就是在子线程中更新主线程的UI。由于AndroidUI是单线程模型,所以只有在主线程中才能够去更新UI,否则系统就会崩溃抛出异常。对于Handler的使用我这里就不在重复了,本次分析所使用的是Android8.1的源码,和旧版源码略有不同,但大致思想是一样的,接下里就直接进入主题吧。

    Handler源码分析

    首先从Handler的构造方法开始

    public Handler() {
            this(null, false);
        }
    

    调用了两个参数的构造方法。

    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();//此处通过Looper.myLooper()获取一个Looper
            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;
        }
    

    在上面的注释处,通过Looper.myLooper()去获取一个Looper,接着判断如果Looper为null的话,就抛出一个异常:

    "Can't create handler inside thread that has not called Looper.prepare()"

    该异常告诉我们没有调用Looper.prepare(),所以我们在创建Handler之前必须调用该方法。
    接着为Handler的成员变量mQueue赋值,所赋的值就是我们从获取的Looper中取出来的。
    接着我们跳转进Looper.myLooper()看看究竟是如何获取Looper的。

    public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    

    通过一个ThreadLocal的对象去获取一个Looper,那这ThreadLocal是什么呢?

    public class ThreadLocal<T> 
    

    ThreadLocal是一个泛型类,可以存储每个线程独有的变量。比如你在线程1中通过ThreadLocal对象的set方法存储了一个String变量“abc”,在线程2中存储一个String变量“def”,那么在这两个线程中通过ThreadLocal对象的get方法获取该变量时会因为在不同的线程中获取不同的值。在线程1中就会得到“abc”,线程2中就会得到“def”。
    由此可见,通过ThreadLocal去获取的Looper也是线程独有的,不同的线程拥有不同的Looper。
    接着我们看看ThreadLocal的set方法。

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

    首先通过Thread.currentThread()获取当前线程,接着把当前线程传入getMap方法来获取一个当前线程的ThreadLocalMap对象。

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

    getMap方法相当简洁,就是通过返回某线程Thread上的成员变量threadLocals来获取ThreadLocalMap的。也就是说每个Thread内部都持有一个ThreadLocalMap对象用来存放线程间独立的变量。
    而ThreadLocalMap其实就是ThreadLocal的一个静态内部类。在ThreadLocalMap的内部有一个数组。

    private Entry[] table;
    
    static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    
    

    一个弱引用对象的数组。每个Entry对象持有一个ThreadLocal的弱引用,而值存放在value字段上。所以ThreadLocalMap存放了当前线程的独立变量,他们全都放在table字段中。

    接着上面的 代码,如果map不为null的话就把值设置在这个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();
            }
    

    根据ThreadLocal的hash值和数组的长度做与运算得到的下标来作为value存放的位置。
    如果下标所处的位置不为空,那说明当前下标不可以存放value,那就调用nextIndex来取得下一个下标,如果下一个下标所处的位置是null,那么就可以把value存放在当前下标位置。大致逻辑就是这样的。
    接下来我们再看看ThreadLocal的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();
        }
    

    首先通过getMap获取到ThreadLocalMap,接着通过ThreadLocalMap.getEntry这个方法获取到存放在ThreadLocal当中的值。
    到此我们可以做一下总结:

    在每个Thread的内部都有一个ThreadLocalMap,这个ThreadLocalMap里有一个名为table的Entry数组,所以ThreadLocal里存放的变量才独立于每个线程。我们往ThreadLocal里存放的对象都是存放进这个Entry数组的(通过hash计算来决定下标值)。
    所以把Looper存放在ThreadLocal当中就可以保证每个线程的Looper是独立存在的。

    当Handler获取了Looper以后就可以正常工作了。我们使用Handler时一般会调用Handler的sendMessage方法

    public final boolean sendMessage(Message msg){
            return sendMessageDelayed(msg, 0);
    }
    

    我们跳进sendMessageDelayed,发现最终会调用

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

    这里的mQueue就是我们构造方法里Looper的mQueue,所以Looper是必须的。
    看看最后一行enqueueMessage。

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    在这里msg.targe指向了this,也就是我们当前的Handler。
    发现最后是调用的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) {
                    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;
                }
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    

    说是消息队列,其实MessageQueue是个链表,因为对于链表来说插入和删除操作的时间复杂度是O(1),所以非常适合用来做消息队列,在enqueueMessage方法里把新消息插入队尾,也就是下面这两行代码。

    msg.next = p; 
    prev.next = msg;
    

    我们通过sendMessage方法把一个Message放进消息队列中,那么是谁来处理消息队列中的消息呢。那就需要Looper登场了,前面说过一个线程如果需要创建一个Handler那么就必须要有一个Looper。我们来看看Looper的构造方法。

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

    在Looper的构造方法里会新建一个MessageQueue,所以这个MessageQueue和Looper是绑定了的,同时会获取创建Looper的那个线程。要使用Looper还必须要调用Looper.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;
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            for (;;) {
                Message msg = queue.next(); // 从消息队列中获取消息
                if (msg == null) {
                    return;
                }
    
                final Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
    
                final long traceTag = me.mTraceTag;
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
                final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
                final long end;
                try {
                    msg.target.dispatchMessage(msg);//执行任务
                    end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                if (slowDispatchThresholdMs > 0) {
                    final long time = end - start;
                    if (time > slowDispatchThresholdMs) {
                        Slog.w(TAG, "Dispatch took " + time + "ms on "
                                + Thread.currentThread().getName() + ", h=" +
                                msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                    }
                }
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
                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方法后会启动一个无限循环,每次循环都会调用如下方法。从消息队列中获取一个Message,如果消息队列中没有消息可以取了,该方法可能会阻塞。

    Message msg = queue.next();
    

    接着会调用

    msg.target.dispatchMessage(msg);
    

    这个target就是之前enqueueMessage时设置的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);
            }
        }
    

    如果创建Message时传入了callback那就调用handleCallback执行任务,否则查看mCallback是否可以调用,可以则调用。mCallback是继承Handler时可选的传入参数。

    到此一个完整的Handler机制原理就讲解完毕了。 Handler流程图.png

    前面说过一个线程中必须要有一个Looper才能使用Handler,否则就会奔溃。那为什么我们在主线程中可以使用Handler呢?这是因为主线程ActivityThread已经帮我们做好了Looper的初始化工作。

    public static void main(String[] args) {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
            SamplingProfilerIntegration.start();
    
            // 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");
        }
    

    使用了Looper.prepareMainLooper();来初始化Looper,并使用了Looper.loop()开启了消息循环队列。
    还有一点需要注意的是,在使用Handler的时候,如果消息都处理完毕了,应该调用Loop.quit或者Looper.quitSafely方法来停止轮询。否则由于Looper会不断的轮询MessageQueue导致该Looper所在的线程无法被释放。

    总结

    Handler在近几年来已经算是非常基础的知识了,在面试当中也是高频题,关于Handler的使用其实是很容易造成内存泄漏的,这里就不在多说了,有兴趣的朋友可以搜索一下Handler内存泄漏相关的知识。

    相关文章

      网友评论

          本文标题:手把手带你解析Handler源码

          本文链接:https://www.haomeiwen.com/subject/ecapbqtx.html