Android的消息机制Handler

980 阅读9分钟

Android 通过Binder机制来解决进程间通信问题,而通过Handler 消息机制来解决线程之间通信问题,或者用来切换线程。毕竟Android要求只能主线程更新界面,对于耗时操作和网络请求都需要在其他线程执行,就涉及到线程切换。

Android应用都是靠消息来驱动工作的。大致原理就是:

  • 一个消息队列,往消息队列投递信息。
  • 一个消息循环,不断从消息队列取消息,然后处理。

而这个工作原理就涉及到三个类:LooperMessageQueueHandler

Looper

Looper的创建需要通过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));
}

这里的sThreadLocal对象是ThreadLocal类型,用于存储线程私有全局变量,具体可看ThreadLocal解析。也就是在线程任何地方获取到的Looper对象都是同一个。通过判断语句来看,一个线程中只能调用一次Looper.prepare创建一次Looper对象。通过Looper的构造函数直接创建了Looper对象。

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

Looper的构造函数中创建了一个消息队列MessageQueue的实例mQueue,并持有当前线程对象的引用mThread。也就是说prepare函数创建了Looper对象,且该对象持有一个消息队列。并将Looper对象设置到线程ThreadLocal中,保证每条线程只有一个Looper对象。

有了消息队列,就要让消息队列跑起来,在调用了prepare函数,再调用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(); // might block
        if (msg == null) {
            return;
        }
        ...
        msg.target.dispatchMessage(msg);
        ...
        msg.recycleUnchecked();
    }
}

myLooper函数通过ThreadLocal对象sThreadLocal获取Looper对象,所以prepare函数需要在loop函数之前调用,否则会抛出异常。

然后在for死循环中,不断调用消息队列MessageQueue对象queuenext函数来获取下一个待处理的消息Message对象message。这里的消息队列来自Looper对象,即在创建Looper对象创建的MessageQueue对象。

然后通过Message对象的target对象分发处理该消息。最后对消息进行回收处理。那这里的target对象是谁?也就是Handler对象了。

Handler

Handler可以理解为消息机制里的辅助类,因为此时消息队列有了,消息循环也有,我们可以手动去队列取消息和往队里方向,但这容易出错。所以通过Handler来投递信息和处理信息。

Handler一共7个构造函数,其中两个已经标记过时了。

//设置Looper
public Handler(@NonNull Looper looper) {
    this(looper, null, false);
}
//设置Looper和Callback
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
    this(looper, callback, false);
}
//设置同步屏障
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Handler(boolean async) {
    this(null, async);
}
//设置回调和同步
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;
}

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

总的看下来,构造函数主要设置三个参数Looper对象,Callback对象,布尔类型async。Callback对象主要涉及消息的处理,后面说,而async表示是否设置同步屏障。通过构造函数了解到Handler的消息队列指向了Looper对象的消息队列对象。

那么如何往消息队列投递信息呢?

Handler投递信息有很多重载函数,但主要通过sendMessage函数投递一个Message对象和post函数投递一个Runable对象。

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

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

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

可见对消息Message对象设置延迟时间和同步屏障,并将Handler对象自身设置给了Message对象的target变量,最后调用的消息队列的enqueueMessage队列将消息投递到消息队列中。

public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

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

post函数只是将Runable对象封装到Message对象中的callback函数。最终还是调用sendMessagedDelayed函数的历程去处理。

从这里我们定位到了在Looperloop函数中Message对象的targetHandler对象,看看dispatchMessage函数。

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

dispatchMessage函数可以看出对消息处理的优先级。

  • 消息Message对象msg自带的Runable对象callback。也就是我们通过post函数投递的Runable对象会最先被处理。
  • 优先级排第二就是我们在创建Handler对象时,设置的全局回调Callback对象mCallback
  • 优先级最低的,也是最常用的,重载handleMessage函数,该函数默认是空实现。

MessageQueue

Handler中调用了MessageQueue对象的enqueueMessage函数,将Message对象投递到队列中。

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

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

            if (mQuitting) {
                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) {//队列头
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

enqueueMessage函数主要根据消息处理的时间when,插入到队列合适的位置。

再看一下next函数,如何从消息队里取消息。

Message next() {
    //当应用重启该mPtr会被置null
    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);

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

            //当所有挂起消息被处理了,则返回null
            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);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        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;

        // 重置该时间,这样可以立即处理下一等待的信息
        nextPollTimeoutMillis = 0;
    }
}

next函数中,如果有同步屏障,会优先返回异步信息Looper对象,然后进行处理。如果队列中没有消息,则直接处理已被挂起的消息。

异步消息与同步消息

默认情况下,Handler对象会将mAsynchronous设置为false,会把消息Message对象的asynchronous设置为false,表示异步消息。当Message对象的asynchronousture表示同步消息。而当target==null,表示这是一个同步屏障。

这三者的作用就是为了处理一个优先级的问题,当同步屏障到来时msg.target==null,优先处理队列中的异步信息。

为什么说msg.target==null是一个同步屏障,通过Handler投递出来的信息msg.target都是不为null的。而发送一个同步屏障的代码。

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

可见,发送一个屏障,并没有设置Handler对象给target变量。而异步信息优先权大于同步信息从何体现?

MessageQueuenext函数中有这么段代码。

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

当发送一个同步屏障时,需要手动移除该屏障,否则会一直停留在异步消息的处理中,因为MessageQueuenext函数没有移除同步屏障。同步屏障的一个应用例子就在ViewRootImplescheduleTraversals函数中。

线程切换

Handler的实际应用就是UI线程与其他线程之间的切换。默认情况下,主线程会创建Looper对象,不需要我们手动创建。我们在主线程创建Handler对象时没有传入Looper对象,那默认就使用了主线程的Looper对象。在其他线程投递信息最终也是在主线程处理,达到线程切换的原理。

RxJavaGlide中网络请求,耗时操作的原理也是使用Handler切回到主线程。

也有面试官会问,能否在子线程创建Handler,答案是可以,前提是要先创建Looper对象,并且开启循环,即调用Looper.preare();Looper.loop()

HandlerThread

正常情况下,使用主线程的Looper.myLooper函数获取的Looper对象来创建的Handler一般不会有问题。

但如果在使用子线程的Looper对象呢。如下面代码。

public class LooperThread extends Thread {
    public Looper myLooper;

    @Override
    public void run() {
        Looper.prepare();
        myLooper=Looper.myLooper();
        Looper.loop();
    }
}

val looperThread = LooperThread()
looperThread.start()
Handler(looperThread.myLooper)
handler.sendEmptyMessage(1)

定义了一个具有Looper对象的线程,这样就可以使用LooperThread对象的Looper对象。但上面代码Handler(looperThread.myLooper)有问题,可能此时Looper对象还没初始化。因此需要采用同步机制来处理该问题,不过Android已经帮我们定义了好了HandlerThread来出来该问题了。

run函数的实现

public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

getLooper函数的实现

public Looper getLooper() {
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
                wasInterrupted = true;
            }
        }
    }
    return mLooper;
}

【参考链接】

Handler的学习总结