消息机制

作者: 30cf443c3643 | 来源:发表于2019-01-11 15:26 被阅读40次

    Handler

    Android 有一条非常重要的开发规范限制:不能在子线程访问UI控件,否则程序异常。所以经常通过Handler来将更新UI的操作切换到主线程中执行。

    为什么不能在子线程访问UI控件?

    因为Android UI控件不是线程安全的,多线程并发访问,或处于不可预期的状态。如果对UI控件加锁,会降低UI控件访问的效率,会阻塞某些线程的执行。

    消息队列

    MessageQueue包含插入enqueueMessage和读取next两个操作,他的内部实现是通过一条单链表的数据结构来维护消息列表

     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 循环
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
                
                // native 层阻塞函数,nextPollTimeoutMillis 为超时时间,首次循环时值为0,即直接返回
                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) {
                            // 下一个消息还没到处理时间,则设置超时时间为还需等待的时间,进入阻塞状态.
                            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 需要取走处理,故需要从链表中断开
                            msg.next = null;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            // 标记为使用中
                            msg.markInUse();
                            // 返回要处理的消息
                            return msg;
                        }
                    } else {
                        // 没有消息要处理,超时时长为-1,循环并等待
                        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,进入阻塞状态,有新消息入队时,会调用 nativeWake() 唤醒
                        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方法里有个for循环,是一个死循环。如果消息队列中没有消息,next会阻塞在这里;有消息时,会返回这条消息,并从链表中删除

    Looper

    handler的工作需要looper。通过Looper.prepare为当前线程创建Looper,然后通过Looper.loop()来开启消息循环。

    public static void loop() {
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue queue = me.mQueue;
    
            // 循环读取消息并处理,无消息时阻塞。这种写法是最常用的 Linux IO 操作方式。
            for (;;) {
                // 取出一个消息,若没有消息要处理,则阻塞
                Message msg = queue.next(); // 可能阻塞
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                final long traceTag = me.mTraceTag;
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
                try {
                    // 调用 handler 的 dispatchMassage() 分发消息
                    msg.target.dispatchMessage(msg);
                    end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                
                // 回收进对象池
                msg.recycleUnchecked();
            }
        }
    
    

    loop方法是个死循环,他会调用MessageQueue的next方法来获取新消息,而next是个阻塞方法,所以loop也会阻塞。

    主线程的死循环一直运行是不是特别消耗CPU资源呢?
        public static void main(String[] args) {
            // 省略...
            
            // 初始化 UI 线程的 looper 对象
            Looper.prepareMainLooper();
            
            // 初始化 ActivityThread,进而初始化其成员变量 mH(Handler子类)
            ActivityThread thread = new ActivityThread();
            // 将 ApplicationThread(Binder) 对象 attach 到 ActivityManagerService(AMS)
            // 注:AMS 运行在 SystemServer 进程的一个线程中,负责调度四大组件等,通过 Binder 与 App 进程进行 IPC
            thread.attach(false);
            
            // 省略...
            
            // 主线程进入循环
            Looper.loop();
        }
    
    

    这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

    ThreadLocal

    线程内部的数据存储类。当某些数据是以线程为作用域且不同线程有不同数据副本时候,可以采用ThreadLocal。对于Handler来说可以获取当前线程的looper

    private ThreadLocal<Boolean> mTLocal = new ThreadLocal<>();
    mTLocal.set(true);
    new Thread(){
         public void run(){
               mTLocal.set(false);
         }
    }
    

    相关文章

      网友评论

        本文标题:消息机制

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