「每日一面」Handler机制及常见深浅问题

什么是Handler?

handler是更新UI界面的机制,也是消息处理的机制,我们可以发送消息,也可以处理消息

为什么Android要用Handler机制

Android应用程序启动时,系统会创建一个主线程,负责与UI组件(widget、view)进行交互,比如控制UI界面界面显示、更新等;分发事件给UI界面处理,比如按键事件、触摸事件、屏幕绘图事件等,因此,Android主线程也称为UI线程。

由此可知,UI线程只能处理一些简单的、短暂的操作,如果要执行繁重的任务或者耗时很长的操作,比如访问网络、数据库、下载等,这种单线程模型会导致线程运行性能大大降低,甚至阻塞UI线程,如果被阻塞超过5秒,系统会提示应用程序无相应对话框,缩写为ANR,导致退出整个应用程序或者短暂杀死应用程序。

除此之外,单线程模型的UI主线程也是不安全的,会造成不可确定的结果。线程不安全简单理解为:多线程访问资源时,有可能出现多个线程先后更改数据造成数据不一致。比如,A工作线程(也称为子线程)访问某个公共UI资源,B工作线程在某个时候也访问了该公共资源,当B线程正访问时,公共资源的属性已经被A改变了,这样B得到的结果不是所需要的的,造成了数据不一致的混乱情况。

线程安全简单理解为:当一个线程访问功能资源时,对该资源进程了保护,比如加了锁机制,当前线程在没有访问结束释放锁之前,其他线程只能等待直到释放锁才能访问,这样的线程就是安全的。

基于以上原因,Android的单线程模型必须遵守两个规则

1. 不要阻塞UI线程;

2. 不要在UI线程之外访问UI组件,即不能在子线程访问UI组件,只能在UI线程访问。

因此,Android系统将大部分耗时、繁重任务交给子线程完成,不会在主线程中完成,解决了第一个难题;同时,Android只允许主线程更新UI界面,子线程处理后的结果无法和主线程交互,即无法直接访问主线程,这就要用到Handler机制来解决此问题。基于Handler机制,在子线程先获得Handler对象,该对象将数据发送到主线程消息队列,主线程通过Loop循环获取消息交给Handler处理。

handler有哪些方法?1、post(Runnable) 2、postDelayed(Runnable ,long)

3、sentMessage 4、sentMessageDelayed

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

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 为异步消息

查看 queue 的 enqueueMessage 方法, 我们剥离出核心代码:

if(p ==null||when ==0||when

如果是新的队列头,直接插入队列

如果队列里面已经有消息了,执行如下逻辑

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.nextprev.next = msg;

插入消息的时候,一般不会唤醒消息队列。如果消息是异步的,并且队列头不是一个异步消息的时候,会唤醒消息队列

if (needWake) {nativeWake(mPtr);}

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

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

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占用的暴增?

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

消息队列构造的时候是否调用了jni部分

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(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 
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机制及常见深浅问题

备注

Java 层和 native 层通过 MessageQueue 里面持有一个 native 的MessageQueue 对象进行交互。WeakMessageHandler 继承自MessageHandler,NativeMessageQueue 继承自 MessageQueue

Java 层和 native 层实质是各自维护了一套相似的消息系统。C层发出的消息和Java层发出的消息可以没有任何关系。所以 Framework 层只是很巧的利用了底层 epoll 的机制达到阻塞的目的。

通过 pollOnce 的分析,可以发现消息的处理其实是有顺序的,首先是处理native message,然后处理native request,最后才会执行java层,处理java层的message

Handler的原理是什么?

答:1、Handler封装消息的发送(主要包括消息发送给谁)

2、Looper——消息封装的载体。(1)内部包含一个MessageQueue,所有的Handler发送的消息都走向这个消息队列;(2)Looper.Looper方法,就是一个死循环,不断地从MessageQueue取消息,如果有消息就处理消息,没有消息就阻塞。

3、MessageQueue,一个消息队列,添加消息,处理消息

4、handler内部与Looper关联,handler->Looper->MessageQueue,handler发送消息就是向MessageQueue队列发送消息。

总结:handler负责发送消息,Looper负责接收handler发送的消息,并把消息回传给handler自己。

MessageQueue存储消息的容器。

整个流程大概如图所示:

「每日一面」Handler机制及常见深浅问题

可以在子线程中创建Handler吗?为什么每个线程只会有一个Looper?

在很多时候,我们可以遇到这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 @NullableLooper 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()

就不会抛出异常了。

HandlerThread的作用是什么?

HandlerThread继承了Thread,是一个包含有looper的线程类。正常情况下,除了主线程,工作线程是没有looper的,但是为了像主线程那样也能循环处理消息,Android也自定义一个包含looper的工作线程——HandlerThread类。

HandlerThread thread=new HandlerThread("handler thread");自动含等待机制,等Looper创建好了,才创建Handler,避免出现空指针异常。

主线程

* ActivityThread 默认创建main线程,main中默认创建Looper,Looper默认创建MessageQueue

* threadLocal保存线程的变量信息,方法包括:set,get

Android更新UI的方式?

1、runOnUIThread 2、handler post 3、handler sendMessage

4、view post 5、AsyncTask

非UI线程真的不能更新UI吗?

不一定,之所以子线程不能更新界面,是因为Android在线程的方法里面采用checkThread进行判断是否是主线程,而这个方法是在ViewRootImpl中的,这个类是在onResume里面才生成的,因此,如果这个时候子线程在onCreate方法里面生成更新UI,而且没有做阻塞,就是耗时多的操作,还是可以更新UI的。

使用Handler遇到的问题?

答:比如说子线程更新UI,是因为触发了checkThread方法检查是否在主线程更新UI,还有就是子线程中没有Looper,这个原因是因为Handler的机制引起的,因为Handler发送Message的时候,需要将Message放到MessageQueue里面,而这个时候如果没有Looper的话,就无法循环输出MessageQueue了,这个时候就会报Looper为空的错误。

主线程怎么通知子线程?

答:可以利用HandlerThread进行生成一个子线程的Handler,并且实现handlerMessage方法,然后在主线程里面也生成一个Handler,然后通过调用sendMessage方法进行通知子线程。同样,子线程里面也可以调用sendMessage方法进行通知主线程。这样做的好处比如有些图片的加载啊,网络的访问啊可能会比较耗时,所以放到子线程里面做是比较合适的。

Handler可能引起的内存泄漏解决办法

因为Handler为内部类,内部类有对外部类的持续引用,所以Activity销毁,Handler可能还在引用,从而造成内存泄漏,可以使用弱引用或者在 Handler方法前加static生命为 static方法

Handler.post(new Runnable())和sendmessage(msg)区别

(1) 都是把消息放到消息队列等待执行,前者放的是一个runnable对象,后者是一个message对象;

(2) 前者最终还是会转化成sendMessage,只不过最终的处理方式不一样,前者会执行runnable的run方法;后者可以被安排到线程中执行。

(3) 两者本质没有区别,都可以更新UI,区别在于是否易于维护等。

欢迎补充,如果错误欢迎指正,谢谢


分享到:


相關文章: