Handler 源码解析

前言

为什么要分析 Handler 源码,经历过Android 面试的都知道Handler 是面试必问的。但是不仅如此Handler也是其他组件不可或缺组件,如HandlerThread、 IntentService、以及Glide 生命周期的控制也有他的身影。

所以总而言之,言而总之 Handler实在是太重要了!!! 学习完这篇文章之后可以看下 Handler 应用

Handler的运行机制

面试的时候常问的一个问题,也是我们对Handler 原理不是很理解的时候经常遇到的问题:我们在主线程里面直接 new Handler()之后可以直接使用它发送和处理事件,而在子线程缺不可以。

首先我们了解下Handler的工作流程

Handler的工作流程

通过上图我们可以总结出

  • Looper 依赖于Thread,我们需要在线程中创建Looper。 Looper.prepare
  • Looper需要在线程中运转,所以他得调用Looper.loop()

那么回到上面那个问题,为什么主线程可以直接通过new Handler()就能使用,而子线程却不行,接下来我们看下代码:

//ActivityThread.java
public static void main(String[] args) {
    //省略代码...

    Looper.prepareMainLooper();

    //省略代码...
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    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");
}
复制代码
public static void loop() {
    //省略代码...
    final MessageQueue queue = me.mQueue;

    //省略代码...

  	//死循环
    for (;;) {
      	// 获取出队的msg
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
				//省略代码...
      	//message 分发给对应的 handler 处理
        msg.target.dispatchMessage(msg);
      
				//省略代码...
      
      	//回收复用
      	msg.recycleUnchecked();
    }
}
复制代码
  • 一个app进程启动,需要zygote fork一个进程出来,而他执行的代码入口就是ActivityThread.main
  • 进程所执行的第一个线程就是我们的主进程。所以Looper.prepareMainLooper()创建了我们主线程的Looper
  • Looper.loop()启动了线程循环,也就是说我们后续所有在主线程运行的代码都是通过handler 处理的(这个很重要),如果主线程的Looper 结束循环,那么我们整个进程也就结束了

接着我们分别看下Looper.prepareMainLooper()方法以及 Handler的构造函数做了什么

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
  	//1.创建Looper 存入到ThreadLocal中
    sThreadLocal.set(new Looper(quitAllowed));
}

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
      	// 不能重复调用,否则抛异常,所以这个方法只能调一次
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
      	//2.从ThreadLocal中获取我们的主线程Looper
        sMainLooper = myLooper();
    }
}

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

//其他子线程可以调用
public static void prepare() {
    prepare(true);
}
复制代码
public Handler() {
    this(null, false);
}

public Handler(Callback callback, boolean async) {
    //省略代码...
  	//3.获取当前线程的Looper
    mLooper = Looper.myLooper();
    //省略代码...
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
复制代码

上面代码中3步:

  1. 创建主线程Looper 存入到ThreadLocal
  2. Looper保存了一个静态的 主线程Looper
  3. Handler 构造函数里会获取当前线程的Looper

分析到这里大家应该也就明白,为什么子线程不能像在主线程那样直接new Handler()了,那么我如何在子线程正常使用Handler呢?

  • 我们可以通过new Handler(Looper.getMainLooper())来使用主线程的Looper来创建Handler
  • 在子线程中执行Looper相关的必要方法,Looper.prepareLooper.loop不过这个会有个问题?最好的替代方案--使用HandlerThread。 这个问题我们先留着,我会在 Handler 其他应用文章中介绍 为什么使用HandlerThread

消息的传递(线程间通信)

Handler的核心就是为了线程间的通信,但是我们要知道一个线程一次只能处理一个,所以在并发的情况下就需要消息队列来保存的。

那么问题来了消息的处理又是按照遵循什么规则呢?先进先出?所以带着这个疑问我们进行代码的分析

消息的入队

消息的发送Handler主要提供了上图所示的方法,最终会执行MessageQueue.enqueueMessage

boolean enqueueMessage(Message msg, long when) {
    //省略代码...

    synchronized (this) {
        //省略代码...

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
      	//1.p=null 表示队列为空 2.when 执行时间 如果为0或者比之前第一个message执行时间早,表示越早执行
      	//所以这两种情况都会将消息插入到链表头
        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.
          	//如果存在阻塞、第一个消息是同步阻塞消息,并且当前消息是异步消息,先将唤醒标志为标记为true
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
          	//3.插入算法,循环遍历直到找到插入的前一个 message prev 和后一个message p
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
              	//如果P是异步消息,当前消息(即使是异步消息)执行时间在这个后面,将唤醒标志为标记为false
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // 4.如果需要唤醒 执行条件必须是满足1或2,并且当mBlocked = true时,mBlocked什么时候会为true 消息的出队会讲解
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
复制代码
  • MessageQueue内部消息的存储是通过链表的方式,按照时间的优先执行顺序排列
  • MessageQueue并没有对队列做限制,所以有多少消息度可以入队,无限存入会有内存溢出的风险。
    • 那为我们可能会有疑问,为什么不用阻塞队列呢?如果队列满了,就不让他入队,大家都可以思考下这个问题
      • 我们要知道主线程的Handler不仅仅我们在用,视图的刷新、Activity的启动等都是用到Handler的消息机制的。如果我们用阻塞队列,队列满了之后,系统消息也就进不去,那整个进程不像挂了吗
      • 所以Handler 消息传递不能婪用
消息的出队

我们知道MessageQueue在获取消息的时候会有阻塞的情况发生,阻塞主要分两种情况

  • message 不到时间,自动唤醒 代码关键步骤执行流程 a1 -> b -> c
  • messageQueue为空,无限等待 代码关键步骤执行流程 a2 -> b -> c
  • 关于唤醒,我们可以看入队 代码的第4步

正常的流程就是将可执行的Menssage 直接返回

Message next() {
    //省略代码...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
      
        //省略代码...
				//c.执行阻塞,
      	//当nextPollTimeoutMillis = -1时无限期阻塞,只有通过新消息入队时唤醒
      	//当nextPollTimeoutMillis > 0 时,阻塞nextPollTimeoutMillis对应的时间自然唤醒,或者新消息执行时间早于当前最新消息的时间时,入队就会执行唤醒代码
        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.
                  	//a1. 下个消息还未准备就绪,距离执行还需多少时间
                    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.
              	//a2.没有消息 nextPollTimeoutMillis 赋值 -1
                nextPollTimeoutMillis = -1;
            }

            //省略代码...

            // 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.
          	//第一次执行for 循环的时候,如果Handler没有消息要执行,则会,获取idle handlers 去执行
          	//只会执行一次下面的代码
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
          	//当没有mIdleHandlers 需要执行 
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
              	//b. 标记阻塞
                mBlocked = true;
                continue;
            }

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

        
        //省略代码...

        // 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;
    }
}
复制代码
内存复用

