【Android Framework系列】第1章 Handler消息传递机制

1 Handler简介

Handler是一套Android的消息传递机制,Handler主要用于同进程的线程间通信。而Binder/Socket用于进程间通信

2 Handler运行机制

Handler运行主要涉及到四个类:Handler、Looper、Message、MessageQueue

Handler:消息处理器,通过obtainMessage()生成消息和handleMessage()处理消息;
Looper:循环器,用于取出消息,每个线程只能够一个Looper,管理MessageQueue,不断从中将Message分发并通知Handler处理消息;
Message:消息类,Handler的接收和处理对象通过obtain()方法可以生成一个Message对象,每个Message对象必须有一个Handler对象target,要执行该消息时通过target找到对应Handler执行
MessageQueue:消息队列,用于存储消息,通过enqueueMessage()放入消息,next()取出消息。

一句话理解:Message通过Handler添加到当前线程的MessageQueue队列,当前线程的Looper不断轮询获取Message并通知对应Handler处理消息。

2.1 运行流程

在这里插入图片描述
假设在线程 A 中创建了 Handler 对象,重写了 handler 的 handleMessage() 方法,并且将 Handler 对象传给了线程 B,则线程 B 和线程 A 间的通讯如下:

  1. 执行准备:线程A 中执行Looper.prepare() 准备线程消息循环和消息队列(MessageQueue),执行Looper.loop()开起无限循环处理消息队列里的任务
  2. 生成消息:线程 B 通过 handler.obtainMessage() 生成消息(内部会调用 Message.abtain() 方法,并且会把当前 handler 传给 message 对象);
  3. 发送消息:线程 B 通过 handler.sendMessage() 发送消息,再调用 mQueue.enqueueMessage() 将消息放入消息队列;
  4. 取出消息:线程A 中 mLooper 对象的 loop() 方法一直循环执行(若 mQueue 中没数据会等待),发现MessageQueue 中有 Message 对象并且到达执行时间则内部会调用 mQueue 对象的 next() 方法,取出一个消息 msg;
  5. 执行任务:线程 A 中取出的 msg 对象在创建时已绑定 Handler,通过调用 msg.target.dispatchMessage() 方法,此处的 target 就是步骤 3 中创建的Handler 对象,通知 Handler 执行 handleMessage() 方法。dispatchMessage(msg) 根据 msg 对象是否有 callback 对象(Runnable 对象),有 callback 对象,就直接执行 callback.run(),无 callback 对象,就进行 Handler.handleMessage(msg) 方法进行处理

在这里插入图片描述

3 Handler 源码分析

我先从Looper类源码开始分析:

3.1 Looper.prepare()

我们先来看看Looper类的prepare()方法

   // sThreadLocal.get() will return null unless you've called prepare().
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    /** Initialize the current thread as a looper.
     * This gives you a chance to create handlers that then reference
     * this looper, before actually starting the loop. Be sure to call
     * {@link #loop()} after calling this method, and end it by calling
     * {@link #quit()}.
     */
    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");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

Looper类中Looper.prepare()为静态方法,先获取sThreadLocal中存储的Looper对象,没有则创建Looper对象存储在sThreadLocal中。Looper.prepare()一个线程中只能调用一次。我们看到sThreadLocal为静态对象,整个进程共用一个ThreadLocal对象。下面来看看ThreadLocal类:

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
    
    
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
    
    
        return t.threadLocals;
    }
    
    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
    
    
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

ThreadLocal类set(T value)方法,通过getMap(Thread t)获取当前线程的ThreadLocalMap对象(每个线程都有一个ThreadLocalMap对象threadLocals),没有则通过createMap(Thread t, T firstValue)给当前线程创建一个threadLocals,然后将当前ThreadLocal对象作为key,新创建的Looper对象作为value存储在当前线程的threadLocals中。

3.2 Looper.loop()

Looper.prepare()后,我们需要调用Looper.loop()开始启动循环获取消息。

