ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android面试6家一线大厂,这个问题是必问

Android面试6家一线大厂,这个问题是必问 年后面了六家大厂每家都会问的一个问题就是Android的消息机制可见Android的消息机制是多么重要消息机制之所以这么重要是因为Android应用程序是通过消息来驱动的Android某种意义上也可以说成是一个以消息驱动的系统UI、事件、生命周期都和消息处理机制息息相关并且消息处理机制在整个Android知识体系中也是尤其重要在太多的源码分析的文章讲得比较繁琐很多人对整个消息处理机制依然是懵懵懂懂。这篇文章通过一些问答的模式结合Android主线程UI线程的工作原理来讲解源码注释很全还有结合流程图如果你对Android 消息处理机制还不是很理解我相信只要你静下心来耐心的看肯定会有不少的收获的。目录Android消息机制流程HandlerMessageMessageQueueLooperHandleThread文末福利篇外话最近想把自己比较薄弱的JavaAndroid基础抽时间进行学习加强些这也更符合自己的内心追求和自我期待。并行的开始另外一段学习旅程从Handler消息机制开启结合消息机制的流程以及源码进行学习分析。一、Android消息机制流程我们先通过下面两张图来对Android消息机制流程以及关键类之间的关系有个了解后面我们再结合源码一一进行分析。消息机制的流程Handler、Message、MessageQueue、Looper之间的关系二、HandlerHandler有两个主要的用途:调度消息在某个时间点执行;不同线程之间通信2.1 全局变量final Looper mLooper; //有Looper的引用 final MessageQueue mQueue;//有MessageQueue的引用 final Callback mCallback; final boolean mAsynchronous; IMessenger mMessenger;2.2 构造方法public Handler() { this(null, false); } public Handler(NonNull Looper looper) { this(looper, null, false); } public Handler(NonNull Looper looper, Nullable Callback callback, boolean async) { mLooper looper; mQueue looper.mQueue; mCallback callback; mAsynchronous async; }2.3 获取Message//从Message复用池中获取一个Message public final Message obtainMessage() { return Message.obtain(this); } //和上面的方法基本一致差异在于从复用池中获取到Message后给what赋值 public final Message obtainMessage(int what) { return Message.obtain(this, what); } //...其他obtainMessage类似2.4 发送消息下面我们挑几个发送方法来看下sendMessage: 发送一个Messagewhen为当前的时间MessageQueue根据when进行匹配插入位置public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis 0) { delayMillis 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() delayMillis); } public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue mQueue; ...... return enqueueMessage(queue, msg, uptimeMillis); }post从消息复用池中获取Message设置Message的Callbackpublic final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m Message.obtain(); m.callback r; return m; }postAtFrontOfQueue(): 将消息插入到队列头部通过调用sendMessageAtFrontOfQueue 加入一个when为0的message到队列即插入到队列的头部需要注意的是 MessageQueue#enqueueMessage的插入到链表中时是根据when比较的when p.when如果之前已经有多个when等于0的消息在队列中这个新的会加入到前面when也为0的后面。public final boolean postAtFrontOfQueue(Runnable r) { return sendMessageAtFrontOfQueue(getPostMessage(r)); } public final boolean sendMessageAtFrontOfQueue(Message msg) { MessageQueue queue mQueue; ...... //第三个参数为0即Message的when为0插入到队列的头部注意到MessageQueue#enqueueMessage的插入到链表中时是根据when比较的when p.when如果之前已经有多个when等于0的消息在队列中这个新的会加入到前面when也为0的后面。 return enqueueMessage(queue, msg, 0); }2.5 派发消息 dispatchMessage优先级如下Message的回调方法callback.run() Handler的回调方法mCallback.handleMessage(msg) Handler的默认方法handleMessage(msg)public void dispatchMessage(NonNull Message msg) { //Message的回调方法优先级最高 if (msg.callback ! null) { handleCallback(msg); } else { //Handler的mCallBack优先级次之 if (mCallback ! null) { if (mCallback.handleMessage(msg)) { return; } } //Handler的handleMessage方法优先级最低大部分都是在该方法中实现Message的处理 handleMessage(msg); } }三、Message全局变量//一些重要的变量 public int arg1; public int arg2; public Object obj; public long when; Bundle data; Handler target; //Message中有个Handler的引用 Runnable callback; //Message有next指针可以组成单向链表 Message next; public static final Object sPoolSync new Object(); //复用池中的第一个Message private static Message sPool; //复用池的大小默认最大50个如果短时间内有超过复用池最大数量的Message会怎样重新new private static int sPoolSize 0; private static final int MAX_POOL_SIZE 50;构造方法查看下是否有可以复用的message如果有复用池的中可复用的Message个数减一返回该Message如果没有重新new一个。注意复用池默认最大数量为50。public static Message obtain() { synchronized (sPoolSync) { //查看下是否有可以复用的message if (sPool ! null) { //取出第一个Message Message m sPool; sPool m.next; m.next null; m.flags 0; // clear in-use flag //复用池的中可复用的Message个数减一 sPoolSize--; return m; } } //如果复用池中没有Message了重新new return new Message(); }recycleUnchecked//标记一个Message时异步消息正常的情况都是同步的Message当遇到同步屏障的时候优先执行第一个异步消息。关于同步屏障我们在MessageQueue中在结合next等方法再介绍。public void setAsynchronous(boolean async) { if (async) { flags | FLAG_ASYNCHRONOUS; } else { flags ~FLAG_ASYNCHRONOUS; } } void recycleUnchecked() { // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. flags FLAG_IN_USE; what 0; arg1 0; arg2 0; obj null; replyTo null; sendingUid UID_NONE; workSourceUid UID_NONE; when 0; target null; callback null; data null; synchronized (sPoolSync) { //可以复用的message为50个如果超过了就不会再复用 if (sPoolSize MAX_POOL_SIZE) { next sPool; sPool this; sPoolSize; } } }//toString和dumpDebug可以Dump出message信息遇到一些问题时可以帮助分析android.os.Message#toString(long)android.os.Message#dumpDebug四、MessageQueueMessageQueue是一个单链表优先队列Message不能直接添加到MessageQueue中要通过Handler以及相对应的Looper进行添加。变量//MessageQueue链表中的第一个Message Message mMessages;next从消息队列中取出下一条要执行的消息如果是同步屏障消息找到第一个队列中中第一个异步消息如果第一个Message的执行时间比当前时间见还要晚记录还要多久开始执行;否则就找到下一条要执行的Message。后面的Looper的loop方法会从过queue.next调用该方法获取需要执行的下一个Message其中会调用到阻塞的native方法nativePollOnce该方法用于“等待”, 直到下一条消息可用为止. 如果在此调用期间花费的时间很长, 表明对应线程没有实际工作要做不会因此会出现ANRANR和这个没有半毛钱关系。关键代码如下Message next() { //native层MessageQueue的指针 final long ptr mPtr; if (ptr 0) { return null; } ...... for (;;) { //阻塞操作当等待nextPollTimeoutMillis时长或者消息队列被唤醒 //nativePollOnce用于“等待”, 直到下一条消息可用为止. 如果在此调用期间花费的时间很长, 表明对应线程没有实际工作要做不会因此会出现ANRANR和这个没有半毛钱关系。 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now SystemClock.uptimeMillis(); Message prevMsg null; //创建一个新的Message指向 当前消息队列的头 Message msg mMessages; //如果是同步屏障消息找到第一个队列中中第一个异步消息 if (msg ! null msg.target null) { do { prevMsg msg; msg msg.next; } while (msg ! null !msg.isAsynchronous()); } if (msg ! null) { //如果第一个Message的执行时间比当前时间见还要晚记录还要多久开始执行 if (now msg.when) { nextPollTimeoutMillis (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { //否则从链表中取出当前的Message 并且把链表中next指向指向下一个Message mBlocked false; if (prevMsg ! null) { prevMsg.next msg.next; } else { mMessages msg.next; } //取出当前的Message的值next置为空 msg.next null; msg.markInUse(); return msg; } } ..... //android.os.MessageQueue#quit时mQuitting为true //如果需要退出立即执行并返回一个null的Messageandroid.os.Looper.loop收到一个null的message后退出Looper循环 if (mQuitting) { dispose(); return null; } ...... if (pendingIdleHandlerCount 0) { // 注意这里如果没有消息需要执行mBlocked标记为true在enqueueMessage会根据该标记判断是否调用nativeWake唤醒 mBlocked true; continue; } ...... } ...... }enqueueMessage向消息队列中插入一条Message如果消息链表为空或者插入的Message比消息链表第一个消息要执行的更早直接插入到头部否则在链表中找到合适位置插入通常情况下不需要唤醒事件队列以下两个情况除外消息链表中只有刚插入的这一个Message并且mBlocked为true即正在阻塞状态收到一个消息后也进入唤醒链表的头是一个同步屏障并且该条消息是第一条异步消息唤醒谁MessageQueue.next中被阻塞的nativePollOnce具体实现如下关于如何找到合适的位置这涉及到链表的插入算法引入一个prev变量该变量指向p也message如果是for循环的内部第一次执行然后把p进行向next移动和需要插入的Message进行比较when关键代码如下boolean enqueueMessage(Message msg, long when) { ...... synchronized (this) { msg.markInUse(); msg.when when; Message p mMessages; boolean needWake; //如果消息链表为空或者插入的Message比消息链表第一个消息要执行的更早直接插入到头部 if (p null || when 0 || when p.when) { msg.next p; mMessages msg; needWake mBlocked; } else { //否则在链表中找到合适位置插入 //通常情况下不需要唤醒事件队列除非链表的头是一个同步屏障并且该条消息是第一条异步消息 needWake mBlocked p.target null msg.isAsynchronous(); //具体实现如下这个画张图来说明 //链表引入一个prev变量该变量指向p也message如果是for循环的内部第一次执行然后把p进行向next移动和需要插入的Message进行比较when Message prev; for (;;) { prev p; p p.next; if (p null || when p.when) { break; } if (needWake p.isAsynchronous()) { needWake false; } } msg.next p; prev.next msg; } //如果插入的是异步消息并且消息链表第一条消息是同步屏障消息。 //或者消息链表中只有刚插入的这一个Message并且mBlocked为true即正在阻塞状态收到一个消息后也进入唤醒 唤醒谁MessageQueue.next中被阻塞的nativePollOnce if (needWake) { nativeWake(mPtr); } } return true; }简单着看下native的epoll (这块还没有深入分析后面篇章补上吧)nativePollOnce 和 nativeWake 利用 epoll 系统调用, 该系统调用可以监视文件描述符中的 IO 事件. nativePollOnce 在某个文件描述符上调用epoll_wait, 而 nativeWake 写入一个 IO 操作到描述符epoll属于IO复用模式调用调用epoll_wait等待. 然后 内核从等待状态中取出 epoll 等待线程, 并且该线程继续处理新消息removeMessages: 移除消息链表中对应的消息需要注意的是在该函数的实现中分为了头部meg的移除和非头部的msg的移除。移除消息链表中头部的和需要移除相同的msgegmsg1.what0;msg2.what0;msg3.what0; msg4.what1; 需要移除what为0的msg即移除前三个移除消息链表中非头部的对应的消息eg:msg1.what1;msg2.what0;msg3.what0; 需要移除what为0的消息即移除后续的消息处处体现链表的查询和移除算法关键代码如下void removeMessages(Handler h, int what, Object object) { ...... synchronized (this) { Message p mMessages; //移除消息链表中头部的和需要移除相同的msg egmsg1.what0;msg2.what0;msg3.what0; msg4.what1; 需要移除what为0的msg即移除前三个 while (p ! null p.target h p.what what (object null || p.obj object)) { Message n p.next; mMessages n; p.recycleUnchecked(); p n; } //移除消息链表中非头部的对应的消息eg:msg1.what1;msg2.what0;msg3.what0; 需要移除what为0的消息即移除后续的消息处处体现链表的查询和移除算法 while (p ! null) { Message n p.next; if (n ! null) { if (n.target h n.what what (object null || n.obj object)) { Message nn n.next; n.recycleUnchecked(); p.next nn; continue; } } p n; } } }postSyncBarrier发送同步屏障消息同步屏障也是一个message只不过这个Message的target为null,. 通过ViewRootImpl#scheduleTraversals()发送同步屏障消息同步屏障消息的插入位置并不是都是消息链表的头部而是根据when等信息而定如果when不为0,消息链表也不空在消息链表中找到同步屏障要插入入的位置如果prev为空该条同步消息插入到队列的头部。关键代码如下/** * android.view.ViewRootImpl#scheduleTraversals()发送同步屏障消息 * param when * return */ private int postSyncBarrier(long when) { // Enqueue a new sync barrier token. // We dont need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final int token mNextBarrierToken; //同步屏障也是一个message只不过这个Message的target为null final Message msg Message.obtain(); msg.markInUse(); msg.when when; msg.arg1 token; Message prev null; Message p mMessages; if (when ! 0) { //如果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 { //如果prev为空该条同步消息插入到队列的头部 msg.next p; mMessages msg; } return token; } }dump MessageQueue信息有时候我们需要dump出当前looper的Message信息来分析一些问题比不是否Queue中有很多消息如果太多就影响队列中后面的Message的执行可能造成逻辑处理比较慢甚至可能导致ANR等情况MessageQueue的默认复用池是50个如果太多排队的Message也会影响性能。通过dump Message信息可以帮助分析。mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);void dump(Printer pw, String prefix, Handler h) { synchronized (this) { long now SystemClock.uptimeMillis(); int n 0; for (Message msg mMessages; msg ! null; msg msg.next) { if (h null || h msg.target) { pw.println(prefix Message n : msg.toString(now)); } n; } pw.println(prefix (Total messages: n , polling isPollingLocked() , quitting mQuitting )); } }五、LooperLooper主要涉及到构造、prepare和loop几个重要的方法在保证一个线程有且只有一个Looper的设计上采用了ThreadLocal以及代码逻辑的控制。变量//一些重要的变量 static final ThreadLocalLooper sThreadLocal new ThreadLocalLooper(); final MessageQueue mQueue; final Thread mThread;构造方法在构造Looper的时候 创建和Looper一一对应的MessageQueueprivate Looper(boolean quitAllowed) { //在构造Looper的时候 new一一对应的MessageQueue mQueue new MessageQueue(quitAllowed); mThread Thread.currentThread(); }prepare我们这里可以看到消息机制是 如何保证一个线程只有一个Looper。//quitAllowed参数是否允许quitUI线程的Looper不允许退出其他的允许退出 private static void prepare(boolean quitAllowed) { //保证一个线程只能有一个Looper这里的sThreadLocal if (sThreadLocal.get() ! null) { throw new RuntimeException(Only one Looper may be created per thread); } sThreadLocal.set(new Looper(quitAllowed)); }loop我们在MessageQueue的next方法已经分析过nativePollOnce这个方法可能会阻塞直到拿到message。如果next返回一个null的Message退出Looper循环否则进行msg的派发。取出的msg执行完之后会加入到回收池中等待复用。recycleUnchecked我们在Message中也已经分析过了。不清楚的可以再回看。public static void loop() { final Looper me myLooper(); if (me null) { throw new RuntimeException(No Looper; Looper.prepare() wasnt called on this thread.); } …… for (;;) { //next方法是一个会阻塞的方法MessageQueue的next方法前面我们已经分析过nativePollOnce这个方法会可能阻塞直到拿到message。 Message msg queue.next(); //收到为空的msgLoop循环退出。那么何时会收到为空的msg呐 quit if (msg null) { // No message indicates that the message queue is quitting. return; } //msg的派发msg.target就是Handler即调用Handler的dispatchMessage派发消息 msg.target.dispatchMessage(msg); …… //msg回收 msg.recycleUnchecked(); }六、HandleThreadHandlerThread是一个带有Looper的Thread。全局变量public class HandlerThread extends Thread { int mPriority;//线程优先级 int mTid -1;//线程id Looper mLooper; private Handler mHandler; ...... }构造方法public HandlerThread(String name) { super(name); //用于run时设置线程的优先级Process.setThreadPriority(mPriority); mPriority Process.THREAD_PRIORITY_DEFAULT; } public HandlerThread(String name, int priority) { super(name); mPriority priority; }run方法进行Looper的prepare和loop的调用配置好Looper环境Override public void run() { //线程id mTid Process.myTid(); //调用Looper的prepare方法,把当前该线程关联的唯一的Looper加入到sThreadLocal中 Looper.prepare(); synchronized (this) { //从sThreadLocal中获取Looper mLooper Looper.myLooper(); notifyAll(); } //设置线程的优先级默认THREAD_PRIORITY_DEFAULT如果是后台业务可以配置为THREAD_PRIORITY_BACKGROUND根据具体场景进行设置 Process.setThreadPriority(mPriority); //可以做一些预设置的操作 onLooperPrepared(); //开始looper循环 Looper.loop(); mTid -1; }使用HandlerThread的一般流程如下// Step 1: 创建并启动HandlerThread线程内部包含Looper HandlerThread handlerThread new HandlerThread(xxx); handlerThread.start(); // Step 2: 创建Handler Handler handler new Handler(handlerThread.getLooper()); handler.sendMessage(msg);这样有一个弊端就是每次使用Handler都要new HandlerThread而Thread又是比较占用内存能不能减少Thread的创建或者说是Thread的复用.并且实现Message能够得到及时执行不被队列中前面的Message阻塞这的确是一个有很有意思很有挑战的事情。七、资料最后的最后如果你打算开始读源码了可以先看看我整理的这份资料。《Android Framework精编内核解析》本笔记讲解了Framework的主要模块从环境的部署到技术的应用再到项目实战让我们不仅是学习框架技术的使用而且可以学习到使用架构如何解决实际的问题由浅入深详细解析Framework让你简单高效学完这块知识第一章深入解析BinderBinder机制作为进程间通信的一种手段基本上贯穿了andorid框架层的全部。所以首先必须要搞懂的Android Binder的基本通信机制。本章知识点Binder 系列—开篇Binder Driver 初探Binder Driver 再探Binder 启动 ServiceManager获取 ServiceManager注册服务(addService)获取服务(getService)Framework 层分析如何使用 Binder如何使用 AIDLBinder 总结Binder 面试题全解析第二章深入解析Handler本章先宏观理论分析与 Message 源码分析再到MessageQueue 的源码分析Looper 的源码分析handler 的源码分析Handler 机制实现原理总结。最后还整理Handler 所有面试题大全解析。Handler这章内容很长但思路是循序渐进的如果你能坚持读完我相信肯定不会让你失望。第三章Dalvik VM 进程系统Andorid系统启动、init 进程、Zygote、SystemServer启动流程、 应用程序的创建使用Activity的创建、销毁 Handler和Looper。第四章 深入解析 WMS窗口管理框架 系统动画框架 View的工作原理。第五章 PackagerManagerService包管理服务资源管理相关类。由于篇幅限制这里只能展示部分内容朋友们如果需要这份完整版的PDF资料合集微信扫描下方CSDN官方二维码【免费获取】。
返回列表