详细解读Android Handler机制

1、你是否真的了解Handler机制

很多同学如果只看过博客的解析,可能没有完全了解到整个Handler机制中的实现,我们了解Handler不能浮于表面,在面试的过程中,发现很多同学并没有从根本上了解这一机制,此篇文章我们去好好捋一捋,重新认识一下他的实现。

2、Looper

我们写过JAVA知道,他总是由一个 public static void main(String[] args)方法开始的,同样我们通过zygote fork的进程也是由此法开始。按照进程的规则,会伴随一个主线程的创建,而我们在main方法里执行的就是主线程的方法。

 public static void main(String[] args) {
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        Looper.loop();
    }

其中涉及到Looper.prepareMainLooper()和Looper.loop()两个方法,我们看看啊这个looper在干什么,首先看prepare。

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

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

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

prepareMainLooper()分了两步行为,一个是调用prepare方法,实际上及时new 一个Looper对象,并设置到线程的ThreadLocal里。第二步则是设置静态变量sMainLooper为刚才创建的Looper,目的则是可让其他线程也可以调用到sMainLooper,这样我们其实就能看到为啥可以通过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;
        for (;;) { // 代码一
            Message msg = queue.next(); // 代码二
            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;
            }

            msg.recycleUnchecked();
        }
    }

可以看到代码一处开了一个死循环,代码二处是从MessageQueue中取一个message。代码三处则是调用message中的target进行处理。这里引出了两个重要的组件:MessageQueue、Message。

3、Message与Handler

我们一般使用Handler的时候,都会执行两种方法(极其衍生)。一种是

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}
一种是
    public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

那实际上,我们看到他们都会执行到sendMessageDelayed方法。其中一个getPostMessage实现从字面上可以知道就是包装Runnable到一个message里面:

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

也就是Runnable成为了Message的成员变量callback。OK,我们接下来看sendMessageDelayed方法:

  public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        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); // 代码二
    }

代码一处看到sendMessageDelayed实际也会走入到sendMessageAtTime,那他的时间怎么计算的呢SystemClock.uptimeMillis() + delayMillis,其中**SystemClock.uptimeMillis()表示系统真实开机到现在运行的时间,这个跟客观存在的物理时间System.currentTimeMillis()**存在区别,需要注意。
代码二处我们同样看到与MessageQueue有关的调用,也就是
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);
    }

也就是调用的MessageQueue的enqueueMessage逻辑。首先将Handler作为target设置到Message里面,并且根据当前Handler属性设置其是不是支持异步的,如果是则将Message默认都设置为异步的,即msg.setAsynchronous

4、MessageQueue

前两节,我们不管是研究发送Message还是Looper处理Message,我们都走到MessageQueue的逻辑里,我们首先看Handler发送的Message如何处理的吧。

boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        if (mQuitting) {  // 代码一
            msg.recycle();
            return false;
        }

        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();  // 实现一:如果发现当前队头元素是个barrier(target = null的msg),并且当前消息是异步的,才唤醒。
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) { // 实现二:如果当前对重有异步消息,则表示不用唤醒,主要是为了规避实现一处的部分场景(头部是barrier,但是有异步消息在执行)。
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        if (needWake) {   // 代码五
            nativeWake(mPtr);
        }
    }
    return true;
}

代码做了截取,其中包含一些打是否使用标记的逻辑被删掉。我们看代码一的实现,表示判断当前MessageQueue是否退出,如果已经退出,则直接不处理Message,MessageQueue的退出则完全由Looper里面方法调用实现,我们也只有有相应接口,这里不再讨论:

public void quit() {
    this.mQueue.quit(false);
}

public void quitSafely() {
    this.mQueue.quit(true);
}

代码二,设置Message的处理时间,也就是设置到Message.when中,由此可以when这个变量是不对外开放设置的。

代码三,如果当前没有预处理的msg(mMessages表示一个链表的头元素,头元素就表示首个要被处理的元素),或者处理时间when是先于预处理的msg的,则将当前的Msg作为首个处理,并且如果原来线程在休眠,则设置needWake = mBlocked,后面会根据needWake唤醒线程。

代码四:遍历链表(这里链表实现就是一个队列),找到一个Msg的when时间大于当前Msg的when的,并将其插入。由于这种消息插入的时候,一般都有Msg在处理,所以默认情况下不用唤醒线程,除非存在异步消息
(并且当前线程由于barrier被休眠了),这个我们最后再讲。有两处实现不好理解,可以参见代码中标注。

