Handler原理,源码,常见问题,从Java到Kernel解析,面试题详解

本文详细介绍了Android Handler的发送和接收消息流程,包括从Java到Kernel的调用过程,涉及Looper和MessageQueue的工作原理。此外,文章还讨论了Handler的异步屏障特性,并给出了相关面试题的解答。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

导读:
本文打算分2步骤来讲解Handler,首先要有一个整体的流程说明,看看他的从Java 到kernel的完整调用过程,由于Handler还算是代码比较简单,逻辑比较清楚,所以这个过程也是相对清晰。
第二个步骤就是习题时间,作为训练,解答市面上关于Handler比较好的问题。
面向读者:好奇Handler具体实现流程的人,需要寻找Hook点,所以必须了解整个流程的人,准备面试的人。

第一步:Handler机制的整体流程

Handler API的调用就类似拿着一个handler的对象到处去发消息,然后在我们创建的Handler会实现一个接受消息的方法,并在这里写好我们的处理逻辑。

Handler所扮演的角色就类似一个管理类,提供给调用方使用,而他的api主要目的也很明确,就是提供给你发消息和接受处理消息的能力,但这些能力的实现却跟handler没有什么关系。

1.1 首先我们看发消息的流程。

Handler的构造器有很多,分别有三个参数,Looper,Callback, async:Boolean, 具体的意思,我们第二步骤再看,先看跟Looper相关的.

public Handler(Looper looper) {
    this(looper, null, false);
}

可以指定Looper,那么不指定的肯定就是当前线程的Looper了,因为Looper使用了TreadLocal来保证每个线程都是唯一性的嘛。

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

可以看到Handler上来就保存了Looper和Looper中的MsgQ对象,接着可以发现所有发消息的逻辑最后都会会聚到这个方法:

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

有一个msg.target 用Handler对象自身this赋值了,So,所有的Handler发送的msg都携带着发送者的信息,也就是作为发送方的Handler对象,这个作用后面再说,我们先看主流程。
最后都是走的MsgQ的方法,看来真正的发送逻辑和Looper关系不大,而是全靠MsgQ.
我们继续看MessageQueue的这个入队方法。

boolean enqueueMessage(Message msg, long when) {
		//对象锁,保证多线程发消息的同步性
        synchronized (this) {
            msg.when = when;
            //下面要表演单链表插入新节点到头部
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                //如果是全新的消息或者时间最早的话直接作为新头部
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                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;
                    }
                }
                //p是上面遍历跳出的正确插入位置,在p前面插入msg消息
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            //暂时先忽略needWake
            if (needWake) {
            //调用native层传入的mPtr指针代表着这个MsgQ的对象,提供给cpp调用
                nativeWake(mPtr);
            }
        }
        return true;
    }

这个nativeWake调用的是frameworks/base/core/jni/android_os_MessageQueue.cpp

这个文件。

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
    mLooper->wake();
}

刚才注释说过了,这个ptr代表着Java层的MsgQ对象,强转一下,最后调用了Looper.cpp的wake方法。


void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif
    ssize_t nWrite;
    do {
    //这里使用了pipe管道,mWakeWritePipeFd代表管道的写端描述符,可以理解为一个flag
        nWrite = write(mWakeWritePipeFd, "W", 1);
    } while (nWrite == -1 && errno == EINTR);
    if (nWrite != 1) {
        if (errno != EAGAIN) {
            ALOGW("Could not write wake signal, errno=%d", errno);
        }
    }
}

至此发送的工作结束,一会儿分析接收消息的流程,小总结一下:
发送消息Java层只跟MsgQ有关,Handler -> MsgQ.java -> MsgQ.cpp -> Looper.cpp -> linux kernel的管道,而MsgQ本质是一个按照时间从前到后排序的单链表,由于MsgQ是Looper的,Looper是线程私有的,所以除了Handler的调用,后面的所有流程都是线程私有的,代表了一个线程。

1.2 我们再看接受消息的流程。

我们都知道再Handler类实现的方法fun handleMessage(Msg)可以接受到消息,但这个消息是从哪里来的呢, 发送的时候最后Looper.cpp调用了Linux 内核的管道,这里需要了解一下pipe和epoll的知识,这是一种多复用监听IO机制,描述一下就是,当管道里有人写入了,会唤醒线程并通知epoll来读数据,如果管道被读光了,而暂时也没有写入,那么读的线程会BLOCK,释放掉CPU资源。那么读到了,kernel就会通知到native层,在通知到Java层,我们反过看源码,从handleMessage来逆向观察,首先寻找handlerMessage是在哪调用的:

 /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