扫描二维码关注公众号,回复: 15200938 查看本文章
    @UnsupportedAppUsage
    final MessageQueue mQueue;
    
   // sThreadLocal.get() will return null unless you've called prepare().
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    @SuppressWarnings("AndroidFrameworkBinderIdentity")
    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;
            }
        }
    }
    
    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
    
    
        return sThreadLocal.get();
    }

    /**
     * Poll and deliver single message, return true if the outer loop should continue.
     */
    @SuppressWarnings("AndroidFrameworkBinderIdentity")
    private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
    
    
        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 {
    
    
            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;
    }

我们看到Looper.loop()也为静态方法,方法中先通过myLooper()获取到当前线程ThreadLocalMap存储的Looper对象。然后开始for循环通过loopOnce()方法开始轮询消息。最终通过MessageQueue类的next()方法来完成

ThreadLocal类get()方法

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
    
    
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
    
    
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
    
    
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

我们看到get()就是获取当前线程对应的ThreadLocalMap对象,传入进程唯一的ThreadLocal对象作为key来获取当前线程唯一的Looper对象。
Looper获取到后,调用最终调用MessageQueue对象的next()方法,我们来看看该方法:

    @UnsupportedAppUsage
    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();
            }

            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) {
    
    
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        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.
                    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;
                    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;
        }
    }
    
	@UnsupportedAppUsage
	private native void nativePollOnce(long ptr, int timeoutMillis); 
	private native static void nativeWake(long ptr);

细心的同学们应该发现nativePollOnce()方法,从方法名看出是一个native层的方法,这个方法非常重要:MessageQueue没有消息时,便阻塞在looper的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生nativeWake()

3.2.1 阻塞与唤醒

nativePollOnce():用于提取消息队列中的消息
nativeWake():用于唤醒功能,在添加消息到消息队列enqueueMessage(), 或者把消息从消息队列中全部移除quit(),再有需要时都会调用nativeWake方法。

  1. 消息阻塞:
    Looper.loop() -> MessageQueue.next() -> nativePollOnce()
    如果有返回则执行消息,没有返回则阻塞在哪里等待唤醒
  2. 消息唤醒:
    handler.post() -> MessageQueue.enqueueMessage.nativeWake()
    handler发送消息handler.post一个message,最终调用MessageQueue的enqueueMessage方法,里面有一个native方法叫nativeWake(),此时handler就往消息队列里面添加了一条消息,并且通过底层唤醒了阻塞状态的线程循环,开始处理消息

3.2.2 nativePollOnce()阻塞的底层原理

nativePollOnce()底层实现我们来看看代码:
android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativePollOnce(JNIEnv*env, jobject obj,
        jintptr, jint timeoutMillis)
     NativeMessageQueue*nativeMessageQueue =
                            reinterpret_cast<NativeMessageQueue*>(ptr);
    //取出NativeMessageQueue对象,并调用它的pollOnce
   nativeMessageQueue->pollOnce(timeoutMillis);
}
//分析pollOnce函数
void NativeMessageQueue::pollOnce(inttimeoutMillis) {
    
    
   mLooper->pollOnce(timeoutMillis); //重任传递到Looper的pollOnce函数
}

Looper.cpp

inline int pollOnce(int timeoutMillis) {
    
    
	return pollOnce(timeoutMillis, NULL, NULL, NULL);
}

int Looper::pollOnce(int timeoutMillis, int*outFd, int* outEvents,void** outData) {
    
    
   intresult = 0;
   for (;;){
    
     //一个无限循环
   //mResponses是一个Vector,这里首先需要处理response
       while (mResponseIndex < mResponses.size()) {
    
    
           const Response& response = mResponses.itemAt(mResponseIndex++);
           ALooper_callbackFunc callback = response.request.callback;
           if (!callback) {
    
    //首先处理那些没有callback的Response
               int ident = response.request.ident; //ident是这个Response的id
               int fd = response.request.fd;
               int events = response.events;
               void* data = response.request.data;
               ......
               if (outFd != NULL) *outFd = fd;
               if (outEvents != NULL) *outEvents = events;
               if (outData != NULL) *outData = data;
               //实际上,对于没有callback的Response,pollOnce只是返回它的
               //ident,并没有实际做什么处理。因为没有callback,所以系统也不知道如何处理
               return ident;
           }
        }
 
        if(result != 0) {
    
    
			if (outFd != NULL) *outFd = 0;
			if (outEvents != NULL) *outEvents = NULL;
			if (outData != NULL) *outData = NULL;
			return result;
        }
        //调用pollInner函数。注意,它在for循环内部
       result = pollInner(timeoutMillis);
    }
}

