摘要:本文在此文基础上,在源码级别上展开进行概述简单用例的使用方法如下所示或者又或者源码解析首先看其构造函数默认为,若为则会检测当前是否是静态类获得当前线程的获得上的由此引入了两个关键对象和。接下来看一下的构造函数。负责消息循环和操作对象。
之前写过一篇文章,概述了Android应用程序消息处理机制。本文在此文基础上,在源码级别上展开进行概述
简单用例Handler的使用方法如下所示:
Handler myHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { ... } } }; class myThread implements Runnable { public void run() { while (!Thread.currentThread().isInterrupted()) { Message message = Message.obtain(); message.what = TestHandler.GUIUPDATEIDENTIFIER; TestHandler.this.myHandler.sendMessage(message); message.recycle(); try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }
或者:
mHandler=new Handler(); mHandler.post(new Runnable(){ void run(){ ... } });
又或者:
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); } }源码解析
首先看其构造函数:
new Handler() ... public Handler() { this(null, false); } ... public Handler(Looper looper, Callback callback) { this(looper, callback, false); } ... public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { // 默认为false,若为true则会检测当前handler是否是静态类 final Class extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } // 1. 获得当前线程的looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can"t create handler inside thread that has not called Looper.prepare()"); } //2. 获得looper上的message queue mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
由此引入了两个关键对象Looper和MessageQueue。
先看 mLooper = Looper.myLooper(); 这一句发生了什么:
public static Looper myLooper() { return sThreadLocal.get(); }
可以看到,该方法返回一个sThreadLocal对象中保存的Looper。关于ThreadLocal类,请参考这里,本文不展开。
如果尚未在当前线程上运行过Looper.prepare()的话,myLooper会返回null。接下来看看Looper.prepare()的实现:
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对象,并将其保存在sThreadLocal中。接下来看一下Looper的构造函数。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
调用完Looper.prepare()后,需调用Looper.loop()才能使消息循环运作起来,其源码如下所示:
public static void loop() { final Looper me = myLooper(); //1. 取出looper对象 if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn"t called on this thread."); } final MessageQueue queue = me.mQueue; //2. 取出looper绑定的message queue // 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(); // loop time. long tm = 0; ... for (;;) { Message msg = queue.next(); // 3. 堵塞式在message queue中取数据 if (msg == null) { // No message indicates that the message queue is quitting. return; } ... msg.target.dispatchMessage(msg); 4. 分发message到指定的target handler ... // Make sure that during the course of dispatching the // identity of the thread wasn"t corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { ... } msg.recycleUnchecked(); // 5. 回收message对象 ... } }
可以简单地将Looper.loop()理解成一个不断检测message queue是否有数据,若有即取出并执行回调的死循环。 接下来看一下Message类:
public final class Message implements Parcelable { public int what; public int arg1; public int arg2; public Object obj; ... /*package*/ int flags; /*package*/ long when; /*package*/ Bundle data; /*package*/ Handler target; /*package*/ Runnable callback; /*package*/ Message next; private static final Object sPoolSync = new Object(); private static Message sPool; private static int sPoolSize = 0; }
what、arg1、arg2这些属性本文不作介绍,我们把目光集中在next、sPoolSync、sPool、sPoolSize这四个静态属性上。
当我们调用Message.obtain()时,返回了一个Message对象。Message对象使用完毕后,调用recycle()方法将其回收。其中obtain方法的代码如下所示:
public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }
可以看到,obtain方法被调用时,首先检测sPool对象是否为空,若否则将其当做新的message对象返回,并“指向"message对象的next属性,sPoolSize自减。可以看出message对象通过next属性串成了一个链表,sPool为“头指针”。再来看看recycle方法的实现:
public void recycle() { if (isInUse()) { if (gCheckRecycle) { throw new IllegalStateException("This message cannot be recycled because it is still in use."); } return; } recycleUnchecked(); } 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 = -1; when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }
如果message对象不是处于正在被使用的状态,则会被回收。其属性全部恢复到原始状态后,放在了链表的头部。sPool对象“指向”它,sPoolSize自增。
综上可以看出,通过obtain和recycle方法可以重用message对象。通过操作next、sPoolSync、sPool、sPoolSize这四个属性,实现了一个类似栈的对象池。
msg.target为handler类型,即向handler成员的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对象,但仍不知道msg对象是如何放到消息队列里面的。通常来说,我们通过Handler的sendMessage(msg)方法来发送消息,其源码如下所示:
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; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); } ... private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
可知sendMessage最终会调用queue.enqueueMessage(msg, uptimeMillis)将msg对象保存至message queue中,uptimeMillis表示msg执行回调的时刻。 我们来看一下MessageQueue类的enqueueMessage方法:
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w("MessageQueue", e.getMessage(), e); msg.recycle(); return false; } // 1.设置当前msg的状态 msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; // 2.检测当前头指针是否为空(队列为空)或者没有设置when 或者设置的when比头指针的when要前 if (p == null || when == 0 || when < p.when) { // 3. 插入队列头部,并且唤醒线程处理msg msg.next = p; mMessages = msg; needWake = mBlocked; } else { //4. 几种情况要唤醒线程处理消息:1)队列是堵塞的 2)barrier,头部结点无target 3)当前msg是堵塞的 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; } } // 5. 将当前msg插入第一个比其when值大的结点前。 msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; }
结合注释,我们可以了解到msg push到queue中时,queue的状态的变化和处理队列的逻辑。
前文中Looper对象的loop方法中:
for (;;) { ... Message msg = queue.next(); // 3. 堵塞式在message queue中取数据 if (msg == null) { // No message indicates that the message queue is quitting. return; } ... msg.target.dispatchMessage(msg); 4. 分发message到指定的target handler ... }
可以看出,message queue的next方法被调用时,可能会发生堵塞。我们来看一看message queue的next方法:
Message next() { // 1. 判断当前loop是否已经使用过,下文会解释这个mPtr final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; // 2. 进入死循环,直到获取到合法的msg对象为止。 for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); // 这个是什么? } // 3. 进入等待,nextPollTimeoutMillis为等待超时值 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // 4. 获取下一个msg final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // 当前节点为barrier,所以要找到第一个asynchronous节点 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // 当前队列里最早的节点比当前时间还要晚,所以要进入堵塞状态,超时值为nextPollTimeoutMillis nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 删除当前节点,并返回 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (false) Log.v("MessageQueue", "Returning message: " + msg); return msg; } } else { // 头结点指向null nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } // 5. 如果当前状态为idle(就绪),则进入idle handle的代码块 // 进入idle的情况有:队列为空;队列头元素blocking; if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // 6. 本轮唤醒(next被调用)时没处理任何东西,故再次进入等待。 mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // 在一次next调用中,这个代码块只会执行一次 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 { // 如果返回true,则idler被保留,下次next的idle时会被调用。 keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf("MessageQueue", "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; } }
代码执行流程见注释。其中IdleHandler是一个接口:
public static interface IdleHandler { boolean queueIdle(); }
IdleHandler提供了一个在MessageQueue进入idle时的一个hook point。更多时与barrier机制一起使用,使message queue遇到barrier时产生一个回调。
总结前面涉及到的几个主要的类Handler、Looper、MessageQueue和Message的关系如下所述:
Handler负责将Looper绑定到线程,初始化Looper和提供对外API。
Looper负责消息循环和操作MessageQueue对象。
MessageQueue实现了一个堵塞队列。
Message是一次业务中所有参数的载体。
框架图如下所示:
+------------------+ | Handler | +----+--------^----+ | | send | | dispatch | | v | +----- <---+ | | | Looper | | | | | +---> -----+ | ^ enqueue| | next | | +--------v------+----------+ | MessageQueue | +--------+------^----------+ | | nativePollOnce | | nativeWake | | +-----------------v------+---------------------+ Lower Layer
最后,留意到MessageQueue中有4个native方法:
// 初始化和销毁 private native static long nativeInit(); private native static void nativeDestroy(long ptr); // 等待和唤醒 private native static void nativePollOnce(long ptr, int timeoutMillis); private native static void nativeWake(long ptr); // 判断native层的状态 private native static boolean nativeIsIdling(long ptr);
将会在后续文章中进行介绍。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/12423.html
摘要:例如上例,只能定位到崩溃在这个方法中,而具体是由于哪个变量为空导致的空指针,则无能为力了。前提简单来说,用上述方法需要两个前提条件有崩溃发生的设备崩溃栈中有行数信息 在我们的漫漫解bug的路上,有一类bug是比较头疼的:崩溃在SDK实现代码中,例如下面这个: java.lang.NullPointerException: java.lang.NullPointerException...
摘要:原文地址游客前言金三银四,很多同学心里大概都准备着年后找工作或者跳槽。最近有很多同学都在交流群里求大厂面试题。 最近整理了一波面试题,包括安卓JAVA方面的,目前大厂还是以安卓源码,算法,以及数据结构为主,有一些中小型公司也会问到混合开发的知识,至于我为什么倾向于混合开发,我的一句话就是走上编程之路,将来你要学不仅仅是这些,丰富自己方能与世接轨,做好全栈的装备。 原文地址:游客kutd...
摘要:原文地址游客前言金三银四,很多同学心里大概都准备着年后找工作或者跳槽。最近有很多同学都在交流群里求大厂面试题。 最近整理了一波面试题,包括安卓JAVA方面的,目前大厂还是以安卓源码,算法,以及数据结构为主,有一些中小型公司也会问到混合开发的知识,至于我为什么倾向于混合开发,我的一句话就是走上编程之路,将来你要学不仅仅是这些,丰富自己方能与世接轨,做好全栈的装备。 原文地址:游客kutd...
摘要:今天,我我的后端书架后端掘金我的后端书架月前本书架主要针对后端开发与架构。尤其是对称加密,非对称加密,私钥加密,公钥加密滴滴动态化方案的诞生与起航掘金这是滴滴架构组发布的第一篇公共技术文章,本文将介绍自研的动态化方案。 android 阿里面试题锦集 - Android - 掘金前几天突然就经历了阿里android实习内推的电面,感觉有好多以前看过的东西都忘记了,然后又复习了一下,找了...
摘要:今天,我我的后端书架后端掘金我的后端书架月前本书架主要针对后端开发与架构。尤其是对称加密,非对称加密,私钥加密,公钥加密滴滴动态化方案的诞生与起航掘金这是滴滴架构组发布的第一篇公共技术文章,本文将介绍自研的动态化方案。 android 阿里面试题锦集 - Android - 掘金前几天突然就经历了阿里android实习内推的电面,感觉有好多以前看过的东西都忘记了,然后又复习了一下,找了...
阅读 2628·2021-11-24 09:39
阅读 3474·2021-10-27 14:19
阅读 1875·2021-08-12 13:25
阅读 2239·2019-08-29 17:07
阅读 1009·2019-08-29 13:44
阅读 472·2019-08-27 14:27
阅读 938·2019-08-26 12:17
阅读 332·2019-08-23 17:16