Android中Message,Handler,Looper的深入分析
16lz
2021-01-26
我们的常用的系统中,程序的工作通常是有事件驱动和消息驱动两种方式,在Android系统中,Java应用程序是靠消息驱动来工作的。
不知道如何更加优美的引出话题,这里以一个demo开始。
代码:
HandlerThread(\frameworks\base\core\java\android\os\HandlerThread.java)
总得来说HandlerThread是一个带有looper的线程,那HandlerThread和Thread有什么区别呢?首先抛开looper不讲,后面会补充,先来研究一下HandlerThread和Thread的区别。
代码:
在Demo中myThread执行Start后会执行run函数:
代码:
Handler
代码:
代码:
代码:
以上只是讲了大概的流程,当然很多细节不能一一俱到了。继续吧,接下来深入一点看看消息循环到底是怎样的。
不得不说的Looper (\frameworks\base\core\java\android\os\Looper.java)
代码:
代码:
代码:
代码:
代码:
1.创建了一个管道,管道两端分别对应读和写,这样设计当然是考虑进程间通信的问题。
2.调用epoll_create创建epoll文件描述符。什么是epoll自行百度。
3.继续调用epoll_ctl设置管道读端。这里就是告诉mEpollFd,它要监控mWakeReadPipeFd文件描述符的EPOLLIN事件,即当管道中有内容可读时,就唤醒当前正在等待管道中的内容的线程。
以上所有的操作都是在初始化中,looper的初始化看起来还是蛮复杂的,可以分成以下几部分:
A.在Java层,创建了一个Looper对象,这个Looper对象是用来进入消息循环的,它的内部有一个消息队列MessageQueue对象mQueue;
B.在JNI层,创建了一个NativeMessageQueue对象,这个NativeMessageQueue对象保存在Java层的消息队列对象mQueue的成员变量mPtr中;
C.在C++层,创建了一个Looper对象,保存在JNI层的NativeMessageQueue对象的成员变量mLooper中,这个对象的作用是,当Java层的消息队列中没有消息时,就使Android应用程序主线程进入等待状态,而当Java层的消息队列中来了新的消息后,就唤醒Android应用程序的主线程来处理这个消息。
Ok,接下来进入消息循环Looper.loop()该函数在前面贴过,消息循环就是一个无限取消息处理消息的过程,处理消息前面也说过,现在看看消息是如何取出来的。
代码:
代码:
代码:
代码:
不知道如何更加优美的引出话题,这里以一个demo开始。
代码:
ublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);Buttonbtn=(Button)findViewById(R.id.button);btn.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(Viewv){HandlerThreadmyThread=newHandlerThread("Test"){@OverrideprotectedvoidonLooperPrepared(){super.onLooperPrepared();}};myThread.start();Log.d("HandlerThreadtest","startthread");HandlermyHandler=newHandler(myThread.getLooper()){@OverridepublicvoidhandleMessage(Messagemsg){super.handleMessage(msg);try{Thread.sleep(3000);}catch(InterruptedExceptione){e.printStackTrace();}Log.d("HandlerThreadtest","handlemessage");}};Messagemsg=myHandler.obtainMessage(1);myHandler.sendMessage(msg);}});}Demo很简单,看似是为了利用消息循环而用消息循环,没有多大意义,从这里开始阐述安卓的消息循环以及消息在系统中是怎样生成以及被处理的。Demo中同时用到了三个类,分别是HandlerThread,Handler,Message,以下将进行一一说明。
HandlerThread(\frameworks\base\core\java\android\os\HandlerThread.java)
总得来说HandlerThread是一个带有looper的线程,那HandlerThread和Thread有什么区别呢?首先抛开looper不讲,后面会补充,先来研究一下HandlerThread和Thread的区别。
代码:
publicclassHandlerThreadextendsThread{privateintmPriority;privateintmTid=-1;privateLoopermLooper;publicHandlerThread(Stringname){super(name);mPriority=Process.THREAD_PRIORITY_DEFAULT;}首先,Thread是HandlerThread的基类,那么可以理解HandlerThread拥有Thread的所有行为特征。其次,其中mLooper是和Thread的最大区别。
在Demo中myThread执行Start后会执行run函数:
代码:
publicvoidrun(){mTid=Process.myTid();Looper.prepare();synchronized(this){mLooper=Looper.myLooper();notifyAll();}Process.setThreadPriority(mPriority);onLooperPrepared();Looper.loop();mTid=-1;}在Run中调用了Looper.prepare()和Looper.loop()这样就进入了该线程的消息循环,什么事Looper以及消息循环是怎样驱动的后后面阐述。
Handler
代码:
publicHandler(Looperlooper){mLooper=looper;mQueue=looper.mQueue;mCallback=null;}将HandlerThread的Looper传给handler,Handler将其保存在mLooper,并且Handler还保存了上面传下来的MessageQueue。然后我们利用Handler来Sendmessage。最终会调用sendMessageAtTime
代码:
publicbooleansendMessageAtTime(Messagemsg,longuptimeMillis){booleansent=false;MessageQueuequeue=mQueue;if(queue!=null){msg.target=this;sent=queue.enqueueMessage(msg,uptimeMillis);}else{RuntimeExceptione=newRuntimeException(this+"sendMessageAtTime()calledwithnomQueue");Log.w("Looper",e.getMessage(),e);}returnsent;}将消息的目标设置为自己,同时将消息插入到消息队列中(此处的消息队列就是HandlerThread中的)。做完这些后,我们发送的消息就会成功进入HandlerThread的处理流程。在插入消息队列时,会根据触发消息的时间将其插入到队列的合适位置。线程在进行loop时:
代码:
publicstaticfinalvoidloop(){Looperme=myLooper();MessageQueuequeue=me.mQueue;while(true){Messagemsg=queue.next();//mightblock//if(!me.mRun){//break;//}if(msg!=null){if(msg.target==null){//Notargetisamagicidentifierforthequitmessage.return;}if(me.mLogging!=null)me.mLogging.println(">>>>>Dispatchingto"+msg.target+""+msg.callback+":"+msg.what);msg.target.dispatchMessage(msg);if(me.mLogging!=null)me.mLogging.println("<<<<<Finishedto"+msg.target+""+msg.callback);msg.recycle();}}}会依次从mQueue中取出消息,然后调用dispatchMessage分发,注意看调用方是msg.target,如果处理的消息是我们发送的,那么将会调用我们类中的dispatchMessage函数。在dispatchMessage中会回调到我们重写的HandleMessage。这样就完成了消息的发送,循环和处理。其中可以看到Looper和Handler紧密配合。如果用生产和消费者模型来比喻的话,Looper就是那个生产者,handler是消费者。Handler将消息丢给消息队列,looper永不停息的取出消息然后分发,最后由handler来继续处理。
以上只是讲了大概的流程,当然很多细节不能一一俱到了。继续吧,接下来深入一点看看消息循环到底是怎样的。
不得不说的Looper (\frameworks\base\core\java\android\os\Looper.java)
代码:
rivateLooper(){mQueue=newMessageQueue();mRun=true;mThread=Thread.currentThread();}创建了一个MessageQueue。
代码:
MessageQueue(){nativeInit();}在构造函数中直接调用了Native的初始化,继续跟进去看看在native中初始化做了些什么
代码:
staticvoidandroid_os_MessageQueue_nativeInit(JNIEnv*env,jobjectobj){NativeMessageQueue*nativeMessageQueue=newNativeMessageQueue();if(!nativeMessageQueue){jniThrowRuntimeException(env,"Unabletoallocatenativequeue");return;}android_os_MessageQueue_setNativeMessageQueue(env,obj,nativeMessageQueue);}做了两件事:1.创建了NativeMessageQueue,2.调用setNativeMessageQueue将刚才创建的NativeMessageQueue保存在Java层MessageQueue中成员变量mPtr中。这是为了后续我们调用Java层的消息队列对象的其它成员函数进入到JNI层时,能够方便地找回它在JNI层所对应的消息队列对象。紧接着来看看NativeMessageQueue又是啥:
代码:
NativeMessageQueue::NativeMessageQueue(){mLooper=Looper::getForThread();if(mLooper==NULL){mLooper=newLooper(false);Looper::setForThread(mLooper);}}创建了一个looper对象,不过这可不是Java层的Looper,记住现在是在Native中。
代码:
Looper::Looper(boolallowNonCallbacks):mAllowNonCallbacks(allowNonCallbacks),mResponseIndex(0){intwakeFds[2];intresult=pipe(wakeFds);LOG_ALWAYS_FATAL_IF(result!=0,"Couldnotcreatewakepipe.errno=%d",errno);mWakeReadPipeFd=wakeFds[0];mWakeWritePipeFd=wakeFds[1];result=fcntl(mWakeReadPipeFd,F_SETFL,O_NONBLOCK);LOG_ALWAYS_FATAL_IF(result!=0,"Couldnotmakewakereadpipenon-blocking.errno=%d",errno);result=fcntl(mWakeWritePipeFd,F_SETFL,O_NONBLOCK);LOG_ALWAYS_FATAL_IF(result!=0,"Couldnotmakewakewritepipenon-blocking.errno=%d",errno);#ifdefLOOPER_USES_EPOLL//Allocatetheepollinstanceandregisterthewakepipe.mEpollFd=epoll_create(EPOLL_SIZE_HINT);LOG_ALWAYS_FATAL_IF(mEpollFd<0,"Couldnotcreateepollinstance.errno=%d",errno);structepoll_eventeventItem;memset(&eventItem,0,sizeof(epoll_event));//zerooutunusedmembersofdatafieldunioneventItem.events=EPOLLIN;eventItem.data.fd=mWakeReadPipeFd;result=epoll_ctl(mEpollFd,EPOLL_CTL_ADD,mWakeReadPipeFd,&eventItem);LOG_ALWAYS_FATAL_IF(result!=0,"Couldnotaddwakereadpipetoepollinstance.errno=%d",errno);#else//Addthewakepipetotheheadoftherequestlistwithanullcallback.structpollfdrequestedFd;requestedFd.fd=mWakeReadPipeFd;requestedFd.events=POLLIN;mRequestedFds.push(requestedFd);Requestrequest;request.fd=mWakeReadPipeFd;request.callback=NULL;request.ident=0;request.data=NULL;mRequests.push(request);mPolling=false;mWaiters=0;#endif#ifdefLOOPER_STATISTICSmPendingWakeTime=-1;mPendingWakeCount=0;mSampledWakeCycles=0;mSampledWakeCountSum=0;mSampledWakeLatencySum=0;mSampledPolls=0;mSampledZeroPollCount=0;mSampledZeroPollLatencySum=0;mSampledTimeoutPollCount=0;mSampledTimeoutPollLatencySum=0;#endif}这坨代码有点多,做了这样几件事:
1.创建了一个管道,管道两端分别对应读和写,这样设计当然是考虑进程间通信的问题。
2.调用epoll_create创建epoll文件描述符。什么是epoll自行百度。
3.继续调用epoll_ctl设置管道读端。这里就是告诉mEpollFd,它要监控mWakeReadPipeFd文件描述符的EPOLLIN事件,即当管道中有内容可读时,就唤醒当前正在等待管道中的内容的线程。
以上所有的操作都是在初始化中,looper的初始化看起来还是蛮复杂的,可以分成以下几部分:
A.在Java层,创建了一个Looper对象,这个Looper对象是用来进入消息循环的,它的内部有一个消息队列MessageQueue对象mQueue;
B.在JNI层,创建了一个NativeMessageQueue对象,这个NativeMessageQueue对象保存在Java层的消息队列对象mQueue的成员变量mPtr中;
C.在C++层,创建了一个Looper对象,保存在JNI层的NativeMessageQueue对象的成员变量mLooper中,这个对象的作用是,当Java层的消息队列中没有消息时,就使Android应用程序主线程进入等待状态,而当Java层的消息队列中来了新的消息后,就唤醒Android应用程序的主线程来处理这个消息。
Ok,接下来进入消息循环Looper.loop()该函数在前面贴过,消息循环就是一个无限取消息处理消息的过程,处理消息前面也说过,现在看看消息是如何取出来的。
代码:
Messagemsg=queue.next();//mightblock代码:
finalMessagenext(){intpendingIdleHandlerCount=-1;//-1onlyduringfirstiterationintnextPollTimeoutMillis=0;for(;;){if(nextPollTimeoutMillis!=0){Binder.flushPendingCommands();}nativePollOnce(mPtr,nextPollTimeoutMillis);synchronized(this){//Trytoretrievethenextmessage.Returniffound.finallongnow=SystemClock.uptimeMillis();finalMessagemsg=mMessages;if(msg!=null){finallongwhen=msg.when;if(now>=when){mBlocked=false;mMessages=msg.next;msg.next=null;if(Config.LOGV)Log.v("MessageQueue","Returningmessage:"+msg);returnmsg;}else{nextPollTimeoutMillis=(int)Math.min(when-now,Integer.MAX_VALUE);}}else{nextPollTimeoutMillis=-1;//省略一部分}}}}调用nativePollOnce,还记得mPtr吗,不记得看前面。
代码:
voidNativeMessageQueue::pollOnce(inttimeoutMillis){mLooper->pollOnce(timeoutMillis);}接着会继续调用Looper.pollInner()
代码:
structepoll_eventeventItems[EPOLL_MAX_EVENTS];inteventCount=epoll_wait(mEpollFd,eventItems,EPOLL_MAX_EVENTS,timeoutMillis);boolacquiredLock=false;等待管道事件,监听管道两端的读写事件,epoll_wait的最后一个参数表示超时时间。在管道没有IO事件发生时自动休眠,直到有事件来激活或者超时。
代码:
for(inti=0;i<eventCount;i++){intfd=eventItems[i].data.fd;uint32_tepollEvents=eventItems[i].events;if(fd==mWakeReadPipeFd){if(epollEvents&EPOLLIN){awoken();}else{LOGW("Ignoringunexpectedepollevents0x%xonwakereadpipe.",epollEvents);}}else{}}如果从epoll_wait返回时eventcount=0,则说明已经超时了什么也不处理。如果eventcount>0则说明有读或者写事件需要处理(当然我们只关心读),所以在后面的判断中如果是读事件就调用awoken,awoken很简单只是读取管道。这样函数从native中开始不断返回最后回到MessageQueue中的nativePollOnce中,然后就是消息的处理,怎么处理上文中已经说过。源代码可以参照(\frameworks\base\core\java\android\os\MessageQueue.java)。
更多相关文章
- Android中TabHost中实现标签的滚动
- android 沉浸式(透明)状态栏实现
- Android基于javamail发送带附件邮件
- (原创)Android(安卓)Studio常用快捷键大全
- android中RadioGroup、RadioButton、Spinner、EditText用法详解(
- Android(安卓)- 混淆jar包 ProGuard GUI 使用方法
- Android(安卓)源码分析鼠标事件传递
- Android在代码中开启OpenGL 4xMSAA 抗锯齿
- android 右上角添加菜单