【迁移博客】深刻理解Android消息机制

深刻理解Android消息机制

在平常的开发中,Android 的消息机制做为系统运行的根本机制之一,显得十分的重要。java

从 Handler 发送消息开始

查看源码,Handler的post、send方法最终都会走到android

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
	if (delayMillis < 0) {
	    delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

复制代码

sendMessageDelayed 会走到数组

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
	msg.target = this;
    if (mAsynchronous) {
	    msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码

这里能够设置 Message 为异步消息bash

查看 queue 的 enqueueMessage 方法, 咱们剥离出核心代码:数据结构

if (p == null || when == 0 || when < p.when) {
	// New head, wake up the event queue if blocked.
	msg.next = p;
    mMessages = msg;
    needWake = mBlocked;
 }
复制代码

若是是新的队列头,直接插入队列异步

若是队列里面已经有消息了,执行以下逻辑async

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;
复制代码

插入消息的时候,通常不会唤醒消息队列。若是消息是异步的,而且队列头不是一个异步消息的时候,会唤醒消息队列ide

if (needWake) {
	nativeWake(mPtr);
}
复制代码

消息队列的具体唤醒过程咱们暂时不细看。把关注点移到 Looper 上。looper在执行的时候具体执行了什么逻辑呢?查看 Looper.java 的 looper() 方法函数

looper 方法中有一个死循环, 在死循环中,会获取下一个 Messageoop

for (;;) {
	Message msg = queue.next(); // might block
}
复制代码
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());
复制代码

当存在一个 barrier 消息的时候,会寻找队列中下一个异步任务。而不是按照顺序。 例如3个消息,1,2,3, 2 是异步消息。若是不存在barrier的时候,next的顺序就是 1,2,3 可是若是存在barrier的时候,则是 2,1,3

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;
}
复制代码

这里若是 next 的 Message 不为空,就返回,而且将它移出队列 在 MessageQueue 为空的时候,会顺便去处理一下 add 过的 IdleHandler, 处理一些不重要的消息

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);
     }
}

复制代码

查看 IdleHandler 的源码。

* Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
    }
复制代码

当 queueIdle() 为 false 的时候,会将它从 mIdleHandlers 中 remove,仔细思考下,咱们其实能够利用IdleHandler实现很多功能, 例如

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
	@Override
	public boolean queueIdle() {
		return false
	}
});
复制代码

咱们能够在 queueIdle 中,趁着没有消息要处理,统计一下页面的渲染时间(消息发送完了说明UI已经渲染完了),或者算一下屏幕是否长时间没操做等等。

拿到 Message 对象后,会将 Message 分发到对应的 target 去

msg.target.dispatchMessage(msg);
复制代码

查看源码

public void dispatchMessage(Message msg) {
	if (msg.callback != null) {
		handleCallback(msg);
    } else {
	    if (mCallback != null) {
	        if (mCallback.handleMessage(msg)) {
	            return;
            }
         }
     handleMessage(msg);
	}
}
复制代码

当 msg 的 callback 不为 null 的时候,即经过 post(Runnable) 发送信息的会执行 handlerCallback(msg) 方法。若是 mCallback 不为 null而且 handleMessage 的结果为 false,则执行 handleMessage 方法。不然会中止分发。

private static void handleCallback(Message message) {
	message.callback.run();
}
复制代码

查看 handlerCallback 方法源码, callback 会获得执行。到这里基本的Android消息机制就分析完了,简而言之就是,Handler 不断的将Message发送到一 根据时间进行排序的优先队列里面,而线程中的 Looper 则不停的从MQ里面取出消息,分发到相应的目标Handler执行。

为何主线程不卡?

分析完基本的消息机制,既然 Looper 的 looper 方法是一个for(;;;)循环,那么新的问题提出来了。为何Android会在主线程使用死循环?执行死循环的时候为何主线程的阻塞没有致使CPU占用的暴增?

继续分析在源码中咱们没有分析的部分:

  1. 消息队列构造的时候是否调用了jni部分
  2. nativeWake、nativePollOnce这些方法的做用是什么

先查看MQ的构造方法:

MessageQueue(boolean quitAllowed) {
	mQuitAllowed = quitAllowed;
	mPtr = nativeInit();
}
复制代码

会发现消息队列仍是和native层有关系,继续查看android/platform/frameworks/base/core/jni/android_os_MessageQueue_nativeInit.cpp中nativeInit的实现:

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    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::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}
复制代码

这里会发如今mq中初始化了 native 的 Looper 对象,查看android/platform/framework/native/libs/utils/Looper.cpp中 Looper 对象的构造函数

// 简化后的代码
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {

	int wakeFds[2];
	int result = pipe(wakeFds);

	mWakeReadPipeFd = wakeFds[0];
	mWakeWritePipeFd = wakeFds[1];
	
	result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
	result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);

	mEpollFd = epoll_create(EPOLL_SIZE_HINT);

	struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); 
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
}
复制代码

这里咱们会发现,在 native 层建立了一个epoll,而且对 epoll 的 event 事件进行了监听。

什么是epoll

在继续分析源码以前,咱们先分析一下,什么是epoll


epoll是Linux中的一种IO多路复用方式,也叫作event-driver-IO。

Linux的select 多路复用IO经过一个select()调用来监视文件描述符的数组,而后轮询这个数组。若是有IO事件,就进行处理。

select的一个缺点在于单个进程可以监视的文件描述符的数量存在最大限制,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增加。