int Looper::pollInner(int timeoutMillis) {
    
    
   
    if(timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
    
    
       nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        ......//根据Native Message的信息计算此次需要等待的时间
        timeoutMillis= messageTimeoutMillis;
     }
    intresult = ALOOPER_POLL_WAKE;
   mResponses.clear();
   mResponseIndex = 0;
#ifdef LOOPER_USES_EPOLL  //我们只讨论使用epoll进行I/O复用的方式
    structepoll_event eventItems[EPOLL_MAX_EVENTS];
    //调用epoll_wait,等待感兴趣的事件或超时发生
    inteventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS,
                                      timeoutMillis);
#else
     ......//使用别的方式进行I/O复用
#endif
    //从epoll_wait返回,这时候一定发生了什么事情
   mLock.lock();
    if(eventCount < 0) {
    
     //返回值小于零,表示发生错误
        if(errno == EINTR) {
    
    
           goto Done;
        }
        //设置result为ALLOPER_POLL_ERROR,并跳转到Done
       result = ALOOPER_POLL_ERROR;
        gotoDone;
    }
 
    //eventCount为零,表示发生超时,因此直接跳转到Done
    if(eventCount == 0) {
    
    
      result = ALOOPER_POLL_TIMEOUT;
        gotoDone;
    }
#ifdef LOOPER_USES_EPOLL
    //根据epoll的用法,此时的eventCount表示发生事件的个数
    for (inti = 0; i < eventCount; i++) {
    
    
        intfd = eventItems[i].data.fd;
       uint32_t epollEvents = eventItems[i].events;
        /*
         之前通过pipe函数创建过两个fd,这里根据fd知道是管道读端有可读事件。
         读者还记得对nativeWake函数的分析吗?在那里我们向管道写端写了一个”W”字符,这样
         就能触发管道读端从epoll_wait函数返回了
         */
        if(fd == mWakeReadPipeFd) {
    
    
           if (epollEvents & EPOLLIN) {
    
    
                //awoken函数直接读取并清空管道数据,读者可自行研究该函数
               awoken();
           }
          ......
        }else {
    
    
           /*
            mRequests和前面的mResponse相对应,它也是一个KeyedVector,其中存储了
            fd和对应的Request结构体,该结构体封装了和监控文件句柄相关的一些上下文信息,
            例如回调函数等。我们在后面的小节会再次介绍该结构体
           */
           ssize_t requestIndex = mRequests.indexOfKey(fd);
           if (requestIndex >= 0) {
    
    
               int events = 0;
               //将epoll返回的事件转换成上层LOOPER使用的事件
               if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT;
               if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;
               if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;
               if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;
               //每处理一个Request,就相应构造一个Response
               pushResponse(events, mRequests.valueAt(requestIndex));
           }  
            ......
        }
    }
Done: ;
#else
     ......
