定时post网站源码分享,网页定时器

今天给各位分享定时post网站源码分享的知识,其中也会对网页定时器进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!

Handler属于八股文中非常经典的一个考题了,导致这个知识点很多时候,考官都懒得问了;这玩意很久之前就看过,但是过了一段时间,就很容易忘记,但是处理内存泄漏,IdleHandler之类的考点答案肯定很难忘。。。虽然考官很多时候不屑问,但是要是问到了,你忘了且不知道怎么回答,那就很尴尬了

鄙人也来炒个剩饭,力求通俗易懂的来描述下Handler机制的整个流程;相关知识点,画了一些流程图,时序图来展示其运行机制,力争让本文图文并茂!

文章中关键方法源码,可以直接点击方法名,跳转查看对应方法的源码

如果看了没收获,喷我!

总流程

开头需要建立个handler作用的总体印象,下面画了一个总体的流程图

从上面的流程图可以看出,总体上是分几个大块的

Looper.prepare()、Handler()、Looper.loop()总流程收发消息分发消息

相关知识点大概涉及到这些,下面详细讲解下!

需要详细的查看该思维导图,请右键下载后查看

使用

先来看下使用,不然源码,原理图搞了一大堆,一时想不起怎么用的,就尴尬了

使用很简单,此处仅做个展示,大家可以熟悉下

演示代码尽量简单是为了演示,关于静态内部类持有弱引用或者销毁回调中清空消息队列之类,就不在此处展示了

来看下消息处理的分发方法:dispatchMessage(msg)

Handler.java\n…\npublicvoiddispatchMessage(@NonNullMessagemsg){\nif(msg.callback!=null){\nhandleCallback(msg);\n}else{\nif(mCallback!=null){\nif(mCallback.handleMessage(msg)){\nreturn;\n}\n}\nhandleMessage(msg);\n}\n}\n…\n

从上面源码可知,handler的使用总的来说,分俩大类,细分三小类

收发消息一体handleCallback(msg)收发消息分开mCallback.handleMessage(msg)handleMessage(msg)

收发一体

handleCallback(msg)使用post形式,收发都是一体,都在post()方法中完成,此处不需要创建Message实例等,post方法已经完成这些操作

publicclassMainActivityextendsAppCompatActivity{\nprivateTextViewmsgTv;\nprivateHandlermHandler=newHandler();\n\n@Override\nprotectedvoidonCreate(BundlesavedInstanceState){\nsuper.onCreate(savedInstanceState);\nsetContentView(R.layout.activity_main);\nmsgTv=findViewById(R.id.tv_msg);\n\n\t//消息收发一体\nnewThread(newRunnable(){\n@Overridepublicvoidrun(){\nStringinfo=&34;;\nmHandler.post(newRunnable(){\n@Overridepublicvoidrun(){\nmsgTv.setText(info);\n}\n});\n}\n}).start();\n}\n}\n

收发分开

mCallback.handleMessage(msg)

实现Callback接口

publicclassMainActivityextendsAppCompatActivity{\nprivateTextViewmsgTv;\nprivateHandlermHandler=newHandler(newHandler.Callback(){\n//接收消息,刷新UI\n@OverridepublicbooleanhandleMessage(@NonNullMessagemsg){\nif(msg.what==1){\nmsgTv.setText(msg.obj.toString());\n}\n//false重写Handler类的handleMessage会被调用,true不会被调用\nreturnfalse;\n}\n});\n\n@Override\nprotectedvoidonCreate(BundlesavedInstanceState){\nsuper.onCreate(savedInstanceState);\nsetContentView(R.layout.activity_main);\nmsgTv=findViewById(R.id.tv_msg);\n\n//发送消息\nnewThread(newRunnable(){\n@Overridepublicvoidrun(){\nMessagemessage=Message.obtain();\nmessage.what=1;\nmessage.obj=&34;;\nmHandler.sendMessage(message);\n}\n}).start();\n}\n}\n

handleMessage(msg)

重写Handler类的handlerMessage(msg)方法

publicclassMainActivityextendsAppCompatActivity{\nprivateTextViewmsgTv;\nprivateHandlermHandler=newHandler(){\n//接收消息,刷新UI\n@OverridepublicvoidhandleMessage(@NonNullMessagemsg){\nsuper.handleMessage(msg);\nif(msg.what==1){\nmsgTv.setText(msg.obj.toString());\n}\n}\n};\n\n@Override\nprotectedvoidonCreate(BundlesavedInstanceState){\nsuper.onCreate(savedInstanceState);\nsetContentView(R.layout.activity_main);\nmsgTv=findViewById(R.id.tv_msg);\n\n//发送消息\nnewThread(newRunnable(){\n@Overridepublicvoidrun(){\nMessagemessage=Message.obtain();\nmessage.what=1;\nmessage.obj=&34;;\nmHandler.sendMessage(message);\n}\n}).start();\n}\n}\n

prepare和loop

大家肯定有印象,在子线程和子线程的通信中,就必须在子线程中初始化Handler,必须这样写

prepare在前,loop在后,固化印象了

newThread(newRunnable(){\n@Overridepublicvoidrun(){\nLooper.prepare();\nHandlerhandler=newHandler();\nLooper.loop();\n}\n});\n

为啥主线程不需要这样写,聪明你肯定想到了,在入口出肯定做了这样的事

ActivityThread.java\n…\npublicstaticvoidmain(String[]args){\n…\n//主线程Looper\nLooper.prepareMainLooper();\nActivityThreadthread=newActivityThread();\nthread.attach(false);\nif(sMainThreadHandler==null){\nsMainThreadHandler=thread.getHandler();\n}\n//主线程的loop开始循环\nLooper.loop();\n\t…\n}\n…\n

为什么要使用prepare和loop?我画了个图,先让大家有个整体印象

上图的流程,鄙人感觉整体画的还是比较清楚的总结下就是Looper.prepare():生成Looper对象,set在ThreadLocal里handler构造函数:通过Looper.myLooper()获取到ThreadLocal的Looper对象Looper.loop():内部有个死循环,开始事件分发了;这也是最复杂,干活最多的方法

具体看下每个步骤的源码,这里也会标定好链接,方便大家随时过去查看

Looper.prepare()可以看见,一个线程内,只能使用一次prepare(),不然会报异常的

Looper.java\n…\npublicstaticvoidprepare(){\nprepare(true);\n}\n\nprivatestaticvoidprepare(booleanquitAllowed){\nif(sThreadLocal.get()!=null){\nthrownewRuntimeException(&34;);\n}\nsThreadLocal.set(newLooper(quitAllowed));\n}\n…\n

Handler()这里通过Looper.myLooper()—>sThreadLocal.get()拿到了Looper实例

Handler.java\n…\n@Deprecated\npublicHandler(){\nthis(null,false);\n}\n\npublicHandler(@NullableCallbackcallback,booleanasync){\nif(FIND_POTENTIAL_LEAKS){\nfinalClass<?extendsHandler>klass=getClass();\nif((klass.isAnonymousClass()||klass.isMemberClass()||klass.isLocalClass())&&\n(klass.getModifiers()&Modifier.STATIC)==0){\nLog.w(TAG,&34;+\nklass.getCanonicalName());\n}\n}\n\nmLooper=Looper.myLooper();\nif(mLooper==null){\nthrownewRuntimeException(\n&39;tcreatehandlerinsidethread&34;thathasnotcalledLooper.prepare()&34;sendMessageAtTime()calledwithnomQueue&34;Looper&39;thavetowake\n//uptheeventqueueunlessthereisabarrierattheheadofthequeue\n//andthemessageistheearliestasynchronousmessageinthequeue.\nneedWake=mBlocked&&p.target==null&&msg.isAsynchronous();\nMessageprev;\nfor(;;){\nprev=p;\np=p.next;\nif(p==null||when<p.when){\nbreak;\n}\nif(needWake&&p.isAsynchronous()){\nneedWake=false;\n}\n}\nmsg.next=p;//invariant:p==prev.next\nprev.next=msg;\n}\n\n//WecanassumemPtr!=0becausemQuittingisfalse.\nif(needWake){\nnativeWake(mPtr);\n}\n}\nreturntrue;\n}\n…\n

来看下发送的消息插入消息队列的图示

接收消息

接受消息相对而言就简单多

dispatchMessage(msg):关键方法呀

Handler.java\n…\npublicvoiddispatchMessage(@NonNullMessagemsg){\nif(msg.callback!=null){\nhandleCallback(msg);\n}else{\nif(mCallback!=null){\nif(mCallback.handleMessage(msg)){\nreturn;\n}\n}\nhandleMessage(msg);\n}\n}\n…\n

handleCallback(msg)触发条件:Message消息中实现了handleCallback回调现在基本上只能使用post()方法了,setCallback(Runnabler)被表明为@UnsupportedAppUsage,被hide了,没法调用,如果使用反射倒是可以调用,但是没必要。。。mCallback.handleMessage(msg)触发条件使用sendMessage方法发送消息(必须)实现Handler的Callback回调分发的消息,会在Handler中实现的回调中分发handleMessage(msg)触发条件使用sendMessage方法发送消息(必须)未实现Handler的Callback回调实现了Handler的Callback回调,返回值为false(mCallback.handleMessage(msg))需要重写Handler类的handlerMessage方法

分发消息

消息分发是在loop()中完成的,来看看loop()这个重要的方法

Looper.loop():精简了巨量源码,详细的可以点击左侧方法名Messagemsg=queue.next():遍历消息msg.target.dispatchMessage(msg):分发消息msg.recycleUnchecked():消息回收,进入消息池

Looper.java\n…\npublicstaticvoidloop(){\nfinalLooperme=myLooper();\n…\nfinalMessageQueuequeue=me.mQueue;\n\t…\nfor(;;){\n//遍历消息池,获取下一可用消息\nMessagemsg=queue.next();//mightblock\n…\ntry{\n//分发消息\nmsg.target.dispatchMessage(msg);\n…\n}catch(Exceptionexception){\n…\n}finally{\n…\n}\n….\n//回收消息,进图消息池\nmsg.recycleUnchecked();\n}\n}\n…\n

遍历消息

遍历消息的关键方法肯定是下面这个

Messagemsg=queue.next():Message类中的next()方法;当然这必须要配合外层for(无限循环)来使用,才能遍历消息队列

来看看这个Message中的next()方法吧

next():精简了一些源码,完整的点击左侧方法名

MessageQueue.java\n…\nMessagenext(){\nfinallongptr=mPtr;\n…\n\nintpendingIdleHandlerCount=-1;//-1onlyduringfirstiteration\nintnextPollTimeoutMillis=0;\nfor(;;){\n\t\t…\n//阻塞,除非到了超时时间或者唤醒\nnativePollOnce(ptr,nextPollTimeoutMillis);\nsynchronized(this){\n//Trytoretrievethenextmessage.Returniffound.\nfinallongnow=SystemClock.uptimeMillis();\nMessageprevMsg=null;\nMessagemsg=mMessages;\n//这是关于同步屏障(SyncBarrier)的知识,放在同步屏障栏目讲\nif(msg!=null&&msg.target==null){\ndo{\nprevMsg=msg;\nmsg=msg.next;\n}while(msg!=null&&!msg.isAsynchronous());\n}\n\nif(msg!=null){\nif(now<msg.when){\n//每个消息处理有耗时时间,之间存在一个时间间隔(when是将要执行的时间点)。\n//如果当前时刻还没到执行时刻(when),计算时间差值,传入nativePollOnce定义唤醒阻塞的时间\nnextPollTimeoutMillis=(int)Math.min(msg.when-now,Integer.MAX_VALUE);\n}else{\nmBlocked=false;\n//该操作是把异步消息单独从消息队列里面提出来,然后返回,返回之后,该异步消息就从消息队列里面剔除了\n//mMessage仍处于未分发的同步消息位置\nif(prevMsg!=null){\nprevMsg.next=msg.next;\n}else{\nmMessages=msg.next;\n}\nmsg.next=null;\nif(DEBUG)Log.v(TAG,&34;+msg);\nmsg.markInUse();\n//返回符合条件的Message\nreturnmsg;\n}\n}else{\n//Nomoremessages.\nnextPollTimeoutMillis=-1;\n}\n\n//这是处理调用IdleHandler的操作,有几个条件\n\t//1、当前消息队列为空(mMessages==null)\n//2、已经到了可以分发下一消息的时刻(now<mMessages.when)\nif(pendingIdleHandlerCount<0\n&&(mMessages==null||now<mMessages.when)){\npendingIdleHandlerCount=mIdleHandlers.size();\n}\nif(pendingIdleHandlerCount<=0){\n//Noidlehandlerstorun.Loopandwaitsomemore.\nmBlocked=true;\ncontinue;\n}\n\nif(mPendingIdleHandlers==null){\nmPendingIdleHandlers=newIdleHandler[Math.max(pendingIdleHandlerCount,4)];\n}\nmPendingIdleHandlers=mIdleHandlers.toArray(mPendingIdleHandlers);\n}\n\n\nfor(inti=0;i<pendingIdleHandlerCount;i++){\nfinalIdleHandleridler=mPendingIdleHandlers[i];\nmPendingIdleHandlers[i]=null;//releasethereferencetothehandler\n\nbooleankeep=false;\ntry{\nkeep=idler.queueIdle();\n}catch(Throwablet){\nLog.wtf(TAG,&34;,t);\n}\n\nif(!keep){\nsynchronized(this){\nmIdleHandlers.remove(idler);\n}\n}\n}\n\n//Resettheidlehandlercountto0sowedonotrunthemagain.\npendingIdleHandlerCount=0;\n\n//Whilecallinganidlehandler,anewmessagecouldhavebeendelivered\n//sogobackandlookagainforapendingmessagewithoutwaiting.\nnextPollTimeoutMillis=0;\n}\n}\n

总结下源码里面表达的意思

next()内部是个死循环,你可能会疑惑,只是拿下一节点的消息,为啥要死循环?为了执行延时消息以及同步屏障等等,这个死循环是必要的nativePollOnce阻塞方法:到了超时时间(nextPollTimeoutMillis)或者通过唤醒方式(nativeWake),会解除阻塞状态nextPollTimeoutMillis大于等于零,会规定在此段时间内休眠,然后唤醒消息队列为空时,nextPollTimeoutMillis为-1,进入阻塞;重新有消息进入队列,插入头结点的时候会触发nativeWake唤醒方法如果msg.target==null为零,会进入同步屏障状态会将msg消息死循环到末尾节点,除非碰到异步方法如果碰到同步屏障消息,理论上会一直死循环上面操作,并不会返回消息,除非,同步屏障消息被移除消息队列当前时刻和返回消息的when判定消息when代表的时刻:一般都是发送消息的时刻,如果是延时消息,就是发送时刻+延时时间当前时刻小于返回消息的when:进入阻塞,计算时间差,给nativePollOnce设置超时时间,超时时间一到,解除阻塞,重新循环取消息当前时刻大于返回消息的when:获取可用消息返回消息返回后,会将mMessage赋值为返回消息的下一节点(只针对不涉及同步屏障的同步消息)

这里简单的画了个流程图

分发消息

分发消息主要的代码是:msg.target.dispatchMessage(msg);

也就是说这是Handler类中的dispatchMessage(msg)方法

dispatchMessage(msg)

publicvoiddispatchMessage(@NonNullMessagemsg){\nif(msg.callback!=null){\nhandleCallback(msg);\n}else{\nif(mCallback!=null){\nif(mCallback.handleMessage(msg)){\nreturn;\n}\n}\nhandleMessage(msg);\n}\n}\n

可以看到,这里的代码,在收发消息栏目的接受消息那块已经说明过了,这里就无须重复了

消息池

msg.recycleUnchecked()是处理完成分发的消息,完成分发的消息并不会被回收掉,而是会进入消息池,等待被复用

recycleUnchecked():回收消息的代码还是蛮简单的,来分析下首先会将当前已经分发处理的消息,相关属性全部重置,flags也标志可用消息池的头结点会赋值为当前回收消息的下一节点,当前消息成为消息池头结点简言之:回收消息插入消息池,当做头结点需要注意的是:消息池有最大的容量,如果消息池大于等于默认设置的最大容量,将不再接受回收消息入池默认最大容量为50:MAX_POOL_SIZE=50

Message.java\n…\nvoidrecycleUnchecked(){\n//Markthemessageasinusewhileitremainsintherecycledobjectpool.\n//Clearoutallotherdetails.\nflags=FLAG_IN_USE;\nwhat=0;\narg1=0;\narg2=0;\nobj=null;\nreplyTo=null;\nsendingUid=UID_NONE;\nworkSourceUid=UID_NONE;\nwhen=0;\ntarget=null;\ncallback=null;\ndata=null;\n\nsynchronized(sPoolSync){\nif(sPoolSize<MAX_POOL_SIZE){\nnext=sPool;\nsPool=this;\nsPoolSize++;\n}\n}\n}\n

来看下消息池回收消息图示

既然有将已使用的消息回收到消息池的操作,那肯定有获取消息池里面消息的方法了

obtain():代码很少,来看看如果消息池不为空:直接取消息池的头结点,被取走头结点的下一节点成为消息池的头结点如果消息池为空:直接返回新的Message实例

Message.java\n…\npublicstaticMessageobtain(){\nsynchronized(sPoolSync){\nif(sPool!=null){\nMessagem=sPool;\nsPool=m.next;\nm.next=null;\nm.flags=0;//clearin-useflag\nsPoolSize–;\nreturnm;\n}\n}\nreturnnewMessage();\n}\n

来看下从消息池取一个消息的图示

IdleHandler

在MessageQueue类中的next方法里,可以发现有关于对IdleHandler的处理,大家可千万别以为它是什么Handler特殊形式之类,这玩意就是一个interface,里面抽象了一个方法,结构非常的简单

next():精简了大量源码,只保留IdleHandler处理的相关逻辑;完整的点击左侧方法名

MessageQueue.java\n…\nMessagenext(){\nfinallongptr=mPtr;\n…\n\nintpendingIdleHandlerCount=-1;//-1onlyduringfirstiteration\nintnextPollTimeoutMillis=0;\nfor(;;){\n\t\t…\n//阻塞,除非到了超时时间或者唤醒\nnativePollOnce(ptr,nextPollTimeoutMillis);\nsynchronized(this){\n//Trytoretrievethenextmessage.Returniffound.\nfinallongnow=SystemClock.uptimeMillis();\nMessageprevMsg=null;\nMessagemsg=mMessages;\n\t\t\t…\n//这是处理调用IdleHandler的操作,有几个条件\n\t//1、当前消息队列为空(mMessages==null)\n//2、未到到了可以分发下一消息的时刻(now<mMessages.when)\n\t\t\t//3、pendingIdleHandlerCount<0表明:只会在此for循环里执行一次处理IdleHandler操作\nif(pendingIdleHandlerCount<0\n&&(mMessages==null||now<mMessages.when)){\npendingIdleHandlerCount=mIdleHandlers.size();\n}\nif(pendingIdleHandlerCount<=0){\nmBlocked=true;\ncontinue;\n}\n\nif(mPendingIdleHandlers==null){\nmPendingIdleHandlers=newIdleHandler[Math.max(pendingIdleHandlerCount,4)];\n}\nmPendingIdleHandlers=mIdleHandlers.toArray(mPendingIdleHandlers);\n}\n\n\nfor(inti=0;i<pendingIdleHandlerCount;i++){\nfinalIdleHandleridler=mPendingIdleHandlers[i];\nmPendingIdleHandlers[i]=null;//releasethereferencetothehandler\n\nbooleankeep=false;\ntry{\nkeep=idler.queueIdle();\n}catch(Throwablet){\nLog.wtf(TAG,&34;,t);\n}\n\nif(!keep){\nsynchronized(this){\nmIdleHandlers.remove(idler);\n}\n}\n}\n\npendingIdleHandlerCount=0;\nnextPollTimeoutMillis=0;\n}\n}\n

实际上从上面的代码里面,可以分析出很多信息

IdleHandler相关信息

调用条件当前消息队列为空(mMessages==null)或未到分发返回消息的时刻在每次获取可用消息的死循环中,IdleHandler只会被处理一次:处理一次后pendingIdleHandlerCount为0,其循环不可再被执行实现了IdleHandler中的queueIdle方法返回false,执行后,IdleHandler将会从IdleHandler列表中移除,只能执行一次:默认false返回true,每次分发返回消息的时候,都有机会被执行:处于保活状态IdleHandler代码MessageQueue.java…/***Callbackinterfacefordiscoveringwhenathreadisgoingtoblock*waitingformoremessages.*/publicstaticinterfaceIdleHandler{/***Calledwhenthemessagequeuehasrunoutofmessagesandwillnow*waitformore.Returntruetokeepyouridlehandleractive,false*tohaveitremoved.Thismaybecallediftherearestillmessages*pendinginthequeue,buttheyareallscheduledtobedispatched*afterthecurrenttime.*/booleanqueueIdle();}publicvoidaddIdleHandler(@NonNullIdleHandlerhandler){if(handler==null){thrownewNullPointerException(&39;taddanullIdleHandler&34;我是IdleHandler&34;我是大帅比&34;第一种方式&34;ThefollowingHandlerclassshouldbestaticorleaksmightoccur:&34;Can&34;+Thread.currentThread()\n+&34;);\n}\nmQueue=mLooper.mQueue;\nmCallback=callback;\nmAsynchronous=async;\n}\n\npublicHandler(@NonNullLooperlooper,@NullableCallbackcallback,booleanasync){\nmLooper=looper;\nmQueue=looper.mQueue;\nmCallback=callback;\nmAsynchronous=async;\n}\n

看看一些通用的构造方法

publicHandler(){\nthis(null,false);\n}\n\npublicHandler(@NonNullLooperlooper){\nthis(looper,null,false);\n}\n\npublicHandler(@NonNullLooperlooper,@NullableCallbackcallback){\nthis(looper,callback,false);\n}\n

总结下这下清楚了!如果不做特殊设置的话:默认消息都是同步消息默认消息都会给其target变量赋值:默认消息都不是同步屏障消息

生成同步屏障消息

在next方法中发现,target为null的消息被称为同步屏障消息,那他为啥叫同步屏障消息呢?

postSyncBarrier(longwhen)sync:同步barrier:屏障,障碍物—>同步屏障同步屏障实际挺能代表其含义的,它能屏蔽消息队列中后续所有的同步方法分发

MessageQueue.java\n…\n@UnsupportedAppUsage\n@TestApi\npublicintpostSyncBarrier(){\nreturnpostSyncBarrier(SystemClock.uptimeMillis());\n}\n\nprivateintpostSyncBarrier(longwhen){\n//Enqueueanewsyncbarriertoken.\n//Wedon&34;Returningmessage:&34;ViewAncestor”);\n}\n\nperformTraversals();\n\nif(mProfile){\nDebug.stopMethodTracing();\nmProfile=false;\n}\n}\n}\n

doTraversal()是怎么被调用呢?调用:mTraversalRunnable在scheduleTraversals()中使用了finalTraversalRunnablemTraversalRunnable=newTraversalRunnable();voidscheduleTraversals(){if(!mTraversalScheduled){…mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL,mTraversalRunnable,null);…}}finalclassTraversalRunnableimplementsRunnable{@Overridepublicvoidrun(){doTraversal();}}postCallback是Choreographer类中方法,该类涉及巨多的消息传递,而且都是使用了异步消息setAsynchronous(true),这些都是和界面刷新相关,所以都是优先处理,完整的流程可以看上面贴的文章postCallback的核心就是让DisplayEventReceiver注册了个Vsync的通知,后期收到送来的Vsync后,我们就执行doTraversal()来重新绘制界面

总结

通过上面的对ViewRootImpl说明,需要来总结下同步屏障对界面绘制过程的影响详细版总结(不讲人话版)

调用View的requestLayout或者invalidate时,最终都会执行scheduleTraversals(),此时会在主线程消息队列中插入一个同步屏障消息(停止所有同步消息分发),会将mTraversalRunnable添加到mCallbackQueues中,并注册接收Vsync的监听,当接受到Vsync通知后,会发送一个异步消息,触发遍历执行mCallbackQueues的方法,这会执行我们添加的回调mTraversalRunnable,从而执行doTraversal(),此时会移除主线程消息队列中同步屏障消息,最后执行绘制操作

通俗版总结

调用requestLayout或者invalidate时,会在主线程消息队列中插入一个同步屏障消息,同时注册接收Vsync的监听;当接受到Vsync通知,会发送一个异步消息,执行真正的绘制事件:此时会移除消息队列中的同步屏障消息,然后才会执行绘制操作

下面给不讲人话版画了个流转图示

总结

消息插入对比

有个很重要的事情,我们再来看下:正常发送消息和同步屏障消息插入消息队列直接的区别,见下图取消息:关于取消息,都是取的mMessage,可以理解为,取消息队列的头结点非延时消息在同步屏障消息之前发送,都会排在同步屏障消息之前延时消息,如果时刻大于发送同步屏障消息的时刻,会排在同步屏障消息之后

Vsync

关于VsyncVsync信号一般是由硬件产生的,现在手机一般为60hz~120hz,每秒刷新60到120次,一个时间片算一帧每个Vsync信号之间的时间就是一帧的时间段来看下执行同步消息时间片:这图真吉儿不好画,吐血由上图可知:某种极端情况,你所发送的消息,在分发的时候,可能存在一帧的延时

总结

相关总结

同步屏障能确保消息队列中的异步消息,会被优先执行鉴于正常消息和同步屏障消息插入消息队列的区别:同步屏障能够及时的屏障队列中的同步消息某些极端场景:发送的消息,在分发的时候,可能会存一帧延时极端场景:Vsync信号到来之后,立马执行了RequestLayout等操作同步屏障能确保在UI刷新中:Vsync信号到来后,能够立马执行真正的绘制页面操作

同步消息和异步消息使用建议

在正常的情况,肯定不建议使用异步消息,此处假设一个场景:因为某种需求,你发送了大量的异步消息,由于消息进入消息队列的特殊性,系统发送的异步消息,也只能乖乖的排在你的异步消息后面,假设你的异步消息占据了大量的时间片,甚至占用了几帧,导致系统UI刷新的异步消息无法被及时执行,此时很有可能发生掉帧

当然,如果你能看明白这个同步屏障栏目所写的东西,相信什么时候设置消息为异步,心中肯定有数

正常情况,请继续使用同步消息特殊情况,需要自己发送的消息被优先处理:可以使用异步消息

考点

上面源码基本就分析到这边了,咱们看看能根据这些知识点,能提一些什么问题呢?

一个小知识

我逛一些论坛的时候,发现有人:对Handler怎么在主线程和子线程进行数据交互的原理,感到迷惑。

如果看完这整篇,或许你的心里已经有了答案,为了更加明确这个知识,我还是在这里总结下吧!主线程和子线程通过handler交互,交互的载体是通过Message这个对象,实际上我们在子线程发送的所有消息,都会加入到主线程的消息队列中,然后主线程分发这些消息,这个就很容易做到俩个线程信息的交互。看到这里,你可能有疑问了,我从子线程发送的消息,怎么就加到了主线程的消息队列里呢???大家可以看看你自己的代码,你的handler对象是不是在主线程初始的?子线程发送消息,是不是通过这个handler发送的?这就很简单了,handler只需要把发送的消息,加到自身持有的Looper对象的MessageQueue里面(mLooper变量)就ok了所以,你在哪个线程里面初始化Handler对象,在不同的线程中,使用这个对象发送消息;都会在你初始化Handler对象的线程里分发消息

1、先来个自己想的问题:Handler中主线程的消息队列是否有数量上限?为什么?

这问题整的有点鸡贼,可能会让你想到,是否有上限这方面?而不是直接想到到上限数量是多少?

解答:Handler主线程的消息队列肯定是有上限的,每个线程只能实例化一个Looper实例(上面讲了,Looper.prepare只能使用一次),不然会抛异常,消息队列是存在Looper()中的,且仅维护一个消息队列

重点:每个线程只能实例化一次Looper()实例、消息队列存在Looper中

拓展:MessageQueue类,其实都是在维护mMessage,只需要维护这个头结点,就能维护整个消息链表

2、Handler中有Loop死循环,为什么没有卡死?为什么没有发生ANR?

先说下ANR:5秒内无法响应屏幕触摸事件或键盘输入事件;广播的onReceive()函数时10秒没有处理完成;前台服务20秒内,后台服务在200秒内没有执行完毕;ContentProvider的publish在10s内没进行完。所以大致上Loop死循环和ANR联系不大,问了个正确的废话,所以触发事件后,耗时操作还是要放在子线程处理,handler将数据通讯到主线程,进行相关处理。

线程实质上是一段可运行的代码片,运行完之后,线程就会自动销毁。当然,我们肯定不希望主线程被over,所以整一个死循环让线程保活。

为什么没被卡死:在事件分发里面分析了,在获取消息的next()方法中,如果没有消息,会触发nativePollOnce方法进入线程休眠状态,释放CPU资源,MessageQueue中有个原生方法nativeWake方法,可以解除nativePollOnce的休眠状态,ok,咱们在这俩个方法的基础上来给出答案

当消息队列中消息为空时,触发MessageQueue中的nativePollOnce方法,线程休眠,释放CPU资源消息插入消息队列,会触发nativeWake唤醒方法,解除主线程的休眠状态当插入消息到消息队列中,为消息队列头结点的时候,会触发唤醒方法当插入消息到消息队列中,在头结点之后,链中位置的时候,不会触发唤醒方法综上:消息队列为空,会阻塞主线程,释放资源;消息队列为空,插入消息时候,会触发唤醒机制这套逻辑能保证主线程最大程度利用CPU资源,且能及时休眠自身,不会造成资源浪费本质上,主线程的运行,整体上都是以事件(Message)为驱动的

3、为什么不建议在子线程中更新UI?

多线程操作,在UI的绘制方法表示这不安全,不稳定。

假设一种场景:我会需要对一个圆进行改变,A线程将圆增大俩倍,B改变圆颜色。A线程增加了圆三分之一体积的时候,B线程此时,读取了圆此时的数据,进行改变颜色的操作;最后的结果,可能会导致,大小颜色都不对。。。

4、可以让自己发送的消息优先被执行吗?原理是什么?

这个问题,我感觉只能说:在有同步屏障的情况下是可以的。

同步屏障作用:在含有同步屏障的消息队列,会及时的屏蔽消息队列中所有同步消息的分发,放行异步消息的分发。

在含有同步屏障的情况,我可以将自己的消息设置为异步消息,可以起到优先被执行的效果。

5、子线程和子线程使用Handler进行通信,存在什么弊端?

子线程和子线程使用Handler通信,某个接受消息的子线程肯定使用实例化handler,肯定会有Looper操作,Looper.loop()内部含有一个死循环,会导致线程的代码块无法被执行完,该线程始终存在。

如果在完成通信操作,我们一般可以使用:mHandler.getLooper().quit()来结束分发操作

说明下:quit()方法会进行几项操作清空消息队列(未分发的消息,不再分发了)调用了原生的销毁方法nativeDestroy(猜测下:可能是一些资源的释放和销毁)拒绝新消息进入消息队列它可以起到结束loop()死循环分发消息的操作拓展:quitSafely()可以确保所有未完成的事情完成后,再结束消息分发

6、Handler中的阻塞唤醒机制?

这个阻塞唤醒机制是基于Linux的I/O多路复用机制epoll实现的,它可以同时监控多个文件描述符,当某个文件描述符就绪时,会通知对应程序进行读/写操作.

MessageQueue创建时会调用到nativeInit,创建新的epoll描述符,然后进行一些初始化并监听相应的文件描述符,调用了epoll_wait方法后,会进入阻塞状态;nativeWake触发对操作符的write方法,监听该操作符被回调,结束阻塞状态

详细请查看:同步屏障?阻塞唤醒?和我一起重读Handler源码

7、什么是IdleHandler?什么条件下触发IdleHandler?

IdleHandler的本质就是接口,为了在消息分发空闲的时候,能处理一些事情而设计出来的

具体条件:消息队列为空的时候、发送延时消息的时候

8、消息处理完后,是直接销毁吗?还是被回收?如果被回收,有最大容量吗?

Handler存在消息池的概念,处理完的消息会被重置数据,采用头插法进入消息池,取的话也直接取头结点,这样会节省时间

消息池最大容量为50,达到最大容量后,不再接受消息进入

9、不当的使用Handler,为什么会出现内存泄漏?怎么解决?

先说明下,Looper对象在主线程中,整个生命周期都是存在的,MessageQueue是在Looper对象中,也就是消息队列也是存在在整个主线程中;我们知道Message是需要持有Handler实例的,Handler又是和Activity存在强引用关系

存在某种场景:我们关闭当前Activity的时候,当前Activity发送的Message,在消息队列还未被处理,Looper间接持有当前activity引用,因为俩者直接是强引用,无法断开,会导致当前Activity无法被回收

思路:断开俩者之间的引用、处理完分发的消息,消息被处理后,之间的引用会被重置断开

解决:使用静态内部类弱引Activity、清空消息队列

定时post网站源码分享的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于网页定时器、定时post网站源码分享的信息别忘了在本站进行查找哦。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平