• Android Handler 机制解析


    1、前言

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

    2、基本使用(GPT)

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

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

      handler.post(new Runnable() {
          @Override
          public void run() {
              // 在UI线程执行的任务
              // 可以更新UI元素
          }
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      或者使用sendMessage方法:

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

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

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

      new Thread(new Runnable() {
          @Override
          public void run() {
              // 后台线程执行任务
              // ...
      
              // 任务完成后,使用Handler将结果传递给UI线程
              handler.post(new Runnable() {
                  @Override
                  public void run() {
                      // 更新UI
                  }
              });
          }
      }).start();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

    这些是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 flag
                    sPoolSize--;
                    return m;
                }
            }
            return new Message();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    可见这种方式可以减少内存分配和垃圾回收的开销,因为它避免了频繁创建和销毁 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    接下来就是取消息的过程,取消息的方法是在 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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    接着看 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 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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    这里面 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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    走到这里,可以发现我们的 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(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)));
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    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() {
        @Override
        public boolean queueIdle() {
            // 在主线程空闲时执行的任务
            // 可以执行一些耗时操作,不会阻塞UI线程
            return false; // 返回true表示继续监听,false表示不再监听
        }
    };
    
    // 注册IdleHandler
    Looper.myQueue().addIdleHandler(idleHandler);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    4、总结

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

  • 相关阅读:
    Python实现视频字幕时间轴格式转换
    iTOP-3568开发板NPU使用安装RKNN Toolkit Lite2
    Chrome清除Cookie未生效
    C++:C++入门基础
    Oracle数据中如何在 where in() 条件传参
    Python操作Kafka基础教程
    HACKTHEBOX——Shocker
    毕业四年,随笔
    前端小案例3:Flex弹性布局行内元素宽度自适应
    重新认识 IP地址
  • 原文地址:https://blog.csdn.net/qq_43183022/article/details/132702332