Handler principle analysis

foreword

Handler can be said to be a commonplace, because it is a very important part of the entire Android system, and it is necessary to learn its principles. The sharing brought by this article is a comprehensive analysis of the principle of Handler. I believe it will be of great help to daily development and interviews. Let's take a look at its principle.

text

Before explaining the Handler principle, let's take a look at its entire process through a picture: insert image description hereOf course, the Handler principle we will talk about next is not only the Handler itself, but also classes such as MessageQueue and Looper.

The role of Handler

In daily development, we often need to switch threads to do something. For example, after the worker thread has completed the calculation, we need to switch to the main thread to update the ui. Then we can use Handler to switch threads at this time, and for example We want to do a delayed task, we can also use Handler to start a delayed task and so on.

Handler

create:

The constructor of Handler will finally call the following constructor:

public Handler(@Nullable Callback callback, boolean async) {

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
复制代码

The constructor initializes the Looper member and checks the Looper. If it has not been initialized before, an exception will be thrown. Therefore, before creating a Handler, you need to ensure that the Looper of the current thread has been created. After the Looper is created, it will be bound to the current thread and stored. As for how it has to be created, we will talk about it later.

But in the main thread, we don't need to create a Looper, because in the main method of ActivityThread, the system has already created it for us:

//只保留核心代码
public static void main(String[] args) {
    Looper.prepareMainLooper();

    Looper.loop();

}
复制代码

Looper.prepareMainLooper() will create a Looper, and this Looper is in the main thread.

Send a message:

Before sending a message, you need to create a Message, which can be said to be the carrier of the entire process, which carries some information. It is created like this:

new Message()
Message.obtain()
handler.obtainMessage()
//直接new跟obtain有什么区别呢?obtain的话,会优先到池子里拿,池子没有到话再创建一个,所以我们一般优先考虑obtain的方式去获取Message。

//发送消息
handler.sendMessage(msg);
复制代码

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    // 省略校验
    return enqueueMessage(queue, msg, uptimeMillis);
}
复制代码

At the end of sending a message, the sendMessageAtTime method is called, which calls the enqueueMessage method:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码

Mainly do three things:

  • Set the target of msg to this, which will be used when the message is distributed, and the content will be analyzed later.
  • If the current Handler supports asynchronous, then msg will be set as an asynchronous message for asynchronous barrier use, which will be analyzed later
  • 调用MessageQueue的enqueueMessage方法。

下面我们一起看看MessageQueue

处理消息:

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
复制代码

从上面的代码我们可以看到分发的顺序是:

  • 如果Message有设置callback,则使用messaeg的callback进行回调,其实我们平时使用handler.post()/postDelay(),都会使用将Runnable赋值给Message的callback。
  • 如果Handler有设置callback,则会先回调Handler的callback。
  • 最后是Handler的handleMessage,这也是我们常见的一个方法,初始化Handler的时候去复写这个方法,并在里边进行消息的处理。

MessageQueue

内部时一个链表结构,用于存放Message和提供Message

插入消息:

上面讲到的enqueueMessage方法,就是用来存放Message的:

//已精简了部分代码
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) {
            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) {
          //唤醒阻塞。(next方法里)
            nativeWake(mPtr);
        }
    }
    return true;
}
复制代码
  • 根据消息的时间寻找合适地方进行插入
  • 插入后尝试唤醒操作,因为在下边的取消息时可能会进入阻塞,调用唤醒的前提是这个消息不是同步屏障。

既然我们MessageQueue提供了存消息的方法,同时也提供了取消息的方法,具体如下:

取出消息:

Message next() {
   //...
    int nextPollTimeoutMillis = 0;
    for (;;) {
        //...
      //尝试阻塞
        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;
            }
    }
}
复制代码

next方法会进入一个循环,然后分下列几步走:

  • 如果没有Message的话,会进入阻塞。如果拿到Message,会先判断是否为同步屏障(msg.target == null),如果是的话,则会去取后面的异步Message。同步屏障后面会进行分析。
  • 拿到Message后,先判断当前消息是否已经到达了分发的时间,则将它从链表中移除并且返回,否则的话会计算分发当前消息需要等多久,然后进行阻塞,阻塞时长为计算出来的时长。
  • 如果没有Message,则会进入阻塞,阻塞没有时长,会一直阻塞。等到有新的消息插入的话,才会通知打断阻塞。

上面便是Message的两个核心方法存和取,那么又是哪个角色会从MessageQueue中进行去消息呢?下面介绍重要的类:Looper。它将负责取消息并进行分发。

Looper

创建:

Looper的创建需要调用Looper.prepare()方法,最后会调用prepare(quitAllowed)方法:

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));
}
复制代码

创建后,便放到了ThreadLocal里边,ThreadLocal可以保证将Looper与线程绑定起来。在创建之前,还会进行校验是否本线程是否已经创建过了。在这里我们顺便看一看Looper的构造函数:

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

