欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android开发学习笔记——Android的Handler消息机制

程序员文章站 2022-05-16 14:42:26
...


在Android开发中,我们知道,Android存在线程安全的问题,在子线程中是无法进行UI更新的,否则就会出现异常。然后,在实际的开发过程中,由于网络请求、IO操作等耗时操作无法在主线程进行,否则很容易阻塞主线程,从而造成ANR错误,所以异步的多线程操作是无法避免的,而异步操作中难免需要更新UI,所以异步更新UI就成为了一个问题,而Android的异步消息传递机制就能够有效解决该问题。Android的消息传递机制,主要就是指Handler的消息传递机制,Handler能够将一个任务切换到Handler所在的线程中去执行,也就是说,在异步操作时,通过Handler传递消息,我们能够将UI更新切换到主线程中执行,这样就避免了线程安全的问题。

Handler的基本使用

AndroidUI线程安全问题

在Android中,由于UI控件不是线程安全的,如果在多线程中访问就很可能出现不可预期的状态,因此Android系统只允许在主线中进行UI操作,否则就会抛出异常信息。我们可以根据ViewRootImpl的源码得知这一点,ViewRootImpl对UI操作进行了验证,如果当前线程不为主线程则抛出异常,如下所示:

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

我们可以尝试在子线程中更新UI,如下所示:

btTest.setOnClickListener {
    Thread(Runnable {
        tvTest.text = "test thread"
    }
    ).start()
}

点击按钮,我们程序就会抛出异常而发生闪退,如下所示:
Android开发学习笔记——Android的Handler消息机制

Handler的基本使用

Handler的主要作用就是将一个任务切换到某个指定的线程中去执行,通过handler,我们能够将异步线程切换到主线程去更新ui。Handler用于主线程中进行UI更新时使用非常简单,只要创建一个handler实例并实现其handleMessage方法在其中进行UI操作,然后在子线程中通过post或者send方法传递异步消息即可。如下:

private val handler = object : Handler() {
    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
        tvTest.text = "test handler"
    }
}

创建handler实例后,我们就可以通过send方法来传递异步消息,从而更新UI,如下所示:

btTest.setOnClickListener {
    Thread(Runnable {
        //传递异步消息,通知更新UI
        handler.sendEmptyMessage(1)
        //通过sendMessage可以传递更多信息
//                handler.sendMessage(Message())
    }
    ).start()
}

或者,我们可以直接通过post方法更新UI,如下所示:

btTest.setOnClickListener {
    Thread(Runnable {
        //直接使用post方法更新UI
        handler.post {
            tvTest.text = "test post"
        }
    }
    ).start()
}

无论哪种方法,我们都可以通过Handler切换到主线程进行UI操作,从而避免在子线程中更新UI。

在子线程中使用Handler机制

Handler机制不仅仅是可以用来进行UI更新的,其实际作用还是实现多线程中的异步消息传递,切换不同线程;所以Handler不仅仅是能够在主线程中使用,还能够在子线程中使用,使用方法和在主线程中使用相同,不同的是,在子线程中使用Handler,我们需要使用到Looper,如下所示:

Thread(Runnable {
    Looper.prepare()
    val handler = object : Handler() {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            tvTest.text = "test handler"
        }
    }
    Looper.loop()
}
).start()

与在主线程中使用不同,在子线程中我们必须先调用Looper.prepare()方法去创建一个Looper,否则无法实例化handler对象并且会抛出异常,如下图:
Android开发学习笔记——Android的Handler消息机制
除此之外,我们还需要调用Looper.loop()来开启消息循环,然后我们就可以使用handler对象来进行消息传递了。那么为什么在子线程中需要调用Looper.prepare()和Looper.loop()而主线程却不需要呢?这就要了解Handler机制的相关原理了。

Handler机制原理

在Handler消息传递机制中,主要涉及到以下几个类:Handler(消息处理者)、Looper(消息分发者)、MessageQueue(消息队列)以及Message(消息对象),其中Handler、Looper和MessageQueue尤为重要。Handler机制的工作流程我们可以大致做一下概述:每个线程中可以创建一个Looper对象作为循环器,当Handler的send和post方法被调用时,便会将消息存入MessageQueue消息队列中,而Looper的loop方法可开启一个死循环,不断去获取MessageQueue消息队列中的Message消息,并处理这个消息,最后交由Handler去处理。接下来,我们可以分别分析Handler、Looper和MessageQueue的原理。

