Android Handler源码分析

做了一年的Android业务,感觉自己的Android能力并没有得到多大提升,下定决心开始阅读Android源码,就从使用最频繁的Handler开始吧。

Handler定义

言归正传,首先Handler是什么,可能大部分人都不太清楚。但大家一定知道,UI线程不能做耗时操作,所有的耗时操作都要扔到子线程中去执行,那问题来了,子线程又不能更新UI,那这时候在耗时操作中要更新UI怎么实现呢?

这时候Android就给我们提供了Handler,通过Handler可以将子线程中的消息抛到主线程中去执行,或者说可以将任意线程A的消息发送给任意线程B,实现线程之间的交互,这就是Handler的作用。由于Android应用是强界面应用,耗时操作与界面的交互特别频繁,这也使Handler的使用异常的多,所以Handler是Android中最为重要的类之一。

Handler实现原理

虽然对Handler的使用非常频繁,但其实对Handler的认识还是比较少的,用的最多的也就是sendMessage和handleMessage这些基础的操作罢了,但对Handler深层的问题并不了解,比如:

  • 一个Handler可以用几个Thread,几个Looper?
  • 如何在子线程新建Handler并使用?
  • Message最好的创建方式
  • 主线程中Looper的轮询死循环为何没有阻塞主线程?
  • Handler的postDelay如何实现的?
  • ThreadLocal对象

以上问题来自于博客Android面试题:Handler ,文中有更详细的讲解,今天带着博客中提的问题来进入源码分析。

首先看一下Handler的构造方法:

/**
 * Default constructor associates this handler with the {@link Looper} for the
 * current thread.
 *
 * If this thread does not have a looper, this handler won't be able to receive messages
 * so an exception is thrown.
 */
public Handler() {
    this(null, false);
}

/**
 * Constructor associates this handler with the {@link Looper} for the
 * current thread and takes a callback interface in which you can handle
 * messages.
 *
 * If this thread does not have a looper, this handler won't be able to receive messages
 * so an exception is thrown.
 *
 * @param callback The callback interface in which to handle messages, or null.
 */
public Handler(Callback callback) {
    this(callback, false);
}

/**
 * Use the provided {@link Looper} instead of the default one.
 *
 * @param looper The looper, must not be null.
 */
public Handler(Looper looper) {
    this(looper, null, false);
}

/**
 * Use the provided {@link Looper} instead of the default one and take a callback
 * interface in which to handle messages.
 *
 * @param looper The looper, must not be null.
 * @param callback The callback interface in which to handle messages, or null.
 */
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}

从源码中我们可以看出Handler有多种构造方法,构造参数主要是两个Looper和Callback。其中Callback可以为null,但Looper必须有(有小伙伴可能会问,不是有没有Looper的构造函数吗),看清楚了,没有Looper的构造函数上写了Looper禁止为空,否则报Exception。

ok,可以为空的参数我们先不管,先去看一下必须要有的Looper是何方神圣。

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

final MessageQueue mQueue;
final Thread mThread;

首先分析一下Looper持有的对象,Looper主要持有四个对象,从字面意思上也可以理解它的作用。

  • sThreadLocal, 当前线程持有的Looper对象
    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
  • sMainLooper,主线程持有的Looper对象
    /**
     * Returns the application's main looper, which lives in the main thread of the application.
     */
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
  • mQueue,当前Looper持有的消息队列
    /**
     * Gets this looper's message queue.
     *
     * @return The looper's message queue.
     */
    public @NonNull MessageQueue getQueue() {
        return mQueue;
    }
  • mThread,当前Looper相关的线程
    /**
     * Gets the Thread associated with this Looper.
     *
     * @return The looper's thread.
     */
    public @NonNull Thread getThread() {
        return mThread;
    }

到这,我们可以大胆地推测一下,Looper就是用来管理当前线程中的任务队列的,所谓的线程间通信,就是将消息发送到对应线程Looper的队列中。

每个Looper对应唯一一个线程,唯一一个消息队列,Looper对象中直接定义了主线程的静态Looper,所以主线程的Looper有且只有一个。

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

/** Initialize the current thread as a looper.
  * This gives you a chance to create handlers that then reference
  * this looper, before actually starting the loop. Be sure to call
  * {@link #loop()} after calling this method, and end it by calling
  * {@link #quit()}.
  */
public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

从上面也可以看出,Looper的构造函数并不向外暴露,要想获得Looper对象,必须要先调用Looper的prepare方法,才能从ThreadLocal中拿到Looper。那也就是说,Handler要想正常初始化,拿到Looper对象,必须要调用Looper.prepare方法,否则不可能拿到Looper。这也是为什么在子线程中新建Handler对象必须要调用Looper.prepare的原因,目的就是初始化Looper。

那小伙伴又奇怪了,平时使用的时候也没有调用Looper.prepare方法呀,使用也是正常的呀。

那是因为平时使用的Handler都是在主线程去创建的,看一下Activity的启动过程,问题就迎刃而解。可以看到,在Activity启动时,就已经自动帮我们调用了Looper.prepare方法,所以在主线程中新建Handler对象,并不需要主动获取Looper对象,因为系统已经帮我们获取了。

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