它的构造函数是私有的,也就是说外面无法直接new出来,并且构造函数里边会初始化MessageQueue。

启动:

上面我们讲过handler发送消息,然后插入到了MessageQueue,那么什么时候才会去处理放到队列里边的消息呢,这时Looper的一个工作,创建后,调用Looper.loop()方法,就会进入一个无限循环,不断去尝试获取Message:

//精简了部分代码
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }

    me.mInLoop = true;
    final MessageQueue queue = me.mQueue;

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            return;
        }
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
      
        try {
            msg.target.dispatchMessage(msg);
        } catch (Exception exception) {
            throw exception;
        }
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        msg.recycleUnchecked();
    }
}
复制代码

loop方法主要做三件事:

  • 开启一起死循环
  • 不断地从MessageQueue里边拿Message
  • 拿到Message后,通过msg.target进行分发,其实target就是Handler,所以这里就能够将消息分发到Handler

上面我们提过,主线程中系统在一开始就启动Looper了,所以我们无需手动启动。

源码里的logging可以通过Looper的setMessageLogging进行设置。在这里我们同时也可以监控每个任务的耗时,通过对比logging的println方法参数值“>>>”和“<<<”进行配对。

回到Handler,看看它是如何进行消息的分发:

上面便是Message发送、取出、分发的整个过程,下面我们继续讲讲两个知识点:同步屏障和IdleHandler。

同步屏障

除了普通的Message以外,还有另外两种特殊的Message:屏障消息与异步消息,屏障消息则是为异步消息服务的。下面对他们进行详细的讲解。

同步屏障的作用:

屏障消息与普通消息的区别是target为null,它的作用就像是开了一绿色通道,如果开启同步屏障,那么发送异步消息后,都会得到优先处理,之前发送到普通消息则不会得到处理,得等到同步屏障的移除。

开启同步屏障:

调用MessageQueue的postSyncBarrier方法,则可以发送一个同步屏障消息,并且会返回对于的token,当我们关闭同步屏障的时候,则使用这个token去关闭:

@UnsupportedAppUsage
@TestApi
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) {
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}
复制代码

可以看到,开启同步屏障是往消息链表中插入一个target为null的Message,并且返回一个token,但是我们平时在开发中,没法直接去调用,如果要使用的话,则可以使用反射进行调用。

移除同步屏障:

@UnsupportedAppUsage
@TestApi
public void removeSyncBarrier(int token) {
    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 (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}
复制代码

通过token,找到屏障消息,然后将其从队列中移除掉,并且看看是否在此屏障消息后面还有消息,有的话需要唤醒操作。

那么屏障消息在哪里起作用呢?其实我们在前面的MessageQueue的next方法已经提过了:

if (msg != null && msg.target == null) {
     //同步屏障,拿到异步消息
     do {
          prevMsg = msg;
          msg = msg.next;
      } while (msg != null && !msg.isAsynchronous());
}
复制代码

如果遇到同步屏障消息,则会去获取异步消息,然后进行分发处理。

哪里有用到呢?

在ViewRootImpl中,scheduleTraversals方法。

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
      //里边发送的是一条异步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}
复制代码

也就是每次去调度刷新ui的时候,便会开启同步屏障,这也可以理解,因为是ui的刷新,所以优先级是比较高的。

IdleHandler

IdleHandler可以用在哪里呢?其实看它名字大概可以猜出它是用来干嘛的,主要是用来在空闲(当前没有Message分发)的时候进行调用。也就是我们可以使用IdleHandler来执行一些不重要或者优先级比较低的任务,让当前消息队列空闲时再去执行,避免影响其他重要的任务执行。

public static interface IdleHandler {
    boolean queueIdle();
}
复制代码

使用idleHandler可以如下:

looper.getQueue().addIdleHandler(new IdleHandler() {
            public boolean queueIdle() {
                //做一些事
                return false;
            }
});
复制代码

其中queueIdle返回的值表示是否继续保留着,等到下次空闲的时候再次调用。返回false表示用完即移除。

idleHandler在什么时候调用呢?

Message next() {
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    for (;;) {
      
            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);
        }
        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);
                }
            }
        }
        pendingIdleHandlerCount = 0;
    }
}
复制代码

That is, in the next method of MessageQueue, if there is currently no Message to be distributed, then IdleHandler will be called from mIdleHandlers. After the call, it will be judged whether to remove it from the list according to the returned result.

Epilogue

The above is the explanation of the principle of Handler. There are many contents. It is a relatively comprehensive analysis of the principle of Handler. If you master the above analysis, you can say that you have basically understood at least 95% of the principle of Handler.

I am participating in the recruitment of the creator signing program of the Nuggets Technology Community, click the link to register and submit .

おすすめ

転載: juejin.im/post/7119667750804389902