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 间的通讯如下:
- 执行准备:线程A 中执行
Looper.prepare()
准备线程消息循环和消息队列(MessageQueue),执行Looper.loop()
开起无限循环处理消息队列里的任务 - 生成消息:线程 B 通过
handler.obtainMessage()
生成消息(内部会调用Message.abtain()
方法,并且会把当前 handler 传给 message 对象); - 发送消息:线程 B 通过
handler.sendMessage()
发送消息,再调用mQueue.enqueueMessage()
将消息放入消息队列; - 取出消息:线程A 中 mLooper 对象的
loop()
方法一直循环执行(若 mQueue 中没数据会等待),发现MessageQueue 中有 Message 对象并且到达执行时间则内部会调用 mQueue 对象的next()
方法,取出一个消息 msg; - 执行任务:线程 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方法。
- 消息阻塞:
Looper.loop() -> MessageQueue.next() -> nativePollOnce()
如果有返回则执行消息,没有返回则阻塞在哪里等待唤醒- 消息唤醒:
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数组拷贝传出用户态并返回就绪的文件描述符总数。用户态并不知道是哪些文件描述符处于就绪态,需要遍历来判断。
epoll:epoll_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.perpare
和Looper.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无遍历,无拷贝。还使用了红黑树(解决查询慢的问题)