到这里,Handler和Looper的关系以及两个对象中的属性解释已经讲完了。

简单总结一下:每个线程对应唯一的Looper,对应唯一队列,但不对应唯一的Handler,毕竟Handler只要拿到Looper就可以创建新的,而Looper.prepare方法则明确说明了一个线程只能有一个Looper。

那通过Handler怎么就能实现线程间通信呢?结合平时调用的方法,可以猜测一下,无非就是通过在子线程中拿到主线程中的Handler,并通过handler向Looper的队列中扔消息,主线程收到消息后处理。

结合源码来验证一下:

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
    
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        // SystemClock.uptimeMillis() + delayMillis:当前时间+延迟时间 = 实际执行的时间
        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);
    }

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // msg是消息本身,uptimeMillils是Message执行的时间
        return queue.enqueueMessage(msg, uptimeMillis);
    }

实现线程间通信的第一步就是通过Handler将Message send出去,而不管调用的哪个send方法,最终都会调用到sendMessageAtTime这个方法上来,显然,最后回到了enqueueMessage这个方法。

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 {
                // 根据message的处理时间,对消息队列重新排序
                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;
    }

可以看出,enqueueMessage方法的作用是根据新消息的执行时间,对消息队列重新排序,将不需要delay的消息置顶,需要delay的消息插到队列中间。这样,Handler就完成了消息的发送工作,下面就是Looper对消息进行处理了。

上文没讲到的是,在调用了Looper.prepare方法之后,还得调用Looper.loop方法,这样Looper才能处理消息。

   /**
     * 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();
        ......
        
		// 死循环遍历队列
        for (;;) {
            // 这个方法会导致程序挂起
            Message msg = queue.next();
            if (msg == null) {
                return;
            }
            ......
            try {
            	// 把消息发送给主线程的Handler处理
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ......
            msg.recycleUnchecked();
        }
    }

在调用loop方法后,Looper开启了一个死循环,遍历消息队列,并循环地把消息发送给主线程的Handler处理。但这里有个疑问,如果循环到消息就发送出去,那延时消息怎么处理呢,看一下queue.next()方法。

Message next() {
		......
        //  死循环遍历
        for (;;) {
            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 {
                    // 队列中没消息了,继续循环
                    nextPollTimeoutMillis = -1;
                }
                
                // 外部调用quit指令,跳出循环
                if (mQuitting) {
                    dispose();
                    return null;
                }
                ......
            }
        }
    }

针对next的源码都已做了相关注释,就不再赘述,简言之,就是next方法会判断栈顶消息是否到了处理时间,如果到了,就返回该消息,如果没有到,就一直循环,直到栈顶消息时间到,该动作会阻塞整个线程。

好了,既然消息找到了,那就将其发给Handler处理就好,怎么发送的呢。从loop方法中可以发现调用了 msg.target.dispatchMessage(msg)方法,所以处理逻辑应该就在这里面。

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
    	// 如果Message传递了Callback,则在Message的Callback中处理消息
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
        	// 如果Handler定义了Callback回调,则在Callback回调中处理
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 否则,就在handleMessage方法中处理
            handleMessage(msg);
        }
    }

    private static void handleCallback(Message message) {
        message.callback.run();
    }
    
    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

在Handler中其实有三个地方可以处理发送过来的消息,分别是Message中的Callback,Handler中的Callback,以及Handler自己的handleMessage方法。在定义Handler和Message时,默认Callback都为空,这也是为什么定义Handler时一定要重写handleMessage的原因,因为默认都会由Handler的handleMessage去处理Message。

至此,Handler的关键代码都已经讲完了,回到开头提的6个问题,一一解答一下。

一个Handler可以有几个Thread,几个Looper?

Handler内部只有一个Looper对象,而一个Looper唯一对应一个Thread,但一个Thread或者一个Looper可以对应多个Handler,比如在子线程中可以通过new Handler(Looper.getMainLooper)拿到主线程对应的Handler,并通过该Handler与主线程联系。

如何在子线程新建Handler并使用?

在子线程中新建Handler之前要调用Looper.prepare,否则会报"Can’t create handler inside thread that has not called Looper.prepare()";在新建完成后要调用Looper.loop方法,否则消息无法得到处理。

Message最好的创建方式

Message可以如何创建?哪种效果更好,为什么?

主线程中Looper的轮询死循环为何没有阻塞主线程?

Looper的轮询死循环正是主线程一直持续运行的原因,如果该循环停了,说明主线程停了。
主线程中Looper的轮询死循环为何没有阻塞主线程?

Handler的postDelay如何实现的?

在上文中已经解释清楚了,通过enqueueMessage对Message进行排序以及queue.next方法对Message时间校对完成。

ThreadLocal对象

ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是

  1. Synchronized是通过线程等待,牺牲时间来解决访问冲突
  2. ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
  3. 正因为ThreadLocal的线程隔离特性,使他的应用场景相对来说更为特殊一些。在android中Looper、ActivityThread以及AMS中都用到了ThreadLocal。当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。

ThreadLocal源码解析

好了,Handler的整理先到这里,由于水平有限,内容定有不准确甚至不正确之处,望见谅,大佬们能指出来最好了,初学Android,向大家学习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值