#endif
    //除了处理Request外,还处理Native的Message
   mNextMessageUptime = LLONG_MAX;
    while(mMessageEnvelopes.size() != 0) {
    
    
       nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
       const MessageEnvelope& messageEnvelope =mMessageEnvelopes.itemAt(0);
        if(messageEnvelope.uptime <= now) {
    
    
           {
    
    
               sp<MessageHandler> handler = messageEnvelope.handler;
               Message message = messageEnvelope.message;
               mMessageEnvelopes.removeAt(0);
               mSendingMessage = true;
               mLock.unlock();
               //调用Native的handler处理Native的Message
               //从这里也可看出NativeMessage和Java层的Message没有什么关系
               handler->handleMessage(message);
           }
           mLock.lock();
           mSendingMessage = false;
            result = ALOOPER_POLL_CALLBACK;
        }else {
    
    
            mNextMessageUptime = messageEnvelope.uptime;
           break;
        }
    }
 
    mLock.unlock();
    //处理那些带回调函数的Response
   for(size_t i = 0; i < mResponses.size(); i++) {
    
    
       const Response& response = mResponses.itemAt(i);
       ALooper_callbackFunc callback = response.request.callback;
        if(callback) {
    
    //有了回调函数,就能知道如何处理所发生的事情了
           int fd = response.request.fd;
           int events = response.events;
           void* data = response.request.data;
           //调用回调函数处理所发生的事件
           int callbackResult = callback(fd, events, data);
           if (callbackResult == 0) {
    
    
               //callback函数的返回值很重要,如果为0,表明不需要再次监视该文件句柄
               removeFd(fd);
           }
           result = ALOOPER_POLL_CALLBACK;
        }
    }
    returnresult;
}

代码细节看得是不是有点晕,其实nativePollOnce()的核心是Linux系统的epoll机制实现的阻塞,作为Android开发只需要了解epoll的大概原理。

3.2.3 epoll机制

请添加图片描述
epoll的通俗解释是一种当文件描述符的内核缓冲区非空的时候,发出可读信号进行通知,当写缓冲区不满的时候,发出可写信号通知的机制
epoll的核心是3个API,核心数据结构是:1个红黑树和1个链表

在这里插入图片描述
JDK在Linux已经默认使用epoll方式,但是JDK的epoll采用的是水平触发,而Netty重新实现了epoll机制,采用边缘触发方式,netty epoll transport 暴露了更多的nio没有的配置参数,如 TCP_CORK, SO_REUSEADDR等等;另外像Nginx也采用边缘触发。

3.2.4 epoll与select、poll的对比

在这里插入图片描述
如上图所示,epoll的效率是最高的,下面我们来看看具体原因:

3.2.4.1 用户态将文件描述符传入内核的方式

select:创建3个文件描述符集并拷贝到内核中,分别监听读、写、异常动作。这里受到单个进程可以打开的fd数量限制,默认是1024。
poll:将传入的struct pollfd结构体数组拷贝到内核中进行监听。
epoll:执行epoll_create会在内核的高速cache区中建立一颗红黑树以及就绪链表(该链表存储已经就绪的文件描述符)。接着用户执行的epoll_ctl函数添加文件描述符会在红黑树上增加相应的结点。

3.2.4.2 内核态检测文件描述符读写状态的方式

select:采用轮询方式,遍历所有fd,最后返回一个描述符读写操作是否就绪的mask掩码,根据这个掩码给fd_set赋值。
poll:同样采用轮询方式,查询每个fd的状态,如果就绪则在等待队列中加入一项并继续遍历。
epoll采用回调机制。在执行epoll_ctl的add操作时,不仅将文件描述符放到红黑树上,而且也注册了回调函数,内核在检测到某文件描述符可读/可写时会调用回调函数,该回调函数将文件描述符放在就绪链表中。

3.2.4.3 找到就绪的文件描述符并传递给用户态的方式

select:将之前传入的fd_set拷贝传出到用户态并返回就绪的文件描述符总数。用户态并不知道是哪些文件描述符处于就绪态,需要遍历来判断。
poll:将之前传入的fd数组拷贝传出用户态并返回就绪的文件描述符总数。用户态并不知道是哪些文件描述符处于就绪态,需要遍历来判断。
epollepoll_wait只用观察就绪链表中有无数据即可,最后将链表的数据返回给数组并返回就绪的数量。内核将就绪的文件描述符放在传入的数组中,所以只用遍历依次处理即可。这里返回的文件描述符是通过mmap让内核和用户空间共享同一块内存实现传递的,减少了不必要的拷贝。

3.2.4.4 重复监听的处理方式

select:将新的监听文件描述符集合拷贝传入内核中,继续以上步骤。
poll:将新的struct pollfd结构体数组拷贝传入内核中,继续以上步骤。
epoll:无需重新构建红黑树,直接沿用已存在的即可。

3.2.4.5 epoll更高效的原因

select和poll的动作基本一致,只是poll采用链表来进行文件描述符的存储
而select采用fd标注位来存放,所以select会受到最大连接数的限制,而poll不会。

select、poll、epoll虽然都会返回就绪的文件描述符数量。但是select和poll并不会明确指出是哪些文件描述符就绪,而epoll会。
造成的区别就是,系统调用返回后,调用select和poll的程序需要遍历监听的整个文件描述符找到是谁处于就绪,
而epoll则直接处理即可。select、poll都需要将有关文件描述符的数据结构拷贝进内核,最后再拷贝出来。而epoll创建的有关文件描述符的数据结构本身就存于内核态中,系统调用返回时利用mmap()文件映射内存加速与内核空间的消息传递:即epoll使用mmap减少复制开销。
select、poll采用轮询的方式来检查文件描述符是否处于就绪态,而epoll采用回调机制。造成的结果就是,随着fd的增加,select和poll的效率会线性降低,而epoll不会受到太大影响,除非活跃的socket很多。epoll的边缘触发模式效率高,系统不会充斥大量不关心的就绪文件描述符虽然epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。

3.2.4.5 epoll机制总结

epoll高效的原因使用mmap减少复制开销、采用回调机制减少耗时。wait返回只返回了就绪的文件描述符数量,而非文件描述符,也就是说我还是要遍历寻找,例如遍历events 看看都有哪些就绪事件,是有读的?还是写的?然后接着去真正调用IO,这个路子也和Java NIO的使用方式是相同的,即:阻塞返回了,说明有就绪事件,然后遍历events,找到自己关注的事件处理。

4 Handler相关问答

Q1:用一句话概括Handler,并简述其原理。

Handler是Android系统的根本,在Android应用被启动的时候,会分配一个单独的虚拟机,虚拟机会执行ActivityThread中的main方法,在main方法中对主线程Looper进行了初始化,也就是几乎所有代码都执行在Handler内部。Handler也可以作为主线程和子线程通讯的桥梁。Handler通过sendMessage发送消息,将消息放入MessageQueue中,在MessageQueue中通过时间的维度来进行排序,Looper通过调用loop方法不断的从MessageQueue中获取消息,执行Handler的dispatchMessage,最后调用handleMessage方法。

Q2:为什么系统不建议在子线程访问UI?(为什么不能在子线程更新UI?)

在某些情况下,在子线程中是可以更新UI的。但是在ViewRootImpl中对UI操作进行了
checkThread,但是我们在OnCreate和onResume中可以使用子线程更新UI,由于我们在
ActivityThread中的performResumeActivity方法中通过addView创建了ViewRootImpl,这个行为是在onResume之后调用的,所以在OnCreate和onResume可以进行更新UI。
但是我们不能在子线程中更新UI,因为UI更新本身是线程不安全的,如果添加了耗时操作之后,一旦ViewRootImpl被创建将会抛出异常
。一旦在子线程中更新UI,容易产生并发问题。

Q3:一个Thread可以有几个Looper?几个Handler?

一个线程只能有一个Looper,可以有多个Handler
线程在初始化时候调用Looper.prepare()方法对将静态对象ThreadLocal作为key(整个进程全部线程的Looper共用一个sThreadLocal),创建Looper对象作为value存储在当前线程的ThreadLocalMap中(其实就只有一个key对应value,key为sThreadLocal,value为looper对象。这两玩意都TM只有一个,所以ThreadLocalMap只有一个键值对。。。)。
通过线程独有的ThreadLocal实现将Looper存储在ThreadLocalMap这样键值对的数据结构中。
为什么是一个Looper
因为Looper需要循环,循环后面的代码无法执行了,所以一个线程只有一个Looper
为什么是多个Handler
为了相互独立,互不干扰,各自处理各自的消息,谁发生的Message谁处理,也可以通过removeMessages清空掉自己的Message。

