Android个人笔记 — Handler常见问题

282 阅读5分钟

个人笔记

Android — Handler常见问题

Handler 是Android所提供的一套消息处理机制,通过HandlerLooperMessageQueueMessage构建出一套完整机制

一.Handler消息处理流程

1.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;
    
   ...
}

可以看到Handler的初始化其实做了两件比较重要的事

  • 通过Looper的myLooper()方法获取了Looper的实例,当Looper实例为空时会抛出异常
  • 从Looper中获取消息队列
    接下来看一下Handler发送消息的常用方法
  • sendMessage(Message msg) 该方法在底层最终会调用 sendMessageAtTime(Message msg, long uptimeMillis)
public boolean sendMessageAtTime(@NonNull 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);
}

在该方法中,调用enqueueMessage()将Message加入MessageQueue

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

该方法中将Handler赋值给msg.target进行绑定,最终将Message加入MessageQueue

  • post(Runnable r) 该方法与sendMessage()方法相同,最终都是调用enqueueMessage()将Message加入到MessageQueue中
    唯一的不同是Handler会通过调用getPostMessage(Runnable r),将Runnable封装成一个Message
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

看完消息发送,接下来看一下Handler如何处理消息
首先Handler会通过调用dispatchMessage()方法来分配消息

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);  // 1
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;  // 2
            }
        }
        handleMessage(msg);  // 3
    }
}

可以看到消息分发时有三种处理方式

  1. msg.callback 在调用post(Runnable r)方法传递消息时,可以看到传入的Runnable会被封装到Message中,则会走逻辑1,此时会直接执行该Runnable
private static void handleCallback(Message message) {
    message.callback.run();
}
  1. mCallback 在Handler初始化时,能够传入一个callback直接重写handleMessage()方法
public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    boolean handleMessage(@NonNull Message msg);
}

并且会赋值给mCallback,此时会进入逻辑2,直接调用重写的handleMessage()来处理消息

  1. handleMessgae() 最后一种,创建一个继承Handler的子类,并重写其中的handleMessage()方法,这时则会进入逻辑3,调用子类的handleMessage()来处理消息

2.Looper 消息流转

看过了Hanlder是如何发送和处理消息的,接下来看一下消息内部流转的具体细节


首先就是在handler初始化时获取的Looper实例
初始化时通过调用Looper.myLooper()获取Looper实例

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

该方法就是取出存入ThreadLocal中的Looper实例

但该方法只是取出Looper实例,那Looper对象的创建具体是在哪呢

可以看到在Handler初始化获取looper对象为空时,会抛出异常,而该异常就是提示如果Looper为空则应先调用Looper.prepare()方法

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对象的创建是在该方法中,并且被存入ThreadLocal中,并且重复调用prepare()方法同样会抛出异常,所以一个线程中只会拥有一个Looper

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

在Looper的初始化中,会创建一个新的MessageQueue对象

可在日常主线程中使用Handler时,其实并没有调用prepare()方法,那为什么没有抛出异常呢 其实在ActivityThread中,即主线程的main()方法中其实已经为我们处理了Looper的初始化

public static void main(String[] args) {
    ...
    
    Looper.prepareMainLooper();  // 1
   
    ...
   
    Looper.loop();  // 2

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

可以看见main()方法中与Looper相关的

  1. 首先是prepareMainLooper()方法,它与prepare()相同,为我们在主线程中创建了一个Looper对象,所以日常在主线程中使用Handler时,并不需要调用Looper.prepare()来初始化Looper
  2. 其次就是Looer.loop()方法
public static void loop() {
    final Looper me = myLooper();
    ...
    final MessageQueue queue = me.mQueue;  // 1

    for (;;) {
        Message msg = queue.next(); // might block  // 2
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        try {
            msg.target.dispatchMessage(msg);  // 3
            ...
        } catch (Exception exception) {
            ...
        } finally {
            ...
        }
        ... 
}

loop()方法是消息处理机制中最重要的部分之一

  1. 首先它会获取Looper中对应的MessageQueue
  2. 其次通过一个死循环从MessageQueue中取出Message。而取出消息是通过MessageQueue.next()该方法有可能会造成阻塞
  3. 在前文Handler部分可以看到在发送消息时,会将对应的Handler实例赋值给Message的target,而这时取到消息后就会调用对应Handler的dispatchMessage()方法来分发消息,走到Handler消息处理的部分

这样就是一个完整的消息流转处理的一个过程

handler.sendMessage() -> messageQueue.enqueueMessage() -> looper.loop() -> queue.next() -> msg.target.dispatchMessage() -> handler.handleMessage()

3.MessageQueue 添加、取出消息

上述提到Handler发送消息最终会将Message加入到MessageQueue中,因此我们来具体看一下MessageQueue.enqueueMessage()方法

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {  // 1
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        if (msg.isInUse()) {  // 2
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        if (mQuitting) {  // 3
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {  // 4.1
            // 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.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) { // 4.2
                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;
}
  1. 可以看到能够加入到MessageQueue中的Message必须与一个对应的traget绑定,即与一个对应的Handler绑定
  2. 当前的Message如果正在被使用,则也不会加入到queue中
  3. 若当前线程已经退出,则Message同样也不会加入到queue中
  4. 加入MessageQueue中有两种情况
    • 消息队列中没有消息或该消息是一个即时的消息,则此时的消息将会插入到队列头部,并且如果此时Looper中的线程被阻塞,同时还会唤醒该线程
    • 若此时消息是一个延时消息,则会通过比较延时的时间循环加入到消息队列中,获得一个有序的延时消息链表

Looper.loop()方法中,最终取出消息是通过MessageQueue.next()方法

Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis); // 1

        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) {  // 2
                    // 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 { // 3
                    // 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 {  // 4
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            } 
            ...
        }
        ...
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}
  1. 可以看到nativePollOnce()是一个阻塞方法,主要是通过native层的epoll监听文件描述符的写入事件来实现的
  • 如果nextPollTimeoutMillis=-1,一直阻塞不会超时
  • 如果nextPollTimeoutMillis=0,不会阻塞,立即返回
  • 如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回
  1. 此时如果还没到Message的发送时间,则会将nextPollTimeoutMillis设置为消息发送需要等待时间,并在下一次循环时nativePollOnce()来阻塞线程
  2. 正常取出消息,将mBlock设置为false,表示现在线程没有被阻塞,并将message标记为正在使用中
  3. 当没有消息时,将nextPollTimeoutMillis设置为-1,来阻塞线程