Looper机制详解

前言

此前有过一篇关于JAVA层Looper的介绍和MessageQueue的介绍
网络上也有一篇很详细的介绍

用一张图来说明looper运作的机制
在这里插入图片描述
looper顾名思义是轮询,轮询的目的是为了查找消息队列(MessageQueue)中的消息并且处理消息(Message)。MessageQueue和Looper是一一对应的关系,一个prepare后的线程中只有一个Looper对象和以及MessageQueue,且线程独有。消息可以是来自Looper所在线程,也可以是来自其他子线程,贴一段代码解释一下

        HandlerThread handlerThread = new HandlerThread("test");
        handlerThread.start();
        Handler mainHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //这里是在主线程中运行的事情
                switch (msg.what) {
                    case 0:
                        //todo
                        break;
                    case 2:
                        //todo
                        break;
                }
            }
        };
        Handler workHandler = new Handler(handlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 1:
                        //这里是在子线程中运行的,如果需要到主线程执行,需要发送message消息到主线程looper中的MQ中
                        mainHandler.sendEmptyMessage(2);
                        break;
                }
            }
        };
        mainHandler.sendEmptyMessage(0);//主线程发送message消息到主线程looper中的MQ中
        workHandler.sendEmptyMessage(1);//主线程发送message消息到子线程looper中的MQ中

轮询底层机制

之前的文章写到了MQ阻塞后没有深入探讨block原因,这里来解惑。之前的流程
Looper中会在queue.next()阻塞

 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;

        for (;;) {
            Message msg = queue.next(); // might block 阻塞
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
			msg.target.dispatchMessage(msg);
		}
}

MessageQueue中nativePollOnce()

Message next() {
    final long ptr = mPtr;
    ...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
  		...
        //阻塞,除非到了超时时间或者唤醒
        nativePollOnce(ptr, nextPollTimeoutMillis);
    }
    ...}

nativePollOnce阻塞方法:到了超时时间(nextPollTimeoutMillis)或者通过唤醒方式(nativeWake),会解除阻塞状态

  1. nextPollTimeoutMillis大于等于零,会规定在此段时间内休眠,然后唤醒
  2. 消息队列为空时,nextPollTimeoutMillis为-1,进入阻塞;重新有消息进入队列,插入头结点的时候会触发nativeWake唤醒方法

为了搞懂nativePollOnce阻塞的原理,我们先来了解Linux epoll机制

epoll机制

这里有篇文章也不错。

概念

epoll是一种I/O事件通知机制,是linux 内核实现IO多路复用的一个实现。
IO多路复用是指,在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作
epoll的通俗解释是一种当文件描述符的内核缓冲区非空的时候,发出可读信号进行通知,当写缓冲区不满的时候,发出可写信号通知的机制。

I/O

输入输出(input/output)的对象可以是文件(file), 网络(socket),进程之间的管道(pipe)。在linux系统中,都用文件描述符(fd)来表示

事件

可读事件,当文件描述符关联的内核读缓冲区可读,则触发可读事件。
(可读:内核缓冲区非空,有数据可以读取)
可写事件,当文件描述符关联的内核写缓冲区可写,则触发可写事件。
(可写:内核缓冲区不满,有空闲空间可以写入)

图解

在这里插入图片描述
epoll_create():创建并初始化eventpoll结构体ep,并将ep放入file->private,并返回fd。会在内核的高速cache区中建立一颗红黑树以及就绪链表(该链表存储已经就绪的文件描述符)
epoll_ctl():注册fd到epoll实例上。在执行epoll_ctl的add操作时,不仅将文件描述符放到红黑树上,而且也注册了回调函数,内核在检测到某文件描述符可读/可写时会调用回调函数,该回调函数将文件描述符放在就绪链表中。
epoll_wait():应用进程阻塞在epoll上,超时时长置为-1表示一直等到有目标事件才会返回。epoll_wait只用观察就绪链表中有无数据即可,最后将链表的数据返回给数组并返回就绪的数量

JAVA层Looper

等待消息

Message next() {
    final long ptr = mPtr;
    ...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
  		...
        //阻塞,除非到了超时时间或者唤醒,最终会调用到 epoll_wait()方法,等待消息
        nativePollOnce(ptr, nextPollTimeoutMillis);
    }
    ...}

生产消息

 boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
			//balabala
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
            	//nativewake里面会执行write()操作,写入消息
                nativeWake(mPtr);
            }
        }
        return true;
    }

C++层Looper

线程prepare时会创建Looper和MessgeQueue对象
Looper为STL对象,线程独有,MessageQueue为Looper的成员变量
在这里插入图片描述

	//创建Looper
    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));
    }
    //创建MQ
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    MessageQueue(boolean quitAllowed) {
      mQuitAllowed = quitAllowed;
      mPtr = nativeInit();    
    }

android_os_MessageQueue.cpp

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
	//新建NativeMQ
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
    	//新建Looper
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

Looper::Looper(bool allowNonCallbacks)
    : mAllowNonCallbacks(allowNonCallbacks),
      mSendingMessage(false),
      mPolling(false),
      mEpollRebuildRequired(false),
      mNextRequestSeq(WAKE_EVENT_FD_SEQ + 1),
      mResponseIndex(0),
      mNextMessageUptime(LLONG_MAX) {
    mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
    LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0, "Could not make wake event fd: %s", strerror(errno));

    AutoMutex _l(mLock);
    //epoll操作
    rebuildEpollLocked();
}

void Looper::rebuildEpollLocked() {
    // Close old epoll instance if we have one.
    if (mEpollFd >= 0) {
#if DEBUG_CALLBACKS
        ALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this);
#endif
        mEpollFd.reset();
    }

    // Allocate the new epoll instance and register the WakeEventFd.
    //创建epoll实例
    mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));

    epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
    //监听fd
    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &wakeEvent);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
                        strerror(errno));

    for (const auto& [seq, request] : mRequests) {
        epoll_event eventItem = createEpollEvent(request.getEpollEvents(), seq);

        int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, request.fd, &eventItem);
        if (epollResult < 0) {
            ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
                  request.fd, strerror(errno));
        }
    }
}

从上面的代码我们可以看到创建Looper和MessageQueue对象时,已经完成了

  • epoll_create
  • epoll_ctl

JAVA层面分析等待消息和消息入queue时也有对应的native操作
等待消息时最终会通过next()方法调用到c++层nativePollOnce

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        ...
        result = pollInner(timeoutMillis);
    }
}

int Looper::pollInner(int timeoutMillis) {
    // Poll.
    int result = POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;

    // We are about to idle.
    mPolling = true;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    ...
}

这里可以看到最终执行了

  • epoll_wait

总结

  1. Looper使用了epoll机制,创建-> 监听 -> 等待
  2. 在JAVA层创建Looper和MessageQueue对象时,对应的会通过nativeInit方法到c++侧创建looper和messagequeue对象,并且通过epoll_create和epoll_ctl方法对相关fd进行监听。
  3. 消息最终会通过write写入
  4. 消息会通过epoll_wait()阻塞后获取并处理
  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值