美文网首页
跨线程通信-Handler

跨线程通信-Handler

作者: 未子涵 | 来源:发表于2022-07-25 23:42 被阅读0次

    从通信机制角度看应用启动过程

    从通信机制角度看应用启动过程.png

    首先,让我们结合通信机制,来看看通过Launcher启动App的过程(假设为冷启动)。结合上图,其完整流程如下:

    • Launcher 通过所持有的 AMS 的 Binder 调用 AMS 的 startActivity() 方法 → Binder 通信
    • AMS 检查目标 App 进程是否已启动,若没有启动,则向 Zygote 进程发送创建新进程的请求 → Socket 通信
    • Zygote 进程 fork 一个子进程(即 App 进程),之后并非立即启动,而是经历了以下流程:
      • 创建 App 进程的 Binder
      • 通过反射创建 App 进程入口函数(main)的 Method 对象,并返回给上层调用方
    • App 进程启动过程中,将自己的 binder 反向 attach 到 AMS 中管理起来 → Binder 通信
    • AMS 通过 App 的 binder 执行其 Activity 的启动流程 → Binder 通信
    • 当进入到 App 进程内部后,之后的通信就可以通过 Handler 进行了

    如何保证SystemServer进程一直活跃

    SystemServer 是由 Zygote 进程创建的第一个进程,并且由 Zygote 反射调用其 main() 方法启动。

    // SystemServer.java
    /**
     * The main entry point from zygote.
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }
    
    private void run() {
        ...
        // Prepare the main looper thread (this thread).
        android.os.Process.setThreadPriority(
                android.os.Process.THREAD_PRIORITY_FOREGROUND);
        android.os.Process.setCanSelfBackground(false);
        Looper.prepareMainLooper();
        Looper.getMainLooper().setSlowLogThresholdMs(
                SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
        ...
        // Loop forever.
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    

    可以看到,在其 run() 方法中,有一个 looper ,并且是 MainLooper ,其中的无限循环,就是为了让进程一直活跃。也就是说,Looper 的价值就是让进程一直活跃

    App进程启动时,在 ActivityThread 的 main() 方法中,也会启动一个 MainLooper ,这两个 Looper 有什么关系?

    没有关系,它们根本就是两个不同进程的 MainLooper 。

    Activity 的生命周期,都是运行在主线程上,事实上,每一个生命周期的代码,都是属于某一个 Message。为什么这么说呢,我们从 Activity 的启动来分析:

    Activity详细启动流程.png

    AMS 调度 Activity 的过程,最终会走到 realStartActivityLocked() 中。

    Activity启动流程.png

    最终又走到了 ApplicationThread 的 scheduleTransaction() 中。

    // ActivityThread.java
    final H mH = new H();
    ...
    @Override
    public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
        // 会走到成员变量 mH 这个 Handler 的 EXECUTE_TRANSACTION 中
        ActivityThread.this.scheduleTransaction(transaction);
    }
    
    class H extends Handler {
        ...
        public static final int EXECUTE_TRANSACTION = 159;
        ...
    
        String codeToString(int code) {
            if (DEBUG_MESSAGES) {
                switch (code) {
                    ...
                    case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
                    ...
                }
            }
            return Integer.toString(code);
        }
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                ...
                case EXECUTE_TRANSACTION:
                    final ClientTransaction transaction = (ClientTransaction) msg.obj;
                    mTransactionExecutor.execute(transaction);
                    if (isSystem()) {
                        // Client transactions inside system process are recycled on the client side
                        // instead of ClientLifecycleManager to avoid being cleared before this
                        // message is handled.
                        transaction.recycle();
                    }
                    // TODO(lifecycler): Recycle locally scheduled transactions.
                    break;
                ...
            }
            Object obj = msg.obj;
            if (obj instanceof SomeArgs) {
                ((SomeArgs) obj).recycle();
            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }
    }
    

    上面的 mTransactionExecutor.execute(transaction); 说明它就是一个消息。

    为什么Acitivyt、Service生命周期都运行在主线程上,设计目的是什么?

    统一规划UI的展示。

    子线程:handler → sendMessage() → MessageQueue.enqueueMessage() 入队列
    主线程:Looper.loop() → MessageQueue.next() → handler.dispatchMessage()

    子线程将数据封装成一个 Message 对象,send 到主线程内部的 MessageQueue 中,等待被执行。

    应用的启动入口 ActivityThread.main() ,做的两件最重要的事就是:将应用自身的 binder attach 到 AMS 中,以及启动主线程的 Looper。

    // ActivityThread.java
    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    
        // Install selective syscall interception
        AndroidOs.install();
    
        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);
    
        Environment.initForCurrentUser();
    
        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);
    
        // Call per-process mainline module initialization.
        initializeMainlineModules();
    
        Process.setArgV0("<pre-initialized>");
    
        // 准备 MainLooper
        Looper.prepareMainLooper();
    
        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        // 将应用自身的 binder attach 到 AMS 中
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
    
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
    
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
    
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        // 启动 MainLooper
        Looper.loop();
    
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    

    Handler 机制源码分析

    Android的消息处理机制主要分为四个部分:

    • 创建消息队列
    • 消息循环
    • 消息发送
    • 消息处理

    主要涉及三个类:

    • MessageQueue
    • Looper
    • Handler

    创建消息队列

    整个创建过程涉及到两个类:MessageQueueLooper。它们在C++层有两个对应的类:NativeMessageQueueLooper

    // Looper.java
    // 注意,这个 sThreadLocal 是静态的
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    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");
        }
        // 将一个Looper对象保存在ThreadLocal里面
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    private Looper(boolean quitAllowed) {
        // 新建一个MessageQueue对象
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
    // MessageQueue.java
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        // 通过JNI初始化C++层的NativeMessageQueue对象
        mPtr = nativeInit();
    }
    private native static long nativeInit();
    

    创建过程如下所示:

    1. Looper的prepare或者prepareMainLooper静态方法被调用,将一个Looper对象保存在ThreadLocal里面。
    2. Looper对象的初始化方法里,首先会新建一个MessageQueue对象。
    3. MessageQueue对象的初始化方法通过JNI初始化C++层的NativeMessageQueue对象。
    4. NativeMessageQueue对象在创建过程中,会初始化一个C++层的Looper对象。
    5. C++层的Looper对象在创建的过程中,会在内部创建一个管道(pipe),并将这个管道的读写fd都保存在mWakeReadPipeFd和mWakeWritePipeFd中。
      然后新建一个epoll实例,并将两个fd注册进去。
    6. 利用epoll的机制,可以做到当管道没有消息时,线程睡眠在读端的fd上,当其他线程往管道写数据时,本线程便会被唤醒以进行消息处理。

    总结得出,其关系如下图所示:

          +------------+     +------+
          |MessageQueue+----^+Looper|
          +-----+------+     +------+
                |                    
                |                    
                |                    
    +-----------+------+     +------+
    |NativeMessageQueue+^----+Looper|
    +------------------+     +------+
    
        A----^B表示B中保存A的引用
    

    消息循环

    // Looper.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.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }
    
        me.mInLoop = true;
    
        // 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);
    
        me.mSlowDeliveryDetected = false;
    
        for (;;) {
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }
    
    private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
        // 调用 MessageQueue.next() 方法读取 Message,该方法会堵塞线程直到有消息到来为止
        Message msg = me.mQueue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return false;
        }
    
        // 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 {
            // 通过 Handler.dispatchMessage() 分发消息
            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 (me.mSlowDeliveryDetected) {
                if ((dispatchStart - msg.when) <= 10) {
                    Slog.w(TAG, "Drained");
                    me.mSlowDeliveryDetected = false;
                }
            } else {
                if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                        msg)) {
                    // Once we write a slow delivery log, suppress until the queue drains.
                    me.mSlowDeliveryDetected = 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();
    
        return true;
    }
    

    消息循环过程如下:

    1. 首先通过调用Looper的loop方法开始消息监听。loop方法里会调用MessageQueue的next方法。next方法会堵塞线程直到有消息到来为止。
    2. next方法通过调用nativePollOnce方法来监听事件。next方法内部逻辑如下所示(简化):
      • 进入死循环,以参数timout=0调用nativePollOnce方法。
      • 如果消息队列中有消息,nativePollOnce方法会将消息保存在mMessage成员中。nativePollOnce方法返回后立刻检查mMessage成员是否为空。
      • 如果mMessage不为空,那么检查它指定的运行时间。如果比当前时间要前,那么马上返回这个mMessage,否则设置timeout为两者之差,进入下一次循环。
      • 如果mMessage为空,那么设置timeout为-1,即下次循环nativePollOnce永久堵塞。
    3. nativePollOnce方法内部利用epoll机制在之前建立的管道上等待数据写入。接收到数据后马上读取并返回结果。

    总结得出,其流程如下图所示:

              +------+    +------------+  +------------------+  +--------------+                    
              |Looper|    |MessageQueue|  |NativeMessageQueue|  |Looper(Native)|                    
              +--+---+    +------+-----+  +---------+--------+  +-------+------+                    
                 |               |                  |                   |                           
                 |               |                  |                   |                           
    +-------------------------------------------------------------------------------+               
    |[msg loop]  |   next()      |                  |                   |           |               
    |            +------------>  |                  |                   |           |               
    |            |               |                  |                   |           |               
    |            |               |                  |                   |           |               
    |            |               | nativePollOnce() |                   |           |               
    |            |               |    pollOnce()    |                   |           |               
    |            |               +----------------> |                   |           |               
    |            |               |                  |                   |           |              
    |            |               |                  |                   |           |               
    |            |               |                  |                   |           |               
    |            |               |                  |                   |           |               
    |            |               |                  |     pollOnce()    |           |               
    |            |               |                  +-----------------> |           |               
    |            |               |                  |                   |           |               
    |            |               |                  |                   | epoll_wait()              
    |            |               |                  |                   +--------+  |               
    |            |               |                  |                   |        |  |               
    |            |               |                  |                   |        |  |               
    |            |               |                  |                   | <------+  |               
    |            |               |                  |                   | awoken()  |               
    |            +               +                  +                   +           |               
    |                                                                               |               
    |                                                                               |               
    +-------------------------------------------------------------------------------+               
    

    消息发送

    在讲消息发送前,我们先看一下消息的创建:

    // Handler.java
    public final Message obtainMessage()
    {
        // 注意这里的this,就是当前 Handler 的实例
        return Message.obtain(this);
    }
    
    // Message.java
    public static Message obtain(Handler h) {
        Message m = obtain();
        // 该 Message 的 target 保存了传入的 Handler
        m.target = h;
    
        return m;
    }
    

    消息发送过程主要由 Handler 对象来驱动。具体就是其 sendMessage() 方法。

    // Handler.java
    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
    
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
    
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
    
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // 将消息压入MessageQueue
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    
    // MessageQueue.java
    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;
    }
    
    private native static void nativeWake(long ptr);
    
    1. Handler对象在创建时会保存当前线程的looper和MessageQueue,如果传入Callback的话也会保存起来。
    2. 用户调用handler对象的sendMessage方法,传入msg对象。handler通过调用MessageQueue的enqueueMessage方法将消息压入MessageQueue。
    3. enqueueMessage方法会将传入的消息对象根据触发时间(when)插入到message queue中。然后判断是否要唤醒等待中的队列。
      • 如果插在队列中间。说明该消息不需要马上处理,不需要由这个消息来唤醒队列。
      • 如果插在队列头部(或者when=0),则表明要马上处理这个消息。如果当前队列正在堵塞,则需要唤醒它进行处理。
    4. 如果需要唤醒队列,则通过nativeWake方法,往前面提到的管道中写入一个"W"字符,令nativePollOnce方法返回。

    总结得出,其流程如下图所示:

              +-------+     +------------+   +------------------+   +--------------+                        
              |Handler|     |MessageQueue|   |NativeMessageQueue|   |Looper(Native)|                        
              +--+----+     +-----+------+   +---------+--------+   +-------+------+                        
                 |                |                    |                    |                               
                 |                |                    |                    |                               
    sendMessage()|                |                    |                    |                               
    +----------> |                |                    |                    |                               
                 |                |                    |                    |                               
                 |enqueueMessage()|                    |                    |                               
                 +--------------> |                    |                    |                               
                 |                |                    |                    |                               
                 |                |                    |                    |                               
                 |                |                    |                    |                               
                 |                |  nativeWake()      |                    |                               
                 |                |    wake()          |                    |                               
                 |                +------------------> |                    |                               
                 |                |                    |                    |                               
                 |                |                    |    wake()          |                               
                 |                |                    +------------------> |                               
                 |                |                    |                    |                               
                 |                |                    |                    |                               
                 |                |                    |                    |write(mWakeWritePipeFd, "W", 1)
                 |                |                    |                    |                               
                 |                |                    |                    |                               
                 |                |                    |                    |                               
                 |                |                    |                    |                               
                 |                |                    |                    |                               
                 +                +                    +                    +                               
    

    消息处理

    在“消息循环”的源码中,我们提到了 Looper 对象的loop方法里面的queue.next方法如果返回了message,那么handler的dispatchMessage会被调用。

    • 如果新建Handler的时候传入了callback实例,那么callback的handleMessage方法会被调用。
    • 如果是通过post方法向handler传入runnable对象的,那么runnable对象的run方法会被调用。
    • 其他情况下,handler方法的handleMessage会被调用。
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            // 如果通过 Message 传入了 Callback 实例,则由其响应
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                // 如果新建Handler的时候传入了callback实例,那么callback的handleMessage方法会被调用
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 否则handler方法的handleMessage会被调用
            handleMessage(msg);
        }
    }
    
    public void handleMessage(@NonNull Message msg) {
    }
    

    总结得出,其流程如下图所示:

             +------+       +-------+                                                                   
             |Looper|       |Handler|                                                                   
             +--+---+       +---+---+                                                                   
                |               |                                                                       
                |               |                                                                       
    loop()      |               |                                                                       
    [after next()]              |                                                                       
    +---------> |               |                                                                       
                |               |                                                                       
                |dispatchMessage()                                                                      
                +-------------> |                                                                       
                |               |                                                                       
                |               |                                                                       
                |               | handleMessage()                                                       
                |               +-------+                                                               
                |               |       |                                                               
                |               |       |                                                               
                |               | <-----+                                                               
                |               |   (callback or subclass)                                              
                |               |                                                                       
                +               +                                                                       
    

    Handler 面试那些事儿

    Handler 跨线程通信的原理

    子线程到主线程通信,其实是利用了“线程间共享内存”。先看下图——Handler工作流程:

    Handler工作流程.png

    跨线程的核心原理:

    跨线程的核心原理.png

    从上图可以看到,跨线程通信明显是一个生产者消费者的设计模式。

    Handler 内存泄露的原因

    匿名内部类默认持有外部类的引用(activity → handler),匿名内部类只是表象,这个问题实际上是考察 JVM 的知识。

    handerl 泄漏时的引用链:activity → handler → Message → MessageQueue → Looper → static sThreadLocal。static 变量就是一个 GC Root。

    GC 算法

    • 引用记数法:缺陷是对象相互引用(形成一个循环)时,无法计算。
    • 可达性分析法:直接或间接的被 GC Root 持有引用。

    子线程中维护的 Looper ,消息队列无消息时的处理方案是什么?有什么用?主线程呢?

    这其实也是考量内存泄漏的。 虽然无消息时,并不会占用cpu,因为 Looper 中有睡眠机制,会停止 cpu 使用,但线程本身需要占用内存(比如每个线程都会有各自的工作内存),且每个应用可创建的线程是有限的,如果一个子线程中维护了一个 Looper ,而 Looper 中必然有一个死循环,只要循环在运行,该子线程就一直存在,如果在无消息时仍然不退出 looper ,就意味着这个线程本身所占用的内存也被泄漏了,并且该线程会成为一个 GCRoot ,被该线程引用的其他对象,也都会被泄漏。

    • 子线程没有消息时的处理方案:调用 looper.quit() 退出循环
    • 用处:停止循环,退出线程
    • 主线程没有消息时怎么处理:不能退出循环,只能让其休眠。
    // MessageQueue.java
    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();
            }
    
            // nextPollTimeoutMillis 会传给 native 层,当传入的是 -1 时,就会“睡眠”
            nativePollOnce(ptr, nextPollTimeoutMillis);
    
            synchronized (this) {
                ...
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        // 根据最近一条消息要等待执行的时间,通过 nativePollOnce 唤醒
                        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.
                    // 无消息时,此变量赋值为 -1,下次循环时传入 nativePollOnce()
                    nextPollTimeoutMillis = -1;
                }
                ...
        }
    }
    

    nativePollOnce() 最终会调用 native层的 MessageQueue 中的 epoll() 机制,“-1”就代表睡眠。

    Handler 如何处理发送延迟消息

    先看看消息队列添加消息和取消息的过程:

    MessageQueue的主要函数.png
    • 消息队列中的消息按待执行时间排序
    • 队头是最早要执行的消息
    • 当队头消息还未到达执行时间时,计算剩余等待时间,并通过 nativePollOnce 传给 native 层的 epoll() ,到时自动唤醒

    Handler 没有消息处理时是阻塞的还是非阻塞的?为什么不会有 ANR 产生?

    首先要明白什么是 ANR ?以及 ANR 的分类、触发原理。

    ANR超时阈值.png

    其中,按键事件(input) 5秒钟未处理完时,其实不一定会触发 ANR ,只有当为获得相应时,不停点击按钮,也就是第一次按钮5秒内未处理完,又点击第二次按钮时,才会触发 ANR。

    Service ANR触发机制.png

    其实 ANR 是一个以 Message 展示出来的 UI。

    • 没有消息处理时是“阻塞”的,这才才会“睡眠”,释放 cpu。
    • ANR 的产生是因为做了个“定时炸弹”,而 handler 中没有做定时炸弹,它的阻塞就是因为没有消息处理了,它要休眠,交出 cpu

    我们使用 Message 时,应该如何创建它?

    通过 obtain 获取 Message ,为什么要这么做呢,看看代码:

    // Handler.java
    /**
     * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
     * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
     *  If you don't want that facility, just call Message.obtain() instead.
     */
    @NonNull
    public final Message obtainMessage()
    {
        return Message.obtain(this);
    }
    
    // Message.java
    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            // 优先从 Message pool 中获取
            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();
    }
    

    当我们看到 sPool 时,就应该明白,通过 obtain() 获取时,是优先从 Message 内部自己维护的一个 Message Pool 中获取 Message 实例的。类似于 Thread Pool 机制,这里的 Message Pool 是为了高效的消息复用,而复用则是为了避免内存抖动。这里采用了享元设计模式

    内存抖动有什么问题?
    内存抖动 → 内存碎片 → OOM
    内存抖动 → 频繁GC(频繁STW) → 应用卡顿

    Thread Pool 和 Message Pool ,都是运用享元模式,创建了一个共享内存池。享元模式的使用非常广,比如地图开发,对于接收到的位置信息,不能每次都 new 一个 Java Bean 出来,而是应该创建一个 Java Bean 的内存池出来,合理复用。再比如股票类应用、 ViewPager、RecyclerView 等等。

    相关文章

      网友评论

          本文标题:跨线程通信-Handler

          本文链接:https://www.haomeiwen.com/subject/bhinirtx.html