Q4:可以在子线程直接new一个Handler吗?那该怎么做?

可以在子线程中创建Handler,我们需要调用Looper.perpareLooper.loop方法。或者通过获取主线程的looper来创建Handler。

Q5:Message可以如何创建?哪种效果更好,为什么?

Message.obtain来创建Message。通过这种方式创建的Message会被存放在一个大小为50的复用池中,这样会复用之前的Message的内存,不会频繁的创建对象,导致内存抖动。

Q6:主线程中Looper的轮询死循环为何没有阻塞主线程?

Looper轮询是死循环,但是当没有消息的时候他会block(阻塞),ANR是当我们处理点击事件的时候5s内没有响应,我们在处理点击事件的时候也是用的Handler,所以一定会有消息执行,并且ANR也会发送Handler消息,所以不会阻塞主线程。Looper是通过Linux系统的epoll实现的阻塞式等待消息执行(有消息执行无消息阻塞),而ANR是消息处理耗时太长导致无法执行剩下需要被执行的消息触发了ANR。

Q7:使用Hanlder的postDealy()后消息队列会发生什么变化?

Handler发送消息到消息队列,消息队列是一个时间优先级队列,内部是一个单向链表。发动postDelay之后会将该消息进行时间排序存放到消息队列中。

Q8:点击页面上的按钮后更新TextView的内容,谈谈你的理解?(阿里面试题)

点击按钮的时候会发送消息到Handler,但是为了保证优先执行,会加一个标记异步,同时会发送一个target为null的消息,这样在使用消息队列的next获取消息的时候,如果发现消息的target为null,那么会遍历消息队列将有异步标记的消息获取出来优先执行,执行完之后会将target为null的消息移除。(同步屏障:拦截同步消息执行,优先执行异步消息。 View 更新时,draw、requestLayout、invalidate 等很多地方都调用异步消息的方法)

同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用Handler.getLooper().getQueue().postSyncBarrier() 并设置消息的setAsynchronous(true)时,target 即为 null ,也就开启了同步屏障。当在消息轮询器 Looper 在loop()中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。

Q9:生产者-消费者设计模式懂不?

举个例子,面包店厨师不断在制作面包,客人来了之后就购买面包,这就是一个典型的生产者消费者设计模式。但是需要注意的是如果消费者消费能力大于生产者,或者生产者生产能力大于消费者,需要一个限制,在java里有一个blockingQueue。当目前容器内没有东西的时候,消费者来消费的时候会被阻塞,当容器满了的时候也会被阻塞。Handler.sendMessage相当于一个生产者,MessageQueue相当于容器,Looper相当于消费者。但我们的Handler为什么不使用java的blockingQueue呢?原因是除了我们上层需要使用到Handler,其实底层的消息都是需要传递给Handler处理,比如:驱动层需要发事件给APP、屏幕点击事件、底层刷新通知等,所以我们使用的是Native层提供的MessageQueue实现消息队列。

Q10:Handler是如何完成子线程和主线程通信的?

在主线程中创建Handler,在子线程中发送消息,放入到MessageQueue中,通过Looper.loop取出消息进行执行handleMessage,由于looper我们是在主线程初始化的,在初始化looper的时候会创建消息队列,所以消息是在主线程被执行的。

Q11:关于ThreadLocal,谈谈你的理解?

ThreadLocal实例进程内只有一个(静态实例),但其内部的set和get方法是获取的当前线程的ThreadLocalMap对象,ThreadLocalMap是每个线程有一个单独的内存空间,不共享,ThreadLocal在set的时候会将数据存入对应线程的ThreadLocalMap中,key=ThreadLocal,value=值

Q12:享元设计模式有用到吗?

享元设计模式就是重复利用内存空间,减少对象的创建,Message中使用到了享元设计模式。内部维护了一个链表,并且最大长度是50,当消息处理完之后会将消息内的属性设置为空,并且插入到链表的头部,使用obtain创建的Message会从头部获取空的Message

