1、前言
在 Android 开发中,Handler 的机制和运行原理这方面的知识可以说是每个人都需要熟悉的。这不仅是因为 Handler 是 Android 应用的基石之一,也因为 Handler 整体设计上也是十分优秀的。接下来我就梳理总结一下常见的 Handler 相关知识点。
2、基本使用(GPT)
-
创建Handler对象:要使用Handler,首先需要创建一个Handler对象。Handler可以在UI线程或其他线程中创建,但通常在UI线程中创建,以便将消息发送到UI线程。
Handler handler = new Handler();
-
发送消息:要将消息发送到Handler,可以使用Handler的
post
方法或sendMessage
方法。通常,您将使用post
方法执行一个Runnable任务。handler.post(new Runnable() {@Overridepublic void run() {// 在UI线程执行的任务// 可以更新UI元素} });
或者使用
sendMessage
方法:Message message = handler.obtainMessage(); message.what = MY_MESSAGE_CODE; handler.sendMessage(message);
-
处理消息:在Handler所在的线程中,可以覆盖
handleMessage
方法来处理消息。通常,您需要继承Handler
类并重写handleMessage
方法。class MyHandler extends Handler {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MY_MESSAGE_CODE:// 处理消息break;// 可以处理更多不同消息类型}} }
-
关联Handler与Looper:Handler需要与Looper(消息循环)关联,以便能够在消息队列中接收和处理消息。通常,UI线程已经有一个与之关联的Looper,所以在UI线程中创建Handler不需要额外配置。但如果您在其他线程中创建Handler,需要先创建一个Looper。
Looper.prepare(); // 创建一个新的Looper Handler handler = new Handler(); // 关联Handler与新的Looper Looper.loop(); // 开始消息循环,必须调用以使Looper活动
-
从后台线程向UI线程发送消息:通常情况下,Handler最常用于在后台线程执行任务后更新UI线程。例如,如果您在后台线程中进行网络请求,请求完成后,可以使用Handler将结果传递给UI线程以更新UI元素。
new Thread(new Runnable() {@Overridepublic void run() {// 后台线程执行任务// ...// 任务完成后,使用Handler将结果传递给UI线程handler.post(new Runnable() {@Overridepublic void run() {// 更新UI}});} }).start();
这些是Android Handler的基本用法。Handler是Android中处理异步任务和多线程通信的重要工具,可以确保UI更新等操作在UI线程中执行,从而避免应用程序崩溃或出现不稳定行为。
3、流程梳理
从 2 中可以看出 Handler 有两种发送信息的方式。第一种是发送 Message;第二种是直接 post runnable。我们分别看下两种方法的源码处理。
3.1 获取 Message 的方式
发送 Message 首先需要获取一个 Message,当然可以直接 new 一个对象出来,但是也可以通过 Message.obtain() 方法来获取一个消息池里面的消息对象。
public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}
可见这种方式可以减少内存分配和垃圾回收的开销,因为它避免了频繁创建和销毁 Message 对象,而是重复使用已有对象。这在Android中的消息处理机制中非常有用,因为通常会有大量的消息对象需要创建和处理,如Handler中的消息队列。因此,Message.obtain 方法的使用方式类似于享元模式,通过共享可复用的对象来减少系统资源的消耗,提高性能。这有助于更有效地管理Android应用程序中的消息处理。
3.2 压入消息队列
这里面接着往下看,会通过 Message.enqueueMessage 将这个消息压入消息队列中。这里就要说下这个 Looper 的获取方式了。查看代码可以看到 Looper 是从 ThreadLocal 里面获取到的。ThreadLocal 保证了在每个线程内只有一个 Looper 对象。到这里消息已经进入消息队列中了。
final MessageQueue mQueue;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;
}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);
}// Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();public static @Nullable Looper myLooper() {return sThreadLocal.get();
}
接下来就是取消息的过程,取消息的方法是在 Looper.java 的 loop 方法中,如果是在子线程使用的情况下需要自己手动启动 Looper.loop 方法开启轮询。主线程中则是由系统在 ActivityThread.java 的 main 方法里面为我们开启了轮询。
public static void main(String[] args) {Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");
}
接着看 loop 方法,这里面是一个死循环,会一直从消息队列中获取消息。获取到了后会执行 msg.target.dispatchMessage(msg); 方法。可以看到在 android30 里面已经系统已经集成了检测耗时消息的机制(logSlowDelivery 相关代码)。
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 blockif (msg == null) {// No message indicates that the message queue is quitting.return;}// 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 {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();}
}
这里面 target 在 enqueueMessage 已经设置成了发送 handler。所以执行逻辑会回到 handler 的 dispatchMessage 方法里面。
3.3 消息执行
这里面可以先看一下 post runnable 方法。其实还是发送的 callback 是 runnable 的 Message。所以处理流程都是统一的。
// Handler.java
public void dispatchMessage(@NonNull Message msg) {// 1、如果 msg 存在 callback 则直接执行,post 方式都会走到这里if (msg.callback != null) {handleCallback(msg);} else {// 2、mCallback 不为空则进入这里处理,这个 mCallback 可以通过构造方法传入if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}// 3、最后会走到自身 handleMessage 方法,这个方法可以通过继承重写handleMessage(msg);}
}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;
}
走到这里,可以发现我们的 Handler 机制在 Java 层已经完全梳理一遍了。下面继续看下 native 层的部分。这里就引入了一个经典问题:那就是主线程 loop 方法是死循环,系统为什么不会卡死呢?
从源码可以看到 loop 方法里面调用了消息队列的 next 方法,这里面会调用继续调用 native 的 nativePollOnce(ptr, nextPollTimeoutMillis); 方法。这里面会通过 epoll 机制,当等待消息的时候,会释放系统资源,当被唤醒时再继续操作。唤醒操作 nativeWake(mPtr); 。
// 0 立即返回,2000 等待 2s,-1 永久休眠
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,jlong ptr, jint timeoutMillis) {NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}// 底层通过 epoll 的方式监听读端,会进入等待,等待写入端有数据写入
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);// 插入消息的时候,会调用 wake 方法,会写入了 1,唤醒
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
3.4 消息屏障
消息屏障的典型用例是在UI线程中执行UI更新,以确保UI更新的操作按照它们被提交的顺序执行。例如,如果在后台线程中进行了多次UI更新,并将这些更新消息发送到UI线程的消息队列中,可以使用 sendMessageAtFrontOfQueue 方法来确保这些UI更新按照它们被发送的顺序执行,从而避免UI显示的不一致性。通过 sendMessageAtFrontOfQueue 方法会将时间设置为 0,在进入消息队列的过程中,会直接插入到队列头部,所以可以确保执行优先级较高。
3.5 IdleHandler
IdleHandler
是Android中的一个回调接口,它用于在主线程空闲时执行任务。当主线程没有处理消息时(即处于空闲状态),IdleHandler
中的回调方法将被触发,允许您执行一些耗时较长的任务,而不会影响到UI的响应性。这在某些情况下非常有用,例如在后台预加载数据或执行其他非UI相关的工作。
// 创建一个IdleHandler
MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {@Overridepublic boolean queueIdle() {// 在主线程空闲时执行的任务// 可以执行一些耗时操作,不会阻塞UI线程return false; // 返回true表示继续监听,false表示不再监听}
};// 注册IdleHandler
Looper.myQueue().addIdleHandler(idleHandler);
这块处理逻辑是在 MessageQueue 的 next 方法内部。
4、总结
到这里基本梳理了 Handler 的一些使用和原理,虽然各种框架和 Kotlin 都可以很方便的执行切换线程的操作了,但是这些原理性的东西还是值得我们学习并了解的。