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

news2024/12/27 0:30:25

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()开始启动循环获取消息。

    @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无遍历,无拷贝。还使用了红黑树(解决查询慢的问题)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/610137.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:Python文件备份的重要性和应用

Python文件备份的重要性和应用 在现代企业和个人用户中&#xff0c;数据备份是一项至关重要的工作&#xff0c;以防止数据丢失或损坏。当涉及到计算机数据时&#xff0c;文件备份是一项基本需求。文件备份还可以用于保护文件&#xff0c;以防它们被病毒、恶意软件或未经授权的…

法规标准-UN R158标准解读

UN R158是做什么的&#xff1f; UN R158全名为针对驾驶员识别车辆后方弱势道路使用者&#xff0c;联合国对倒车系统和机动车的统一规定&#xff0c;该法规涉及批准倒车和机动车辆的装置&#xff0c;主要为保证倒车时避免碰撞&#xff0c;方便驾驶员观察了解车辆后部人员和物体…

dSPACE一览(暂存)

1. SCALEXIO - dSPACE 2. dSPACE仿真流程介绍&#xff08;dSPACE软件介绍、仿真演示、自动化API接口使用&#xff09;_云溪溪儿的博客-CSDN博客 目录 硬件 板卡 软件 VEOS 应用领域 全面总线仿真 高效集成多种建模方法 硬件 板卡 SCALEXIO FPGA Subsystems DS6601 FPG…

STM32通过esp8266连接WiFi接入MQTT服务器

上文我们讲到如何搭建本地MQTT服务器&#xff0c;现在介绍如何通过stm32连接MQTT 一.首先我们初始化esp8266这里我们使用的是USART4与其通信代码如下 void UART4_Init(uint32_t bound) {GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;RCC_APB1…

高通 Camera HAL3:添加一条PipeLine

一.概述 添加一条PipeLine实现两路Raw进&#xff0c;一路Raw出 二.简介 要添加的PipeLine&#xff1a;SWMFMergeRawTwo2One 包含1个memcpy node&#xff0c;这个node用于将2个raw buffer input输入 变为 1个raw buffer output输出 三.添加 3.1 在相应的Usecase下添加一个p…

Spring Cloud Alibaba - Nacos源码分析(一)

目录 一、源码 1、为什么要分析源码 2、看源码的方法 二、Nacos服务注册与发现源码剖析 1、Nacos核心功能点 2、Nacos服务端/客户端原理 2.1、nacos-example 2.2、Nacos-Client测试类 3、项目中实例客户端注册 一、源码 1、为什么要分析源码 1. 提升技术功底&#x…

NB使用MQTT连接格物平台

内容简介&#xff1a; 本文主要记述了怎么使用NB-IoT模块&#xff0c;采用MQTT协议连接联通的格物平台&#xff0c;并且实现单属性和多属性数据的上报。 1 创建产品 打开格物平台&#xff0c;进行注册登录&#xff1b;之后点击页面的控制台&#xff0c;进入设备管理引擎&#x…

爬取中文新闻+正向、逆向最大匹配算法分词+算法优化+P、R、F值评估(完整详细过程+Python源码)

如标题所示&#xff0c;本文旨在记录这次分词实验&#xff0c;将主要从以下四点展开&#xff1a; 1、新闻文本的获取&#xff08;完整爬虫过程&#xff09; 爬取新闻网站中多个板块的新闻&#xff0c;这里建议可以多爬一些板块&#xff0c;保证新闻内容主题丰富&#xff0c;分…

计算机网络 - 移动网络

Wireless links and network Characterstics 无线网络信号会衰减很快, 会被别的source影响, 传播过程中经历多个路径比如经过建筑, 树木等收到影响. 所以就有了信噪比的概念 CDMA CDMA是一种在多点连接时候避免collision的一种算法. 主要是每个sender都用不同的编码, 然后se…

chatgpt赋能python:Python声音处理入门指南

Python声音处理入门指南 如果你是一个音乐爱好者或者处理声音的工程师&#xff0c;Python语言是值得你考虑的一种工具&#xff0c;它拥有丰富的库&#xff0c;可以帮助你在声音分析、编辑、压缩和转换等方面做出成果。 Python声音处理库 Python语言拥有一个大量的声音处理库…

