핸들러 원리 분석

머리말

핸들러는 안드로이드 시스템 전체에서 매우 중요한 부분이고 그 원리를 익혀야 하기 때문에 흔하다고 할 수 있습니다. 이 글이 가져온 공유는 핸들러의 원리에 대한 종합적인 분석으로 일상의 발전과 인터뷰에 큰 도움이 될 것이라고 생각합니다.

텍스트

Handler 원리를 설명하기 전에 전체 과정을 그림으로 살펴보자. 여기에 이미지 설명 삽입물론 다음에 이야기할 Handler 원리는 Handler 자체뿐만 아니라 MessageQueue, Looper 등의 클래스도 포함한다.

핸들러의 역할

일상적인 개발에서 우리는 종종 무언가를 하기 위해 스레드를 전환해야 합니다. 예를 들어 작업자 스레드가 계산을 완료한 후 UI를 업데이트하기 위해 메인 스레드로 전환해야 합니다. 그런 다음 이때 Handler를 사용하여 스레드를 전환할 수 있습니다. 예를 들어 지연된 작업을 수행하고 싶거나 Handler를 사용하여 지연된 작업을 시작하는 등의 작업을 수행할 수도 있습니다.

매니저

만들다:

Handler의 생성자는 마지막으로 다음 생성자를 호출합니다.

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

생성자는 Looper 멤버를 초기화하고 Looper를 확인하는데, 초기화되지 않은 경우 예외가 발생하므로 Handler를 생성하기 전에 현재 스레드의 Looper가 생성되었는지 확인해야 합니다. 생성되면 현재 쓰레드에 바인딩되어 저장되는데 어떻게 생성해야 하는지는 추후에 다루도록 하겠습니다.

그러나 메인 스레드에서 우리는 루퍼를 생성할 필요가 없습니다. ActivityThread의 메인 메소드에서 시스템이 이미 우리를 위해 생성했기 때문입니다:

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

    Looper.loop();

}
复制代码

Looper.prepareMainLooper()는 Looper를 생성하고 이 Looper는 메인 스레드에 있습니다.

메세지를 보내다:

메시지를 보내기 전에 메시지를 생성해야 하는데, 메시지는 전체 프로세스의 전달자라고 할 수 있으며 몇 가지 정보를 담고 있습니다. 다음과 같이 생성됩니다.

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

메시지 전송이 끝나면 enqueueMessage 메서드를 호출하는 sendMessageAtTime 메서드가 호출됩니다.

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

주로 다음 세 가지를 수행합니다.

  • msg의 대상을 이것으로 설정하면 메시지를 배포할 때 사용되며 나중에 내용을 분석합니다.
  • 현재 Handler가 비동기를 지원하는 경우 msg는 비동기 장벽 사용을 위한 비동기 메시지로 설정되며 나중에 분석됩니다.
  • 调用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;
    }
}
复制代码

즉, MessageQueue의 다음 메소드에서는 현재 배포할 Message가 없으면 mIdleHandlers에서 IdleHandler를 호출하고, 호출 후 반환된 결과에 따라 목록에서 제거 여부를 판단한다.

발문

위의 내용은 Handler의 원리에 대한 설명입니다. 내용이 많습니다. Handler의 원리에 대한 비교적 포괄적인 분석입니다. 위의 분석을 마스터하면 기본적으로 95% 이상의 내용을 이해했다고 말할 수 있습니다. 핸들러의 원리.

Nuggets Technology Community의 작성자 서명 프로그램 모집에 참여하고 있습니다. 링크를 클릭하여 등록하고 제출 하십시오.

Supongo que te gusta

Origin juejin.im/post/7119667750804389902
Recomendado
Clasificación