epoll在select的基础上(实际是在poll的基础上)作了改进,epoll一样只告知那些就绪的文件描述符,并且当咱们调用epoll_wait()得到就绪文件描述符时,返回的不是实际的描述符,而是一个表明就绪描述符数量的值,你只须要去epoll指定的一个数组中依次取得相应数量的文件描述符便可。

另外一个本质的改进在于epoll采用基于事件的就绪通知方式(设置回调)。在select中,进程只有在调用必定的方法后,内核才对全部监视的文件描述符进行扫描,而epoll事先经过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用相似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便获得通知

关于epoll和select,能够举一个例子来表达意思。select的状况和班长告诉全班同窗交做业相似,会挨个去询问做业是否完成,若是没有完成,班长会继续询问。

而epoll的状况则是班长询问的时候只是统计了待交做业的人数,而后告诉同窗做业完成的时候告诉把做业放在某处,而后喊一下他。而后班长每次都去这个地方收做业。


大体了解了epoll以后,咱们继续查看nativePollOnce方法,同理,会调用native Looper的pollOnce方法

while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {
                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;
                return ident;
            }
        }
复制代码

在pollOnce中,会先处理没有callback的response(ALOOPER_POLL_CALLBACK = -2),处理完后会执行pollInner方法

// 移除了部分细节处理和日志代码
// 添加了分析源码的日志
int Looper::pollInner(int timeoutMillis) {
	if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
	        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
	        int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
	        if (messageTimeoutMillis >= 0
	                && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
	            timeoutMillis = messageTimeoutMillis;
	        }
	  }

	// Poll.
    int result = ALOOPER_POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
	// 等待事件发生或者超时
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // Acquire lock.
    mLock.lock();


	// Check for poll error.
	// epoll 事件小于0, 发生错误
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        result = ALOOPER_POLL_ERROR;
        goto Done;
    }

	if (eventCount == 0) {
		// epoll事件为0,超时,直接跳转到Done
        result = ALOOPER_POLL_TIMEOUT;
        goto Done;
    }

	//循环遍历,处理全部的事件
	for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeReadPipeFd) {
            if (epollEvents & EPOLLIN) {
                awoken();  //唤醒,读取管道里面的事件
            } else {
            }
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;         
                // 处理request,生成response对象,push到相应的Vector
                pushResponse(events, mRequests.valueAt(requestIndex));
            } else {               
            }
        }
    }

Done: ;

	// Invoke pending message callbacks.
	// 发生超时的逻辑处理
    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) {
	    // 处理Native端的Message
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
            // Remove the envelope from the list.
            // We keep a strong reference to the handler until the call to handleMessage
            // finishes. Then we drop it so that the handler can be deleted *before*
            // we reacquire our lock.
            { // obtain handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();
                handler->handleMessage(message);   // 处理消息事件
            } // release handler

            mLock.lock();
            mSendingMessage = false;
            result = ALOOPER_POLL_CALLBACK;   // 设置回调
        } else {
            // The last message left at the head of the queue determines the next wakeup time.
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }

    // Release lock.
    mLock.unlock();

    // Invoke all response callbacks.
    // 执行回调
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == ALOOPER_POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd);  //移除fd
            }
            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear();  // 清除reponse引用的回调方法
            result = ALOOPER_POLL_CALLBACK;  // 发生回调
        }
    }
    return result;
}
复制代码

看到这里,咱们其实能够看出来总体消息模型由 native 和 Java 2层组成,2层各自有本身的消息系统。 Java层经过调用 pollonce 来达到调用底层epoll 让死循环进入阻塞休眠的状态,以免浪费CPU, 因此这也解释了为何Android Looper的死循环为何不会让主线程CPU占用率飙升。

java层和native层的对应图以下:

handler.png

备注

  1. Java 层和 native 层经过 MessageQueue 里面持有一个 native 的MessageQueue 对象进行交互。WeakMessageHandler 继承自MessageHandler,NativeMessageQueue 继承自 MessageQueue
  2. Java 层和 native 层实质是各自维护了一套类似的消息系统。C层发出的消息和Java层发出的消息能够没有任何关系。因此 Framework 层只是很巧的利用了底层 epoll 的机制达到阻塞的目的。
  3. 经过 pollOnce 的分析,能够发现消息的处理实际上是有顺序的,首先是处理native message,而后处理native request,最后才会执行java层,处理java层的message

能够在子线程中建立Handler吗?为何每一个线程只会有一个Looper?

在不少时候,咱们能够遇到这2个问题。既然看了 Handler 的源码,那么,咱们就顺便分析一下这 2 个问题。

查看Handler的构造方法,无参构造方法最后会调用

public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
复制代码

能够看到,这里会直接获取Looper

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
复制代码

这里会把每一个 Looper 存到相应的ThreadLocal对象中,若是子线程直接建立了Handler,Looper 就会是一个null,因此会直接跑出一个"Can't create handler inside thread that has not called Looper.prepare()"的RuntimeException

那么咱们是什么时候把Looper放入ThreadLocal对象的呢?能够在Looper.prepare()中找到答案

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));
}
复制代码

这也解释了,在每一个 Thread 中,只会存在一个 Looper 对象。若是咱们想在子线程中正常建立 Handler,就须要提早运行当前线程的 Looper,调用

Looper.prepare()
复制代码

就不会抛出异常了。

总结

消息机制做为 Android 的基础,仍是很是有深刻了解的必要。对于咱们遇到Handler发送消息的时候跑出的系统异常的排查也颇有意义。

特别感谢

本次源码的阅读过程当中,遇到了不少不了解的问题例如epoll,这里很是感谢IO哥(查看IO哥大佬)助和指导。让我在某些细节问题上暂时绕过和恍然大悟。

相关文章
相关标签/搜索