MessageQueue

MessageQueue是一个消息队列,在Handler机制中拥有存储消息,其注意包含两个操作,插入消息和获取消息分别对应着两个方法enqueueMessage和next。需要注意的是,虽然MessageQueue叫消息队列,但是它的内部实现并不是用队列实现的,而是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上更有优势。首先,我们看enqueueMessage的实现,如下所示:

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

我们可以看到,其主要操作实际上就是一个单链表的插入操作,使用了一个mMessages对象表示当前待处理的消息即链表头部,其它的消息都按照时间来排序。然后,我们来看看next方法的实现,MessageQueue是如何获取消息的,如下所示:

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

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

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                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);
            }

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

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

可以发现,next方法就是一个无限循环的方法,如果消息队列中没有消息,那么next方法就会一直阻塞在这里,当有新消息到来时,next方法就会返回这条消息并将其从单链表中移除。

Looper

我们首先从Looper开始分析,Looper在Handler机制中主要承担着一个消息循环的角色,其会不断从MessageQueue中查看是否存在新消息并对消息进行处理,否则就会一直阻塞。对于Looper类主要需要我们去分析其prepare()和loop()方法。不过,首先我们可以看下其构造方法,如下:

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

首先,我们可以看到,Looper只有一个构造方法,而且是一个私有构造方法,也就是说,我们无法通过new来实例化一个Looper对象。其次,我们可以看到,在其构造方法中,Looper会构建一个MessageQueue消息队列,并将当前线程对象保存起来。从上一节的描述中我们知道,Handler的工作需要Looper,如果当前线程中没有Looper对象就会报错,那么既然Looper对象无法通过new来构造,那么应该怎么为当前线程创建一个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));
}

我们可以看到,prepare()方法最后调用了prepare(boolean quitAllowed)方法,在该方法中创建了Looper对象,那么sThreadLocal是个什么东西呢?ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获得数据。从Looper代码中,我们可以得知sThreadLocal其泛型指定为Looper,也就是说sThreadLocal中实际存储为当前线程Looper对象,我们从prepare(boolean quitAllowed)方法看到,当sThreadLocal.get()不为空时,会抛出异常,提示每个线程只能有一个Looper,如果为空就会创建Looper对象并使用set方法进行存储。也就是说,在每个线程中,只能拥有一个Looper,只能调用一次prepare()方法。

在创建了Looper对象后,我们就可以使用loop方法开启消息循环了,也只有调用loop()方法之后,消息循环系统才会真正起作用,如下所示:

public static void loop() {
    final Looper me = myLooper();//获取当前线程Looper对象
    if (me == null) {//如果为空抛出异常
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    if (me.mInLoop) {
        Slog.w(TAG, "Loop again would have the queued messages be executed"
                + " before this one completed.");
    }

    me.mInLoop = true;
    final MessageQueue queue = me.mQueue;//获取当前looper的消息队列

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    // Allow overriding a threshold with a system prop. e.g.
    // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
    final int thresholdOverride =
            SystemProperties.getInt("log.looper."
                    + Process.myUid() + "."
                    + Thread.currentThread().getName()
                    + ".slow", 0);

    boolean slowDeliveryDetected = false;
	//开启消息循环
    for (;;) {
        Message msg = queue.next(); // might block 从消息队列中取出消息,next为阻塞操作
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;//退出循环
        }

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        // Make sure the observer won't change while processing a transaction.
        final Observer observer = sObserver;

        final long traceTag = me.mTraceTag;
        long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
        long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
        if (thresholdOverride > 0) {
            slowDispatchThresholdMs = thresholdOverride;
            slowDeliveryThresholdMs = thresholdOverride;
        }
        final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
        final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

        final boolean needStartTime = logSlowDelivery || logSlowDispatch;
        final boolean needEndTime = logSlowDispatch;

        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }

        final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
        final long dispatchEnd;
        Object token = null;
        if (observer != null) {
            token = observer.messageDispatchStarting();
        }
        long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
        try {
        	// 将真正的处理工作交给message的target(handler)处理分发消息
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        if (logSlowDelivery) {
            if (slowDeliveryDetected) {
                if ((dispatchStart - msg.when) <= 10) {
                    Slog.w(TAG, "Drained");
                    slowDeliveryDetected = false;
                }
            } else {
                if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                        msg)) {
                    // Once we write a slow delivery log, suppress until the queue drains.
                    slowDeliveryDetected = true;
                }
            }
        }
        if (logSlowDispatch) {
            showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
        }

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }
}