Q13: Handler内存泄漏问题及解决方案

内部类持有外部类的引用导致了内存泄漏,如果Activity退出的时候,MessageQueue中还有一个Message没有执行,这个Message持有了Handler的引用,而Handler持有了Activity的引用,导致Activity无法被回收,导致内存泄漏。使用static关键字修饰,在onDestory的时候将消息清除。
简单理解
当Handler为非静态内部类时,其持有外部类Actviity对象,所以导致Looper->MessageQueue->Message->Handler->Activity,这个引用链中Message如果还在MessageQueue中等待执行,则会导致Activity一直无法被释放和回收。

导致的原因
因为Looper需要循环,所以一个线程只有一个Looper,但一个线程中可有多个Handler,MessageQueue中消息Message执行时不知道要通知哪个Handler执行任务,
所以在Message创建时中存入了Handler对象target用于回调执行的消息。如果Handler是Activity这种短生命周期对象的非静态内部类时,则会让创建出来的Handler对象持有该外部类Activity的引用,Message还在队列中导致引用着Handler,而非静态内部类Handler引用外部类Activity导致Activity无法被回收,最终导致内存泄漏。

解决办法
1.Handler不能是Activity这种短生命周期的对象类的内部类;
2.在Activity销毁时,将创建的Handler中的消息队列清空并结束所有任务

Q14: Handler异步消息处理(HandlerThread)

内部使用了Handler+Thread,并且处理了getLooper的并发问题。如果获取Looper的时候发现Looper还没创建,则wait,等待looper创建了之后在notify

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

子线程中创建了Looper,当没有消息的时候子线程将会被block,无法被回收,所以我们需要手动调用quit方法将消息删除并且唤醒looper,然后next方法返回null退出loop

Q16: 既然可以存在多个Handler往MessageQueue中添加数据(发消息是各个handler可能处于不同线程),那他内部是如何确保线程安全的?

在添加数据和执行next的时候都加了this锁,这样可以保证添加的位置是正确的,获取的也会是最前面的。

Q17: 关于IntentService,谈谈你的理解

HandlerThread+Service实现,可以实现Service在子线程中执行耗时操作,并且执行完耗时操作时候会将自己stop。

Q18: Glide是如何维护生命周期的?

一般想问的应该都是这里

    @NonNull
    private RequestManagerFragment getRequestManagerFragment(
            @NonNull final android.app.FragmentManager fm,
            @Nullable android.app.Fragment parentHint,
            boolean isParentVisible) {
    
    
        RequestManagerFragment current = (RequestManagerFragment)
                fm.findFragmentByTag(FRAGMENT_TAG);
        if (current == null) {
    
    
            current = pendingRequestManagerFragments.get(fm);
            if (current == null) {
    
    
                current = new RequestManagerFragment();
                current.setParentFragmentHint(parentHint);
                if (isParentVisible) {
    
    
                    current.getGlideLifecycle().onStart();
                }
                pendingRequestManagerFragments.put(fm, current);
                fm.beginTransaction().add(current,
                        FRAGMENT_TAG).commitAllowingStateLoss();
                handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
            }
        }
        return current;
    }

1.为什么会判断两次null,再多次调用with的时候,commitAllowingStateLoss会被执行两次,所以我们需要使用一个map集合来判断,如果map中已经有了证明已经添加过了
2.handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(),我们需要将map里面的记录删除。

Q19:handler 主线程阻塞了怎么办,阻塞怎么唤醒?

Android系统事件驱动系统,loop循环处理事件,如果不循环程序就结束了

Q20:Handler底层为什么用epoll,不用select、poll?

Socket非阻塞IO中select需要全部轮询不适合,poll也是需要遍历和copy,效率太低了。epoll非阻塞式IO,使用句柄获取APP对象,epoll无遍历,无拷贝。还使用了红黑树(解决查询慢的问题)

猜你喜欢

转载自blog.csdn.net/u010687761/article/details/130915378