代码五:如果线程需要唤醒了,也就是needWake为true,则唤醒线程。

以上提到的线程就是Looper所在的线程。

我们再看一下next()方法:

Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        int pendingIdleHandlerCount = -1; 
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // 代码一
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
               
                // 代码二
                if (msg != null) {
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    nextPollTimeoutMillis = -1;
                }
                if (mQuitting) {
                    dispose();
                    return null;
                }
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null;
                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            nextPollTimeoutMillis = 0;
        }
    }

代码一,如果发现当前msg为barrier(msg.target == null),则找到第一个异步的消息,也就是异步的队头msg。其中不改变barrier的队列结构,barrier在mMessages也在prevMsg中表示,所以如果有barrier,只有异步消息能进入到下一步处理,不然就会设置nextPollTimeoutMillis为-1,这就会进入到无限的休眠,等待唤醒。

代码二,为实现主体,如果发现当前时间小于msg.when,也就是还没到当前最先要执行msg的时间。此时设置nextPollTimeoutMillis为msg.when-now的时间差,这个用来干嘛的,我们可以回到循环的一开始的代码:

for (;;) {
    if (nextPollTimeoutMillis != 0) {
        Binder.flushPendingCommands();
    }
    nativePollOnce(ptr, nextPollTimeoutMillis);

这个nativePollOnce就是表示让线程休眠nextPollTimeoutMillis时间,目的很简单,因为队头元素还没到时间,那就将线程休眠。这就跟我们前面加入msg的逻辑对应上了。
如果发现当前已经到了msg.when的时间呢?首先置标志位mBlocked = false。然后将本msg从队列中移除,由于还有不处理的prevMsg(也就是barrier),所以,需要把当前msg设置为prevMsg.next:

if (prevMsg != null) {
    prevMsg.next = msg.next;
}

当然如果没有不处理的prevMsg,就是将队头直接设置为msg.next:
mMessages = msg.next;
然后直接返回当前msg,给上层处理。我们再回到第二节中讲到的
msg.target.dispatchMessage(msg)。就可以发现,target其实就是handler,那就相当于执行了handler.dispatchMessage(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();
}

msg.callback是什么,我们上面提到,其实就是post的Runnable。
mCallback是什么,这其实我们在构造handler的时候,可以设置一个默认的callback:

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

最后handleMessage(msg),就是我们很熟悉的,经常去复写用于处理msg的方法了。
所以这三个结构,是这样一步一步的下来,需要记清。

5、Barrier与异步消息

这个接口hide的,不是上层的api接口,但是系统很多知识都有涉及,我们看一下实现:

public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        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;
        }
    }

代码一,就是包装msg,可以看到没有包装msg.target。

代码二,根据when时间,找到mMessages队列中,barrier所在的位置,与前面类似。

从前面我们也看到,异步消息,只是用来和barrier配合使用的,barrier就是用来封堵同步消息的,可以在有barrier的时候,不执行msg。

6、Looper、MessageQueue、Handler对应关系

由于Looper会设置为线程的唯一的ThreadLocal,所以每个线程只能创建一个,MessageQueue则跟随Looper一起创建,所以也是唯一的。那Handler是我们使用的时候才创建,理论上我们可以生成多个。那多个Handler同时使用一个Looper,会造成消息混乱吗?当然不会,因为实现的机制里,我们发现每个msg都会把发送其的handler放到msg.target中,而处理时同样还是取出了msg.target,然后调用了其dispatchMessage方法。所以Handler可以整个过程,保持着谁发送,谁处理的原则。我们简单看一个removeMessage的实现:
Handler的removeMessage方法:

  public final void removeMessages(int what) {
        mQueue.removeMessages(this, what, null);
    }

调用到对应的MessageQueue的removeMessages方法:

void removeMessages(Handler h, int what, Object object) {
        if (h == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;
            // 代码一
            while (p != null && p.target == h && p.what == what
                   && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();
                p = n;
            }
            // 代码二
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (n.target == h && n.what == what
                        && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }

代码一,找到队列中,所有同时满足handler一样(p.target == h),msg.what一样(p.what == what)、obj一样的(object == null || p.obj == object)。直到不满足的为止。

代码二,以此为队列头,往后再次搜索满足条件的msg,并进行移除。这个记录队头的目的,是因为要重新构建队列。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值