Looper的loop方法代码较多,但是其工作流程很容易理解,首先通过myLooper()方法获取到当前线程的looper对象,如果looper对象为空则抛出异常,否则就获取当前looper的消息队列,然后就开启消息循环,进入一个死循环中,只有当获取到的message为空时才会跳出循环。MessageQueue的next方法是一个阻塞方法,进入消息循环后,looper会一直调用next方法获取消息,如果没有新消息就会一直阻塞下去,如果获取到新消息就会对其进行处理,而其中最重要的一行代码就是:msg.target.dispatchMessage(msg);通过源码,我们可以看到Message的target就是一个Handler对象,也就是说Looper最终将取到的消息通过diapatchMessage方法交由Handler处理。

Handler

Handler作为Android消息机制中的主要对象,其主要作用为发送消息并且接收处理消息。同样,我们首先来看Handler的构造方法,如下所示:

public Handler(@Nullable Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }
	//关联当前线程的Looper对象
    mLooper = Looper.myLooper();
    if (mLooper == null) {//如果looper对象为空,抛出异常
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    //关联当前looper对象的消息队列
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

通过源码,我们发现Handler的构造方法最后都会调用上述方法,从上述方法中我们可以看到Handler会根据Looper.myLooper()方法获取到当前线程的Looper对象并建立关联,当looper对象为空时即当前线程中未调用prepare()方法创建Looper对象时,就会抛出异常提示当前线程未调用Looper.prepare(),这也就解释了为什么之前我们在子线程中创建Handler时,未调用Looper.prepare()会报错。然后,需要注意的是,Handler在创建对象时直接将当前Looper对象的消息队列作为自己的消息队列,因此loop方法可以获取到Handler对象发送的消息。
这时,我们可能会有疑问,为什么在主线程中可以直接创建Handler对象而不需要调用prepare方法呢?其实,这是由于在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。查看ActivityThread中的main()方法,代码如下所示:

public static void main(String[] args) {
    SamplingProfilerIntegration.start();
    CloseGuard.setEnabled(false);
    Environment.initForCurrentUser();
    EventLogger.setReporter(new EventLoggingReporter());
    Process.setArgV0("<pre-initialized>");
    //等同于prepare方法,不过其线程为主线程
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    AsyncTask.init();
    if (false) {
        Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

创建好Handler对象之后,我们就可以通过Handler对象发送消息了,Handler中提供了一系列的send方法和post方法,但实际上这些方法最终都会调用enqueueMessage方法,如下所示:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;//设置target对象为当前handler对象
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);//向消息队列中插入message
}

该方法中主要存在两行需要注意的代码,首先就是msg.target = this;即将Message的target对象设置为当前的handler对象,这也是为什么looper中的msg.target.dispatchMessage(msg)最终能够将消息分发给handler处理的原因,然后就是向消息队列中插入了message,也就是说,其实Handler发送消息的过程仅仅是向消息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper,Lopper收到消息后就会开始处理了,最终通过msg.target.dispatchMessage将消息交由Handler处理,即Handler的dispatchMessage方法被调用,这时Handler就进入了处理消息的阶段,代码如下:

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的callback是否为空,如果不为空就调用handleCallback来处理消息。Message的callback是一个Runnable对象,实际上就是Handler的post方法所传递的Runnable对象,而handleCallback实际上就是调用Runnable的run方法,如下:

private static void handleCallback(Message message) {
    message.callback.run();
}

其次,检查mCallback是否为空,不为空就调用mCallback的handleMessage方法来处理消息,Callback是一个接口,我们可以通过Handler(callback)来创建对象,这样我们就可以不需要派生Handler的子类,如下所示:

val handler = Handler(Handler.Callback {
    tvTest.text = "test handler"
    true
})

最后,调用Handler的handleMessage方法来处理消息。

总结

至此,我们对Android的Handler就分析完毕了,通过分析源码我们也能够了解到为什么通过Handler就可以实现异步UI操作了,这是因为Handler的handleMessage方法最终是在Looper的loop方法中实现的,而主线程的Looper是在主线程中创建并执行的loop方法,因此handleMessage是在主线程中执行的,从而也就没有了线程安全的问题。