电脑通过VNC连接树莓派

0. 实验准备 VNC软件 VNC Viewer 或者 MobaXterm&#xff08;安装包点击即可下载&#xff09; 可以使用SSH登录进去或者有屏幕的树莓派 一台可以使用的电脑 树莓派和电脑连接在同一个局域网下 0.5 树莓派的公共操作 打开树莓派的 VNC 功能 有屏幕的 打开 VNC 功能&#xff…

日志框架 --- Logback

文章目录 1. 什么是logback2. logback的日志级别3. 日志级别的层级4. logback配置文件4.1 logger标签4.2 root标签4.3 appender标签4.4 filter标签4.5 encoder标签 5. 整体演示5.1 配置文件5.2 运行结果 1. 什么是logback Logback是一个用于Java应用程序的日志框架&#xff0c…

android 如何分析应用的内存(三)

android 如何分析应用的内存&#xff08;三&#xff09; 接上文 细节部分包括如下 native部分 寄存器内容是什么。如pc指向何处&#xff0c;sp指向何处指定地址内容是什么。如变量a对应的内容线程堆栈内容是什么。如主线程的堆栈&#xff0c;UI线程的堆栈堆区的对象有哪些。…

图像边缘提取

什么是图像边缘: 图象的边缘是指图象局部区域亮度变化显著的部分&#xff0c;该区域的灰度剖面一般可以看作是一个阶跃&#xff0c;既从一个灰度值在很小的缓冲区域内急剧变化到另一个灰度相差较大的灰度值。 什么是灰度值: 指图像中点的颜色深度&#xff0c;范围一般从0到255…

C/C++性能提升之cache分析

在开发过程中&#xff0c;我们有时会碰到程序性能瓶颈&#xff0c;这时候需要我们查找热点代码&#xff0c;借用一些命令、工具去分析自己的程序&#xff0c;下面我就介绍一下如何使用perf工具分析程序的cache命中率。 在编写代码前先介绍一下我们的硬件平台&#xff0c;我电脑…

【LeetCode全题库算法速练】2、两数相加

文章目录 一、题目&#x1f538;题目描述&#x1f538;样例1&#x1f538;样例2&#x1f538;样例3 二、代码参考 作者&#xff1a;KJ.JK &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &a…

chatgpt赋能python:Python多行代码一行展示:精简代码,高效编程的绝佳选择

Python多行代码一行展示&#xff1a;精简代码&#xff0c;高效编程的绝佳选择 介绍 在Python的开发中&#xff0c;我们经常需要编写较长的代码&#xff0c;在展示和调试代码时&#xff0c;多行代码会使代码显得过长和复杂。同时&#xff0c;多行代码还会增加代码中的空白行数…

chatgpt赋能python:Python处理颜色RGB简介

Python 处理颜色 RGB 简介 在现代 Web 设计中&#xff0c;颜色的使用非常重要。网站和应用程序的设计师通常需要控制经他们的项目中使用的颜色。最常见的颜色表示方法是 RGB&#xff0c;即红、绿、蓝。RGB 是一种添加光线颜色的方法&#xff0c;它基于红、绿和蓝三种颜色原料的…

Linux操作系统——第一章 进程

目录 基本概念 描述进程-PCB task_struct-PCB的一种 task_ struct内容分类 组织进程 查看进程 通过系统调用获取进程标示符 通过系统调用创建进程-fork初识 进程状态 进程状态查看 Z(zombie)-僵尸进程 僵尸进程危害 孤儿进程 进程优先级 基本概念 查看系统进程 …

【RestAPI】优秀Rest API设计规范

一、API 设计原则 将 REST 映射到 DDD 模式 实体、聚合和值对象等模式旨在对领域模型中的对象施加特定的约束。 在 DDD 的许多介绍文章中&#xff0c;模式是使用构造函数或属性 getter 和 setter 等面向对象的 (OO) 语言概念建模的。 设计 API 时&#xff0c;请考虑这些 API…