这里可以看到,给谁处理有三种方式,如果msg本身携带了callback, 那么交给msg处理,如果handler对象自己有mCallback,那么交给自己的mCallback处理,这两个callback都是在Msg和Handler的构造器中由调用方传入的。如果两个都没有意愿处理消息,才调用Handler自身的handleMsg()方法。
那么再上一层,是谁调用了dispatchMessage呢,答案就是在Looper.java中调用的。

  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 (;;) {
        	//著名的死循环,queue.next获取下一个信息可能会导致阻塞block
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
            	//把取到的消息分发给handler处理,msg.target就是handler,上文我们提到过。
                msg.target.dispatchMessage(msg);
               
            } finally {
                
            }
            /*把msg对象的引用释放掉,断开与GC root节点的引用链
,为什么不直接置为null呢,是因为msg对象有一个池来维护(单链表实现,非常的简洁优雅),来重复利用实例,所以并不想msg对象被回收掉*/
            msg.recycleUnchecked();
        }
    }

很显然,这个死循环,一直在操作的就是用MsgQ取到next节点,那么怎么才能取到next节点呢?

Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
        	/*之前说过这个ptr是MsgQ的引用指针,提供给native层调用,这个方法很关键,next方法后面的内容作为 Area B,暂时隐藏掉*/
            nativePollOnce(ptr, nextPollTimeoutMillis);
			...
            /*code of Area B */
            ...

下面展示调用链

MsgQ.java -> MsgQ.cpp -> Looper.cpp -> Lopper.pollInner()


static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
        //ptr强转为MsgQ
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    //调用pollOnce本体
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    //取下一个节点的任务也是交个Looper
    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 (;;) {
    	//死循环调用pollInner
        result = pollInner(timeoutMillis);
    }
}
int Looper::pollInner(int timeoutMillis) {
    // Poll.
    int result = POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;
    // We are about to idle.
    mIdling = true;
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    //epoll_wait会等待消息的到来,也就是如果有人通过发消息写入了pipe,这是可以取到消息,别忘了这个函数是被死循环调用的。
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    // No longer idling.
    mIdling = false;
    // Acquire lock.
    mLock.lock();
    // Check for poll error.
    ...
    // Check for poll timeout.
 	...
 	
    // 获取到信息后,接下来就是读取了
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            }
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
                // 插入到response队列中供后续获取
                pushResponse(events, mRequests.valueAt(requestIndex));
            } 
        }
    }
//Done代码块开始
Done: ;


    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
            { // obtain handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();
                // 处理native层的Message
                handler->handleMessage(message);
            } // release handler
        }
    }

    // Release lock.
    mLock.unlock();
  // 调用所有的request的回调.
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
            ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
                    this, response.request.callback.get(), fd, events, data);
#endif
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(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();
            result = POLL_CALLBACK;
        }
    }
    return result;
}

poll小结

pollInner()方法的处理流程:

  1. 先调用epoll_wait(),这是阻塞方法,用于等待事件发生或者超时;
  2. 对于epoll_wait()返回,当且仅当以下3种情况出现:
    POLL_ERROR,发生错误,直接跳转到Done;
    POLL_TIMEOUT,发生超时,直接跳转到Done;
    检测到管道有事件发生,则再根据情况做相应处理:如果是管道读端产生事件,则直接读取管道的数据;如果是其他事件,则处理request,生成对应的reponse对象,push到reponse数组;
  3. 进入Done标记位的代码段:
    先处理Native的Message,调用Native 的Handler来处理该Message;
    再处理Response数组,POLL_CALLBACK类型的事件;

从上面的流程,可以发现对于Request先收集,一并放入reponse数组,而不是马上执行。真正在Done开始执行的时候,是先处理native Message,再处理Request,说明native Message的优先级高于Request请求的优先级。

另外pollOnce()方法中,先处理Response数组中不带Callback的事件,再调用了pollInner()方法。

拿到msg后,我们来看刚才隐藏掉的Area B

for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);
			//Area B 开始
            synchronized (this) {
                // 查询记录当前的时间 now
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                //经过nativePollOnce,取到的msg已经在链表里了
                Message msg = mMessages;
                //上文我们分析过,handler 发送msg的时候都会把自己赋值给target,这里是异步屏障的处理,一会儿讲
                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) {
                /*如果msg的时间大于现在的时间,也就是msg不是之前发出的消息*/
                    if (now < msg.when) {
                        // 证明这条消息是下一条消息,还没准备好,设置一个超时时间,当他准备好的时候唤醒
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 取到消息.
                        mBlocked = false;
                        //这里处理异步屏障,会从链表中间截取优先级高的消息
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                        //正常消息取第一个也就是msg,所以头节点移到下一个节点
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        //如果这里返回了当然循环就进入下一个,没有返回的话,就会Block
                        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;
                }
        }
1.3 异步屏障

我们再看一下异步屏障这块代码

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

如果target == null,开始遍历单链表,只要msg不是null且msg不是isAsynchronous的,就一直找,这明显是要找到一个消息是isAsynchronous的才会罢休,否则一直往后找。
之前分析过,handler发消息最后会调用enqueueMessage的方法,里面第一行就是msg.target = this;,那么怎么才能发target为空的消息呢,可以看到越过Handler,MsgQ里面有一个public的方法:

	public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }
    
	private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            //创造一个msg,注意没有复制msg.target,默认值是null
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;
			//插入队列
            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            //返回一个令牌token,有点像引用计数
            return token;
        }
    }
	
	//根据token移除这条target为null的消息
	public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