内存复用的功能Message已经提供了,但是我们还是要思考下为什么需要内存复用?

我们试想下,如果我们不复用Message,我们就需要无限的new 一个新的Message,然后内存不足以new 一个新的Message,这时候就会回收再创建,回收即“stop the word”。这样就会产生内存的抖动,性能也因此受到了影响,

所以内存的复用他不仅能够避免内存抖动,也是性能优化的策略。类似Glide里面Bitmap复用也是基于这个原因设计内存复用。

内存复用采用享元的设计模式,接下来我们看下是如何回收、如何复用的

// Message链表 有next指向 下一个 可复用的Message
private static Message sPool;

// 在loop循环里当处理好message分发之后,会调用message.recycleUnchecked 进行回收
void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
          	//将message插入到链表前面
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

// 复用Message
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();
}
复制代码

注意:创建Message对象的时候尽量使用obtain

同步屏障

同步屏障的概念,在Android中开发人员非常容易忽略,因为这个概念在我们普通的开发过程中太少见了,很容易被忽略。

大家经过上面的学习应该知道,线程的消息都是放到一个MessageQueue里面,取消息的时候是互斥取消息,而且只能从头部取消息,而添加的消息是按照消息的执行的先后顺序进行排序的,那么问题来了,同一个时间范围内的消息,如果它需要立即执行,那我们怎么办,我们需要等到队列轮询到我自己的时候才能执行,那岂不是黄花菜都凉了。所以我们需要给紧急需要执行的消息一个绿色通道,这个绿色通道就是同步屏障的概念。

同步屏障是什么?

屏障的意思即为阻碍,同步屏障就是阻碍同步消息,只让异步消息通过

开启同步屏障
public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}
复制代码
取消同步屏障
public void removeSyncBarrier(int token) {
    // Remove a sync barrier token from the queue.
    // If the queue is no longer stalled by a barrier then wake it.
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        final boolean needWake;
        if (prev != null) {
            prev.next = p.next;
          	//说明同步屏障未执行,所以不需要唤醒,按正常逻辑执行
            needWake = false;
        } else {
            mMessages = p.next;
          	//说明同步屏障已执行,并且下一个也不是同步屏障,所以需要唤醒,按正常逻辑执行
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked();

        // If the loop is quitting then it is already awake.
        // We can assume mPtr != 0 when mQuitting is false.
        if (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}
复制代码
异步消息的场景

在日常的开发过程中,很少会用到同步阻塞。这个基本上在Android的更新UI中用的比较多,优先级较高,需要优先处理。

例如,在View更新时,draw,requestLayout、invalidate等很多地方调用ViewRootImpl#scheduleTraversals

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
      	//开启同步阻塞
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
      	//发送异步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}
复制代码

mChoreographer.postCallback最终会执行Choreographer.postCallbackDelayedInternal

private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    //省略代码...

    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);//发送异步消息
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}
复制代码

移除同步阻塞

void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        mChoreographer.removeCallbacks(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}
复制代码
注意

同步屏障消息最好应用在短延时的消息 毫秒级的。因为我们同步屏障之后会让异步消息优先执行,他可能有延时,所以我们会阻塞线程,如果这个延时过长会产生时间上的浪费。比方说我延时了1s,但是在这1s内MessageQueue队列中排在异步消息之前的消息完全有时间消费掉,那么我们这个同步屏障就没有执行的意义了,还造成了时间层面的浪费。所以再次强调同步屏障消息最好应用在短延时的消息 毫秒级的

总结

Handler的作用

  • 使得Android开发难度大大降低
  • 几乎看不到多线程死锁问题
  • 方便了线程间的通信
分类:
Android
标签: