赞
踩
个人博客:haichenyi.com。感谢关注
Android程序是一个以消息驱动的程序,页面的跟新,Activity生命周期的变化,点击事件等等都与消息息息相关。
简单的理解Handler发送消息的流程:Handler发送消息(message)到MessageQueue,然后,Looper通过loop()方法循环从MessageQueue里面读取消息。然后,发送给对应的target(Handler)。
我们带着问题来理解这个流程,最后,我们在重新总结一下。辣么问题就来了:
我们结合源码一起来看一下这些问题:
第一个问题,handler都是一样的,为什么Looper会分Looper.getMainLooper()和普通的Looper?我们都知道
Handler handler = new Handler(Looper.getMainLooper())
通过这个Looper.getMainLooper()方式得到得Handler,可以改变UI,其他的不行,这是为什么呢?我们都知道,UI线程才能改变UI。
ps:app的启动入口是在ActivityThread的main方法。
捡一些 (我看的懂的),呸,是主要的,跟我们聊的这个相关的位置贴出来,源码如下:
public static void main(String[] args) { ... //loop调用了一个准备MainLooper方法(按照方法的名字意思翻译的) Looper.prepareMainLooper(); ... ActivityThread thread = new ActivityThread(); thread.attach(false, startSeq); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } ... //Looper调用loop()方法进入了循环模式 Looper.loop(); //如果走到这里,那就没有进入循环模式,就抛出异常了,异常字面意思很好理解,主线程的loop意外的退出了 throw new RuntimeException("Main thread loop unexpectedly exited"); }
注释应该写的比较清楚了吧?这里我想说的是,main启动的时候,这个线程就是UI线程,这个是系统给规定的,只有在这个线程里面才能改变UI。
我们再来看看这个Looper.prepareMainLooper()里面做了什么操作
@Deprecated public static void prepareMainLooper() { //调用prepare方法,传了一个false的Boolean值 prepare(false); //锁 synchronized (Looper.class) { //sMainLooper不等于null,就抛异常 if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } //等于null,就把myLooper()方法的返回值赋值给sMainLooper sMainLooper = myLooper(); } } //prepare,一个Boolean类型的参数,看名字意思应该是:是否允许退出 private static void prepare(boolean quitAllowed) { //sThreadLocal.get()值不等于null,就抛异常 if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //sThreadLocal.get()值等于null,就new一个Looper,放到sThreadLocal中 sThreadLocal.set(new Looper(quitAllowed)); } //Looper的构造方法,我们此时主线程new的时候传的是false private Looper(boolean quitAllowed) { //新建了一个消息队列,MsgQueue,并且把这个boolean传进去了,赋值给mQueue变量 mQueue = new MessageQueue(quitAllowed); //把当前线程赋值给了mThread变量 mThread = Thread.currentThread(); } //消息队列的构造方法,Boolean类型的参数,到这里就应该知道了,表示这个线程是否允许退出,true:允许退出。false:不允许退出 MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; //nativeInit():native方法,不知道是啥,应该是一些需要的初始化 mPtr = nativeInit(); } //sThreadLocal.get()的值返回回去 public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
注释都有了,最开始调用的prepare(false)方法,看完上面的注释,我大致串一下。
ps:说一下这个sThreadLocal变量,我也不知道怎么说,反正就是它的类型是:ThreadLocal,它就是一个普通的类,然后泛型是Looper,所以,这个类就是用来放Looper的。大致这么理解
我们再说回这个方法,主要就是,
prepare()方法,到这里就说完了,我们再看剩下的代码,往上面翻一下,再看一下。
ps:sMainLooper变量,类型就是Looper
剩下的代码就是一个锁方法,
我们Looper.getMainLooper()获取的Looper就是这个sMainLooper,也就是我们当前线程(UI线程)的Looper,我们只有绑定了这个looper的handler才能改变UI,因为,这个handler是在给UI线程传递消息。
为什么不等于null就抛出异常了呢?因为sMainLooper只在系统的时候创建,不能在其他的时候创建,如果,在其他的时候创建,说明系统启动的时候没有创建Looper,那么,主线程就没法通信,这是有问题的。
第一个问题我说明白了吧?Looper.getMainLooper()获取到的是主线程的Looper,跟它绑定的handler能改变UI,没有跟它绑定的hanler都不能改变UI
第二个问题,handler发送的消息过程是什么样子的?
说到这里,我们先聊一下Message类
public final class Message implements Parcelable { public int what; public int arg1; public int arg2; public Object obj; ... public long when; @UnsupportedAppUsage /*package*/ Handler target; ... @UnsupportedAppUsage /*package*/ Message next; /** @hide */ public static final Object sPoolSync = new Object(); private static Message sPool; private static int sPoolSize = 0; private static final int MAX_POOL_SIZE = 50;
写代码这么长时间,我们发了那么多消息,是不是都没有仔细看看Message的成员变量?看看上面这几个变量。
什么池子啊,什么最大值啊。我相信很多人跟我的反应都是一样的,线程池,复用。所以这里就是消息池,消息能复用,消息池最大的消息个数是50个,异步。
延申到这里,引出我想问的第一个问题,消息的创建,消息创建的两种方式:一种是new出来,一种是obtain的方式,它有一系列的重载方法。
//第一种:new的方式
Message msg1 = new Message();
msg1.what = 1;
msg1.arg1 = 20;
handler.sendMessage(msg1);
//第二种:obtain的方式
Message msg = Message.obtain();
msg.what =1;
msg1.arg1 = 20;
handler.sendMessage(msg);
第一种没啥好说的,我们看第二种:Message.obtain()
public static Message obtain() { //加锁 synchronized (sPoolSync) { //判断sPool是否为空 if (sPool != null) { //sPool不为空,就复用sPool msg对象 Message m = sPool; //然后,把m的next赋值给sPool sPool = m.next; //然后,把已经去出去的msg的额next置空 m.next = null; m.flags = 0; // clear in-use flag //这时候,消息池已经去出去了一条消息,消息池大小就减一 sPoolSize--; return m; } } //sPool为空,就new一个msg对象 return new Message(); }
简单的理解就是,不需要重新创建消息,从消息池里面取出一条消息,赋值给我们需要创建的msg对象。
这里为什么要加锁?什么情况下需要加锁?当然是防止并发呀,handler可以随时随地的发消息,所以,为了防止并发,加锁。
问题来了,这个sPool是什么时候赋值的呢?我们创建消息的时候没有赋值。创建的时候没有赋值,我们在Message类里面,检索sPool对象,我们找到如下方法:
@UnsupportedAppUsage void recycleUnchecked() { //重置一些列的成员变量 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) { //当前消息池子是否小于限制的最大值 if (sPoolSize < MAX_POOL_SIZE) { //把sPool赋值给next next = sPool; //sPool赋值现在的消息 sPool = this; //消息池子大小加1 sPoolSize++; } } }
看这个方法名就应该能猜到,消息回收的时候调用的。所以,在消息回收的时候,就把这条消息重置,把这条回收的消息赋值给sPool,这里就是赋值的位置。在消息回收的时候赋值。
所以,只要你并发量不大,你每次都是obtain创建消息,基本上都是复用的,不会重新创建消息。
消息说完了,跑题了,跑题了,言归正传
handler发送消息的流程
欢迎来到走进科学之Android消息发送流程,我们来一步一步的剖析这条消息是怎么一步一步放进消息队列的。
Message msg = Message.obtain();
msg.what =1;
msg1.arg1 = 20;
handler.sendMessage(msg);
我们来看这个sendMessage的源码。
public final boolean sendMessage(@NonNull Message msg) { //是不是眼熟,没错,它实际上调用的就是我们延时消息的方法,只不过,这个延时的时间是0 return sendMessageDelayed(msg, 0); } //我们再来看看这个sendMessageDelayed方法 public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) { //这里有个判断时间,小于0,就赋值给0,所以,发送延时消息的时候时间传递负数,会立马接收到消息,知道是为什么了吧? if (delayMillis < 0) { delayMillis = 0; } //这里又调用的sendMessageAtTime方法 return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } //这里我想说的是SystemClock.uptimeMillis():表示系统开机时间 //我们再来看看这个sendMessageAtTime方法 public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { //这里有一个消息队列的判断,这个消息队列是在Handler创建的时候赋值的。 //可以点进去看一下。Hander构造方法传递一个Looper,Looper构造方里面创建了msgQueue,就是这个。 MessageQueue queue = mQueue; //如果是空,就抛异常,因为消息队列都没有,循环啥? if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } //这里又调用了enqueueMessage return enqueueMessage(queue, msg, uptimeMillis); } private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { //这里,我们前面说消息的时候,说很重要,就是在handler发送消息的这里赋值,这个值也是后面Looper发送给哪个handler的依据。 msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); //这个是判断你的这个消息是不是异步,提升消息优先级的位置。同步消息,同步屏障,异步消息。 if (mAsynchronous) { msg.setAsynchronous(true); } //这里就开始进入到消息队列了 return queue.enqueueMessage(msg, uptimeMillis); }
msg.target是后面Looper拿到这条消息之后,发送的目的地,不然,Looper怎么知道要发送给谁(handler)?
提升消息优先级的位置。同步消息,同步屏障,异步消息。也是比较重要,后面再细唠。
到这里handler的发送就完了,MessageQueue怎么把这条消息放进去的呢?方法如下:
boolean enqueueMessage(Message msg, long when) { //判断有没有目标handler,如果没有,直接就抛异常,都没有这个目的地,我最后取出这条消息,我发给谁?所以,直接就抛异常 if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } //加锁,不加锁,如果很多消息同时需要加紧队列就会出问题 synchronized (this) { //判断这条消息是否正在使用,如果正在使用,那也抛异常。为什么消息会正在使用呢? //我们前面说了obtain方式消息是复用的,发送消息会并发,所以,是吧? if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } //如果,当前msgQueue正在退出,把消息回收了。 //比方说,你新建线程请求网络,网络请求完,线程一般就会死掉了,线程都没有了,MsgQueue当然要退出了。 if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } //走到这里,消息就准备放进消息队列了,就是放在那里的问题 //给消息加个标记,表示消息正在使用。跟前面那个判断正好对应 msg.markInUse(); //这个时间,还记不记得?系统开机时间+你延时的时间 msg.when = when; //把消息队列的当前消息赋值给p Message p = mMessages; //是否需要唤醒线程,唤醒跟休眠都是native方法。 boolean needWake; //当前消息是空,说明当前消息队列没有消息,就直接把我们传递的这条消息加进队列 //我加进来的这条消息的执行时间是0,时间是不会有负数的,如果传进来是负数,都被改成0了,所以,我加的这条消息应该是最先执行的,所以,要加进队列 //加进来的这条消息的执行时间小于当前线程的执行时间,我加进来的这条消息执行的时间,比你当前消息队列循环的时间小,说明,我要在它的前面执行,要加进队列 //上面这个时间小的问题,你可以理解成,消息队列循环的时间是延时10秒处理的,我新进的这条消息是要延时5秒,所以,要放在它的前面 if (p == null || when == 0 || when < p.when) { //走到这里,说明新加消息需要放在队首 //我新加的消息放进来了之后,要把当前消息的后面,也就是我新加消息的next //因为,我新加的消息要在它的前面执行 msg.next = p; //然后,把我新加消息赋值给当前消息变量 mMessages = msg; needWake = mBlocked; } else { //走到这个else里面,就说明当前消息不需要放到队首,就循环判断看它要被放在哪 needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; //进入死循环 for (;;) { prev = p; p = p.next; //如果当前msg的下一个消息是空,表示没有消息了,for循环就要中止了,需要把新加消息放进来了 //如果当前消息的下一条消息的执行时间在新加的执行时间的后面,说明,新加消息要在这条消息的前面执行。所以,for循环就要中止了,需要把新加消息放进来了 if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } //把当前消息放在新加消息的后面 msg.next = p; //把新加消息,放在当前执行消息的后面。此时,消息就插件队列了 prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. //是否需要唤醒消息队列开始循环获取消息,是native层面做的事情。 if (needWake) { nativeWake(mPtr); } } return true; }
总结下来就是:三个条件
来判断当前消息是否需要插到队首,只要满足上面的任意一个条件,就需要放进队首;否则,for循环判断当前消息需要放到消息队列的哪个位置。需要插队的话就记得把队列中后面的消息放到当前消息的后面。
再重复一遍,这个时间是SystemClock.uptimeMillis() + delayMillis,系统开机时间+你传递的延时时间。
到这里,消息就被插件消息队列了。代码基本上每行都有注释,一遍没有看懂的话就多看几遍。
消息已经放进队列了,第二个问题就结束了,接下来就是第三个问题:Looper怎么读取消息的?
Looper是通过loop()方法循环读取消息的。代码如下:
代码比较多,我把无关的(看不懂的)都去掉了
public static void loop() { //获取当前线程的looper final Looper me = myLooper(); //如果,等于null,就抛异常,看异常的消息就应该看的出来,说没有在当前线程调用Looper.prepare()方法 //Looper.prepare()这个方法就是创建Looper的 if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } ... //获取当前线程的消息队列 final MessageQueue queue = me.mQueue; ... //进入死循环读取消息 for (;;) { //读取队列中的下一条消息,可能会阻塞线程 Message msg = queue.next(); //如果,没有消息了,就退出循环,进入休眠状态 if (msg == null) { // No message indicates that the message queue is quitting. return; } ... //获取观察者模式的对象 final Observer observer = sObserver; ... Object token = null; if (observer != null) { //这里应该是这个观察者对象发送了一个消息正在分发的消息 token = observer.messageDispatchStarting(); } ... try { //msg.target:是不是很眼熟?就是需要接收这条消息的handler //通过这个handler调用dispatchMessage方法,发送消息 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); } } ... //眼熟不?就是前面说的,消息回收,重复利用,就是在消息分发完成之后触发 msg.recycleUnchecked(); } }
我们先看一下这个handler的dispatchMessage方法:
public void dispatchMessage(@NonNull Message msg) { //这个Message的callback是什么时候赋值的呢?就是创建Message的时候,可以回过头去看一下 if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //这个方法,眼熟吗?看下面,我们新建handler的时候,不就重写了这个方法吗? handleMessage(msg); } } Handler handler = new Handler(Looper.getMainLooper()){ @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); } };
到此,消息的发送,入队,取消息,处理,就形成了闭环了。整个流程:
我们接下来说第四个问题:handler发送消息能发送延时消息,Looper读取到消息之后,怎么确定是立刻发送回去,还是隔多久发送回去?
我们上面分析完,好像并没有看到这个延时消息的问题啊,Looper的loop方法是,只要queue.next()返回给它消息了,它就直接发送回去了,没有什么延时。
重点就在这里queue.next(),读取消息。这里也是提升消息优先级的位置(同步屏障,异步消息)。
@UnsupportedAppUsage Message next() { final long ptr = mPtr; //通过注释翻译过来就是:loop已经退出了,或者应用正在尝试重启一个looper,就直接return null if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration //下一次循环的时间,单位是:秒 int nextPollTimeoutMillis = 0; //开始进入死循环去读取消息 for (;;) { if (nextPollTimeoutMillis != 0) { //不知道啥意思。 Binder.flushPendingCommands(); } //这是一个native方法,看名字,大概的意思应该就是循环一次,经过nextPollTimeoutMillis长的时间 //就是底层C/C++经过这么长时间之后,触发一次循环 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; //屏障消息的实质就是创建一条target为null的消息 //看这里的if条件,正常的消息target不等于null,这里的判断是不会进入的。 if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. //再看这里的do...while循环,退出的条件是找到一条不为空的异步消息。 //msg.isAsynchronous():异步消息返回true,取反之后就是false,更前面&&,就是false,就退出do..while循环了 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. //修改这个下次循环的时间,前面说的native调用的时间,就是根据这个变量判断的。 //时间就是消息执行的时间-系统开机时间=延时时间 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { //否则就是一条实时消息,就是正常的赋值流程,返回这条消息给looper,然后发送出去 // 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 { //消息是空,就表示没有更多消息了 nextPollTimeoutMillis = -1; } ... } }
流程就是:
看到了吗?MessageQueue取消息的流程,通过msg的执行时间与当前系统的开机时间进行比较,延时消息就是判断了延时多长时间之后,告诉底层多长时间之后,你还要调用一次这个取消息的方法。这就是延时消息的实现。
既然说到这里,我们就直接聊一下这个消息的优先级
ps:这里的异步消息,同步消息,并不是说多线程去处理消息。异步消息是有一个属性是true
//同步消息
Message msg = Message.obtain();
//异步消息,调用了一个setAsynchronous并且设置为true
Message msg = Message.obtain();
msg.setAsynchronous(true);
我们平时发消息是下面这个样子的:
Message msg1 = Message.obtain(handler,new Runnable(){ @Override public void run() { Log.v("hcy","这是一条延时3秒的消息"); } }); handler.sendMessageDelayed(msg1,3*1000); Message msg = Message.obtain(handler, new Runnable() { @Override public void run() { Log.v("hcy","这是一条延时5秒的异步消息"); } }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { msg.setAsynchronous(true); } handler.sendMessageDelayed(msg,5*1000); Log.v("hcy","两条消息都发送完了");
上面就是new了两条消息,一条同步消息,一条异步消息,如果没有屏障消息的情况下,同步消息和异步消息是一样的,没啥区别。程序运行完,过三秒钟同步消息回调,再过两秒打印异步消息回调,上面消息的打印结果如下:
2021-11-21 08:53:10.363 29452-29452/com.haichenyi.myapplication V/hcy: 两条消息都发送完了
2021-11-21 08:53:13.366 29452-29452/com.haichenyi.myapplication V/hcy: 这是一条延时3秒的消息
2021-11-21 08:53:15.365 29452-29452/com.haichenyi.myapplication V/hcy: 这是一条延时5秒的异步消息
那么,什么是屏障消息呢?怎么实现呢?我们先说怎么实现的。
Message msg1 = Message.obtain(handler,new Runnable(){ @Override public void run() { Log.v("hcy","这是一条延时3秒的消息"); } }); handler.sendMessageDelayed(msg1,3*1000); Message msg = Message.obtain(handler, new Runnable() { @Override public void run() { Log.v("hcy","这是一条延时5秒的异步消息"); //异步消息处理完移除消息屏障 try { Class<?> msgQueue = Class.forName("android.os.MessageQueue"); Method removeSyncBarrier = msgQueue.getDeclaredMethod("removeSyncBarrier", int.class); removeSyncBarrier.invoke(Looper.myQueue(),token); }catch (Exception e){ e.printStackTrace(); } } }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { msg.setAsynchronous(true); } handler.sendMessageDelayed(msg,5*1000); try { //启动消息屏障 Class<?> msgQueue = Class.forName("android.os.MessageQueue"); Method postSyncBarrier = msgQueue.getDeclaredMethod("postSyncBarrier"); token = (int) postSyncBarrier.invoke(Looper.myQueue()); }catch (Exception e){ e.printStackTrace(); } Log.v("hcy","两条消息都发送完了");
打印结果如下:
2021-11-21 09:05:36.915 29743-29743/com.haichenyi.myapplication V/hcy: 两条消息都发送完了
2021-11-21 09:05:41.922 29743-29743/com.haichenyi.myapplication V/hcy: 这是一条延时5秒的异步消息
2021-11-21 09:05:41.949 29743-29743/com.haichenyi.myapplication V/hcy: 这是一条延时3秒的消息
代码执行完之后,先是过了5秒回调了异步消息,然后立刻回调了同步消息,为什么呢?因为,同步消息是延时3秒执行呀,异步消息是延时5秒执行,因为加了消息屏障,会把异步消息的优先级提到同步消息的前面,所以,执行完异步消息,同步消息的执行时间早就过了,肯定要立刻执行呀。
说了这么多,那么,这个提升优先级是怎么实现的呢?透过现象去看本质。两段代码的区别,就是通过反射,执行了两个方法postSyncBarrier,removeSyncBarrier。其中还有一个带参数的方法。
//执行消息屏障 try { Class<?> msgQueue = Class.forName("android.os.MessageQueue"); Method postSyncBarrier = msgQueue.getDeclaredMethod("postSyncBarrier"); token = (int) postSyncBarrier.invoke(Looper.myQueue()); }catch (Exception e){ e.printStackTrace(); } //移除消息屏障 try { Class<?> msgQueue = Class.forName("android.os.MessageQueue"); Method removeSyncBarrier = msgQueue.getDeclaredMethod("removeSyncBarrier", int.class); removeSyncBarrier.invoke(Looper.myQueue(),token); }catch (Exception e){ e.printStackTrace(); }
我们先来看看这个消息屏障的方法:
@UnsupportedAppUsage @TestApi //我们反射调用的是这个方法,它最终执行的是一个同样名字的带参的重载方法 public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis()); } //最终执行到这里 private int postSyncBarrier(long when) { //以来还是老规矩,加锁,防止多线程调用 synchronized (this) { //token比较重要,是移除屏障的标记 final int token = mNextBarrierToken++; //常规的msg的创建,msg执行的时间是系统的开机时间 final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; //把这个token值赋值给了msg的arg1变量 msg.arg1 = token; //说白了,下面就是链表操作了,不停的移动指针 //prev:上一条消息变量 Message prev = null; //p:消息。 //mMessages:这个变量眼熟不?我们前面消息从队列中取的时候就是那个next()方法, //在进入for循环里面,判断是否是屏障消息之前,是不是也同样是给一个成员变量赋值,赋值的值也是mMessages。 Message p = mMessages; //执行时间不等于0,前面取的时候那三个条件,有一个时间等于0,就插进消息队首,这里我觉得也可以这样理解 if (when != 0) { //这里是一个while循环,字面理解就是当前消息不等于null,当前消息的执行时间,小于屏障消息的执行时间,就继续循环。直到这两个条件不满足为止 //结合上下文的意思就是,我执行这个屏障消息的时候,如果发现队列里面还有消息的执行时间在我这个屏障消息的前面,就继续让它先执行。 while (p != null && p.when <= when) { //把当前消息赋值给变量prev prev = p; //把当前消息的下一条消息,赋值给p变量(当前消息变量) p = p.next; //继续while循环 } } //经过上面的while循环之后,就找准了屏障消息该插入的为止了 if (prev != null) { //如果prev不等于null,表示,消息队列里面还有需要在屏障消息前面执行的消息 //队列就变成了:prev-屏障消息-当前消息 msg.next = p; prev.next = msg; } else { //如果prev等于null,就表示消息队列里面没有需要在屏障消息执行前面执行的消息了 //队列也就变成了:屏障消息-当前消息 msg.next = p; mMessages = msg; } //返回这个token值。移除屏障消息的时候需要用到 return token; } }
上面这个执行消息屏障说的很清楚了吧?多看注释,多理解。
我们再来看看这个移除消息屏障
@UnsupportedAppUsage @TestApi public void removeSyncBarrier(int token) { //加锁 synchronized (this) { //两个变量赋值 Message prev = null; Message p = mMessages; //这个while循环,第一个条件:当前消息不等于null(当前消息队列还有消息) //第二个条件:当前消息得目的地不为空(我们屏障消息这里是等于空的,这里应该是为了判断其他地方调用这个方法) //然后就是第三个条件,我们传进来的token值,就是上面执行屏障消息时候的返回值,当时赋值给了arg1。这里比较,如果相同,那么,这条消息就是屏障消息 //第二个条件和第三个条件是或的关系,满足一条就行。 //屏障消息就要移除,链表的常规移除操作 while (p != null && (p.target != null || p.arg1 != token)) { prev = p; p = p.next; //其实就是:原来是:上一条消息——屏障消息——下一条消息 //变成了:上一条消息——下一条消息 } //到这里就移除完了 //上面while循环完,发现当前消息是空,说明消息队列中没有消息了,直接抛异常 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. //native层的是否需要唤醒服务 if (needWake && !mQuitting) { nativeWake(mPtr); } } }
总结一下这个提升消息优先级的方式就是:把你想发送的消息定义view异步消息发送,光这样还不行,还要发送一条屏障消息,具体流程:
其实有个更简单的方法,handler发消息的api都给出来了
//发送消息到队列前面
handler.sendMessageAtFrontOfQueue(msg1);
经过上面的整个流程之后,最后一个问题就比较简单了,自己看一下源码吧,我给出结论:
Looper.prepare():给当前线程创建Looper,MessageQueue的过程,这个MessageQueue是可退出的
Looper.loop():从头到尾都在说loop(),循环读取消息。
总结一下handler的东西:整理了一个流程图:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。