Android Handler 机制解析

1、前言

在 Android 开发中,Handler 的机制和运行原理这方面的知识可以说是每个人都需要熟悉的。这不仅是因为 Handler 是 Android 应用的基石之一,也因为 Handler 整体设计上也是十分优秀的。接下来我就梳理总结一下常见的 Handler 相关知识点。

2、基本使用(GPT)

  1. 创建Handler对象:要使用Handler,首先需要创建一个Handler对象。Handler可以在UI线程或其他线程中创建,但通常在UI线程中创建,以便将消息发送到UI线程。

    java 复制代码
    Handler handler = new Handler();
  2. 发送消息 :要将消息发送到Handler,可以使用Handler的post方法或sendMessage方法。通常,您将使用post方法执行一个Runnable任务。

    java 复制代码
    handler.post(new Runnable() {
        @Override
        public void run() {
            // 在UI线程执行的任务
            // 可以更新UI元素
        }
    });

    或者使用sendMessage方法:

    java 复制代码
    Message message = handler.obtainMessage();
    message.what = MY_MESSAGE_CODE;
    handler.sendMessage(message);
  3. 处理消息 :在Handler所在的线程中,可以覆盖handleMessage方法来处理消息。通常,您需要继承Handler类并重写handleMessage方法。

    java 复制代码
    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MY_MESSAGE_CODE:
                    // 处理消息
                    break;
                // 可以处理更多不同消息类型
            }
        }
    }
  4. 关联Handler与Looper:Handler需要与Looper(消息循环)关联,以便能够在消息队列中接收和处理消息。通常,UI线程已经有一个与之关联的Looper,所以在UI线程中创建Handler不需要额外配置。但如果您在其他线程中创建Handler,需要先创建一个Looper。

    java 复制代码
    Looper.prepare(); // 创建一个新的Looper
    Handler handler = new Handler(); // 关联Handler与新的Looper
    Looper.loop(); // 开始消息循环,必须调用以使Looper活动
  5. 从后台线程向UI线程发送消息:通常情况下,Handler最常用于在后台线程执行任务后更新UI线程。例如,如果您在后台线程中进行网络请求,请求完成后,可以使用Handler将结果传递给UI线程以更新UI元素。

    java 复制代码
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 后台线程执行任务
            // ...
    
            // 任务完成后,使用Handler将结果传递给UI线程
            handler.post(new Runnable() {
                @Override
                public void run() {
                    // 更新UI
                }
            });
        }
    }).start();

这些是Android Handler的基本用法。Handler是Android中处理异步任务和多线程通信的重要工具,可以确保UI更新等操作在UI线程中执行,从而避免应用程序崩溃或出现不稳定行为。

3、流程梳理

从 2 中可以看出 Handler 有两种发送信息的方式。第一种是发送 Message;第二种是直接 post runnable。我们分别看下两种方法的源码处理。

3.1 获取 Message 的方式

发送 Message 首先需要获取一个 Message,当然可以直接 new 一个对象出来,但是也可以通过 Message.obtain() 方法来获取一个消息池里面的消息对象。

java 复制代码
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

可见这种方式可以减少内存分配和垃圾回收的开销,因为它避免了频繁创建和销毁 Message 对象,而是重复使用已有对象。这在Android中的消息处理机制中非常有用,因为通常会有大量的消息对象需要创建和处理,如Handler中的消息队列。因此,Message.obtain 方法的使用方式类似于享元模式,通过共享可复用的对象来减少系统资源的消耗,提高性能。这有助于更有效地管理Android应用程序中的消息处理。

3.2 压入消息队列

这里面接着往下看,会通过 Message.enqueueMessage 将这个消息压入消息队列中。这里就要说下这个 Looper 的获取方式了。查看代码可以看到 Looper 是从 ThreadLocal 里面获取到的。ThreadLocal 保证了在每个线程内只有一个 Looper 对象。到这里消息已经进入消息队列中了。

java 复制代码
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 方法里面为我们开启了轮询。

java 复制代码
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 相关代码)。

java 复制代码
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 block
        if (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。所以处理流程都是统一的。

java 复制代码
// 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); 。

c++ 复制代码
// 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相关的工作。

java 复制代码
// 创建一个IdleHandler
MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
        // 在主线程空闲时执行的任务
        // 可以执行一些耗时操作,不会阻塞UI线程
        return false; // 返回true表示继续监听,false表示不再监听
    }
};

// 注册IdleHandler
Looper.myQueue().addIdleHandler(idleHandler);

这块处理逻辑是在 MessageQueue 的 next 方法内部。

4、总结

到这里基本梳理了 Handler 的一些使用和原理,虽然各种框架和 Kotlin 都可以很方便的执行切换线程的操作了,但是这些原理性的东西还是值得我们学习并了解的。

相关推荐
恋猫de小郭10 小时前
你是不是觉得 R8 很讨厌,但 Android 为什么选择 R8 ?也许你对 R8 还不够了解
android·前端·flutter
城东米粉儿11 小时前
Android Glide 笔记
android
城东米粉儿12 小时前
Android TheRouter 笔记
android
城东米粉儿18 小时前
Android AIDL 笔记
android
城东米粉儿18 小时前
Android 进程间传递大数据 笔记
android
城东米粉儿18 小时前
Android KMP 笔记
android
冬奇Lab20 小时前
WMS核心机制:窗口管理与层级控制深度解析
android·源码阅读
松仔log20 小时前
JetPack——Paging
android·rxjava
城东米粉儿21 小时前
Android Kotlin DSL 笔记
android