所以异步屏障就是一条target为null的msg,如果队列中插入了这条msg,那么取到它的时候,整个队列都会暂停,去遍历寻找Mssage.isAsynchronous() == true的消息,优先取出这条消息返回,然后根据插入屏障消息时候返回的token调用removeSyncBarrier,把这条消息移除,队列回复原来的逻辑。
大概的意思就是全部交通管制,让领导的车先走,领导车过去了,在取消管制,恢复正常交通,一定是因为这条消息比较重要呗。
举一个例子,ViewRootImpl会调用scheduleTraversals()方法来更新视图,

 	void scheduleTraversals() {
 			//插入异步屏障,开始交通管制,并且获取本次管制编号mTraversalBarrier 
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //发送重要的更新视图消息,让领导车队先走
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

postCallback最后会调用在这里

private void postCallbackDelayedInternal(int callbackType,
         Object action, Object token, long delayMillis) {    
         Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
         msg.arg1 = callbackType;
         msg.setAsynchronous(true);
         mHandler.sendMessageAtTime(msg, dueTime);
    }

可以看到这条加急信息是设置了msg.setAsynchronous(true),他就可以在异步屏障中畅通无阻,会优先把这条消息取出来执行。
而对应的,领导的车过去了,交通管制结束,拿着刚才的管制编号mTraversalBarrier把屏障移除:

    void unscheduleTraversals() {
         mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);         
    }

第二步:习题

1,Looper 使用死循环for(;;)取消息,为什么主线程没有阻塞?
这个题玩了文字游戏,让你默认为没有阻塞,阻塞肯定是阻塞的,人家源码都写的清清楚楚,会阻塞。但为什么主线程我们感觉不到阻塞,真的只是我们感觉不到而已,因为阻塞的条件是没有消息了,而主线程会受到包括屏幕刷新,视图刷新,Activity生命周期等等各种各样的消息,所以不会感觉到阻塞,这个问题应该问的是为什么没有ANR,下面给出官方文档ANR说明的图:
在这里插入图片描述
第四条写的,线程处于阻塞状态,可能导致ANR,所以这个题,可以回答为什么正常情况下是不会ANR的, 而什么情况下才会导致ANR。

2,一个线程有几个 Handler?一个线程有几个 Looper?如何保证?如何在子线程创建Looper?
这个题目也很坑爹, 线程和Handler不存在拥有的关系,每个线程都可以自己new一个handler,或者使用自己工作内存里的主存handler实例的副本,Handler我们分析过了,他只不过是一个管理类,所以和一个普通对象没有什么区别。
Looper我们来看下源码

public final class Looper {
   static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
   private static Looper sMainLooper;  // guarded by Looper.class
   final MessageQueue mQueue;
   final Thread mThread;

    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));
    }
 /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    public static void loop() {...
		 mQueue = new MessageQueue(quitAllowed);
	}
}

每个Looper创建的时候都必须调用Looper.prepare()方法(主线程Looper系统帮忙调用了),为什么呢,因为prepare方法里实例化了一个Looper对象,并且用ThreadLocal存储了他,以后再调用Looper.myLooper的时候,就直接从ThreadLocal里取,这时候在哪个线程调用Looper.myLooper,就会取出所在线程的Looper对象,所以这个题问的也不准确,线程有几个Looper。。。线程没有Looper,是在一个线程实例化Looper的时候(Looper.prepare()),后面在取出来就只能取到这个线程当年创造的这个Looper实例,可以看到这几个方法都是静态方法,这几个属性也都是静态属性,静态属性的引用是存在于虚拟机的方法区的,而且是GC root节点,所以不会被回收,才能保证在任何线程内,调用Looper的这些静态方法,都可以实现功能,如果问一个线程内能得到几个MsgQ,肯定也是一个,因为MsgQ是在Looper中实例化的。

2,Handler导致内存泄露是怎么回事?
这个是因为Handler持有了Aty的引用,用于收到消息时候操作Aty.xxx()
Handler如果作为内部类,直接持有外部类Aty的引用,如果作为独立的类,把Aty作为强引用传入,也是持有Aty的引用。当Aty destroy的时候,Handler还在发消息,接受消息,而此时handler的引用在msg手中

 Looper.loop(){
 	for(;;){
 		//我们知道 msg.target is handler
 		msg.target.dispatchMessage(msg);
 	}
 }

loop方法在执行,也就是在虚拟机栈中作为一个栈帧存在着,虚拟机栈的引用也是GC root节点,所以无法回收,而Aty的引用是强引用被Handler持有,就间接连入Gc Root 的引用链了。所以无法回收Aty且已经destroy,导致内存泄露。解决方式使用弱引用即可,当Activity被destroy之后,持有者被置空,调用者被虚拟机栈出栈,脱离GCroot引用链,而与这边的引用是弱引用,下一次GC的时候内存就会被回收掉。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值