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

本文详细介绍了Android中Handler的使用及其消息传递机制。通过分析Handler、Looper和MessageQueue的原理,阐述了如何在主线程和子线程中安全地进行UI更新。Handler通过将任务切换到指定线程,确保在主线程执行UI操作,避免线程安全问题。同时,文章揭示了Looper在子线程中必须调用prepare()和loop()的原因。
摘要由CSDN通过智能技术生成


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

点击按钮,我们程序就会抛出异常而发生闪退,如下所示:
在这里插入图片描述

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对象并且会抛出异常,如下图:
在这里插入图片描述
除此之外,我们还需要调用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是在主线程中执行的,从而也就没有了线程安全的问题。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值