Android中的消息机制.docx

上传人:b****5 文档编号:7367000 上传时间:2023-01-23 格式:DOCX 页数:12 大小:43.82KB
下载 相关 举报
Android中的消息机制.docx_第1页
第1页 / 共12页
Android中的消息机制.docx_第2页
第2页 / 共12页
Android中的消息机制.docx_第3页
第3页 / 共12页
Android中的消息机制.docx_第4页
第4页 / 共12页
Android中的消息机制.docx_第5页
第5页 / 共12页
点击查看更多>>
下载资源
资源描述

Android中的消息机制.docx

《Android中的消息机制.docx》由会员分享,可在线阅读,更多相关《Android中的消息机制.docx(12页珍藏版)》请在冰豆网上搜索。

Android中的消息机制.docx

Android中的消息机制

Android中的消息机制

执行耗时的操作,比如网络请求,IO操作等,需要在子线程中运行,不然会阻塞主线程。

而执行完网络请求等耗时操作后通常需要更新UI,如果在子线程中更新UI,那么程序会崩溃。

因为Android的UI是线程不安全的。

解决的方案是只需把更新UI的操作切换到主线程即可,这时就轮到Handler出场了,相信大家都对Handler的用法很熟悉了。

当我们在子线程向服务端拉取数据后,主线程是不知道的,这时handler在子线程发送一个消息到主线程告诉主线程:

我已经请求数据完毕,现在你要更新UI了。

然后handlerMessage方法接收到消息即可处理数据更新UI。

这一切都是那么的自然。

Android中的消息机制,由四个角色承担。

分别是Handler,Looper(消息循环),MessageQueue(消息队列),Thread。

看到下图这四个角色的关联,先有一个大概的认识

那接下来就从源码(Api-23)的角度一起来学习消息机制吧

先从handler对象的创建开始,接着再分析handler怎么发送消息。

我们通常在主线程中创建handler,看看他的构造方法。

publicHandler(){

this(null,false);

}

可以看到这个无参数的构造方法,内部使用this关键字调用含有两个参数的构造方法。

那就找到两个参数的构造方法,源码如下:

publicHandler(Callbackcallback,booleanasync){

if(FIND_POTENTIAL_LEAKS){

finalClass

extendsHandler>klass=getClass();

if((klass.isAnonymousClass()||klass.isMemberClass()||klass.isLocalClass())&&

(klass.getModifiers()&Modifier.STATIC)==0){

Log.w(TAG,"ThefollowingHandlerclassshouldbestaticorleaksmightoccur:

"+

klass.getCanonicalName());

}

}

mLooper=Looper.myLooper();

if(mLooper==null){

thrownewRuntimeException(

"Can'tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()");

}

mQueue=mLooper.mQueue;

mCallback=callback;

mAsynchronous=async;

}

可以看到Looper调用myLooper方法获取到Looper对象,如果mLooper==null的话,会抛出

Can'tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()

的异常。

大概的意思就是无法在没有调用Looper.prepare()的线程中创建handler。

我在刚开始学习Handler的时候经常会遇到这个错误。

不急,等下在分析到底为什么,现在我们只需要知道如果Looper.myLooper()没有获取到Looper对象的话就会报这个错。

到了这里,Handler和Looper就建立起了关联。

接着往下看完最后几行代码

mQueue=mLooper.mQueue;

从Looper对象中取出MessageQueue对象并赋值。

MessageQueue就是消息队列,那么他里面存储着很多消息吗?

到了这一步,Handler通过Looper与MessageQueue也建立起了关联。

我们跟踪Looper的myLooper方法进去,解决为什么会抛出Can’tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()异常。

myLooper方法源码如下:

publicstatic@NullableLoopermyLooper(){

returnsThreadLocal.get();

}

只有一行代码,从线程中取出Looper对象,那么我们有理由相信,这个ThreadLocal是通过set方法把Looper对象设置进去的。

想一想ThreadLocal在哪里把Looper对象设置进去了呢。

回到刚才想要解决的问题:

Can’tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()。

那会不会是Looper的prepare方法呢?

publicstaticvoidprepare(){

prepare(true);

}

prepare方法调用了它的一个参数的重载,那么我们就看看那个重载的方法

privatestaticvoidprepare(booleanquitAllowed){

if(sThreadLocal.get()!

=null){

thrownewRuntimeException("OnlyoneLoopermaybecreatedperthread");

}

sThreadLocal.set(newLooper(quitAllowed));

}

找到了线索,ThreadLocal确实是在Looper的prepare方法里把Looper对象设置进去的,而且从第一行的判断可以知道,一个线程只有一个Looper对象。

到了这里,Looper与ThreadLocal建立起了关联。

可以看下Looper的构造方法

privateLooper(booleanquitAllowed){

mQueue=newMessageQueue(quitAllowed);

mThread=Thread.currentThread();

}

创建了一个MessageQueue对象。

好,结合我们的分析可以知道,如果Looper没有调用prepare方法,ThreadLocal的get方法就会返回空,那么Looper.myLooper()也会返回空,所以就抛出了Can'tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()的异常。

那么问题又来了,我们写程序时好像没有手动调用Looper.prepare()吧,也不会抛出异常。

前面提到,我们通常都是在主线程,也就是UI线程中创建handler的。

而在主线程中,系统已经为我们创建了一个Looper对象,所以不会抛出异常了。

而那些会抛出异常报错的情况,是在子线程中创建的handler,但是又没有调用Looper.prepare()去创建Looper对象。

继续前进。

那就来看看,主线程在什么时候创建了Looper对象吧。

在ActivityThread的main方法,这个方法是应用程序的入口。

main方法的源码如下:

publicstaticvoidmain(String[]args){

//代码省略

Looper.prepareMainLooper();

ActivityThreadthread=newActivityThread();

thread.attach(false);

if(sMainThreadHandler==null){

sMainThreadHandler=thread.getHandler();

}

if(false){

Looper.myLooper().setMessageLogging(new

LogPrinter(Log.DEBUG,"ActivityThread"));

}

//EndofeventActivityThreadMain.

Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

Looper.loop();

thrownewRuntimeException("Mainthreadloopunexpectedlyexited");

}

找到了Looper.prepareMainLooper(),这和Looper.prepare()太像了吧,跟进去看看

publicstaticvoidprepareMainLooper(){

prepare(false);

synchronized(Looper.class){

if(sMainLooper!

=null){

thrownewIllegalStateException("ThemainLooperhasalreadybeenprepared.");

}

sMainLooper=myLooper();

}

}

又兜了回来,还是调用了prepare方法的。

所以主线程是已经创建了一个Looper对象的。

Handler的创建过程分析完毕,现在总算搞明白了。

那先总结一下,Handler的创建是依赖于Looper的。

而主线程是默认创建了一个Looper对象的。

每一个Looper会关联一个线程(ThreadLocal中封装了Looper)。

每一个Looper中又会封装一个消息队列。

这样一来,handler,Looper,MessageQueue,Thread四个角色就关联了起来,你中有我,我中有你。

handler在主线程中创建,是因为要和主线程的消息队列关联起来,那样handler的handleMessage方法才会在主线程中执行,那么这样在更新UI就是线程安全的了。

接着继续吧,还很多问题没有解决

相信你更想了解Handler是怎么发送消息的。

通常我们是创建一个Message对象,并将一些从服务端拉取的数据,标记,参数等赋值到Message的一些字段what,arg1,obj等,handler调用sendMessage方法发送,就能将这个数据发送到主线程,然后在handlerMessage方法处理更新UI即可。

那我们就从handler的sendMessage方法开始寻找信息

publicfinalbooleansendMessage(Messagemsg)

{

returnsendMessageDelayed(msg,0);

}

sendMessage会调用sendMessageDelayed方法并将message对象传进去,第二个参数是延时时间,使用sendMessage方法时默认为0的。

那么来到sendMessageDelayed方法

publicfinalbooleansendMessageDelayed(Messagemsg,longdelayMillis)

{

if(delayMillis<0){

delayMillis=0;

}

returnsendMessageAtTime(msg,SystemClock.uptimeMillis()+delayMillis);

}

兜兜转转,最终会调用sendMessageAtTime方法,并将message对象传进。

继续跟进sendMessageAtTime方法,

publicbooleansendMessageAtTime(Messagemsg,longuptimeMillis){

MessageQueuequeue=mQueue;

if(queue==null){

RuntimeExceptione=newRuntimeException(

this+"sendMessageAtTime()calledwithnomQueue");

Log.w("Looper",e.getMessage(),e);

returnfalse;

}

returnenqueueMessage(queue,msg,uptimeMillis);

}

上面分析了,在创建Looper对象的时候,会创建一个MessageQueue,所以只要Looper是正常创建的话,消息队列是不为空的。

那么到最后一行的enqueueMessage方法,源码如下:

privatebooleanenqueueMessage(MessageQueuequeue,Messagemsg,longuptimeMillis){

msg.target=this;

if(mAsynchronous){

msg.setAsynchronous(true);

}

returnqueue.enqueueMessage(msg,uptimeMillis);

}

可以看到最后一行调用了MessageQueue的enqueueMessage方法。

注意:

上面贴出的enqueueMessage是Handler的方法,不是MessageQueue的,只是做了一层包装而已,真正的入队消息队列的操作当然是在MessageQueue中。

而且从第一行的msg.target=this中可以知道,msg的target字段,其实就是handler。

MessageQueue的enqueueMessage方法源码如下:

booleanenqueueMessage(Messagemsg,longwhen){

if(rget==null){

thrownewIllegalArgumentException("Messagemusthaveatarget.");

}

if(msg.isInUse()){

thrownewIllegalStateException(msg+"Thismessageisalreadyinuse.");

}

synchronized(this){

if(mQuitting){

IllegalStateExceptione=newIllegalStateException(

msg.target+"sendingmessagetoaHandleronadeadthread");

Log.w(TAG,e.getMessage(),e);

msg.recycle();

returnfalse;

}

msg.markInUse();

msg.when=when;

Messagep=mMessages;

booleanneedWake;

if(p==null||when==0||when

//Newhead,wakeuptheeventqueueifblocked.

msg.next=p;

mMessages=msg;

needWake=mBlocked;

}else{

//Insertedwithinthemiddleofthequeue.Usuallywedon'thavetowake

//uptheeventqueueunlessthereisabarrierattheheadofthequeue

//andthemessageistheearliestasynchronousmessageinthequeue.

needWake=mBlocked&&p.target==null&&msg.isAsynchronous();

Messageprev;

for(;;){

prev=p;

p=p.next;

if(p==null||when

break;

}

if(needWake&&p.isAsynchronous()){

needWake=false;

}

}

msg.next=p;//invariant:

p==prev.next

prev.next=msg;

}

//WecanassumemPtr!

=0becausemQuittingisfalse.

if(needWake){

nativeWake(mPtr);

}

}

returntrue;

}

Messagequeue中有一个对象mMessage用于指向当前传进的msg,即最新的消息。

而刚才的sendMessageAtTime(Messagemsg,longuptimeMillis)方法,第二个参数指定了时间,然后在这里按照这个uptimeMillis来进行消息的排序,而我分析的结果msg.next是指向下一个消息,这样每一个消息都是按照时间的排序关联了起来,排在前面的消息指向了排在后面的消息。

以上是进入消息队列的分析,handler调用sendMessage方法的最终将message对象传进messagequeue。

完毕,那么消息是怎么从消息队列出来的呢?

这时我们要回看ActiviryThread的main方法,去寻找点线索。

源码在上面已贴出。

发现了倒数第二行的Looper.loop(),简单理解就是消息循环执行循环操作。

这里一定能满足我们的好奇心。

那么跟进。

loop方法的源码如下:

publicstaticvoidloop(){

finalLooperme=myLooper();

if(me==null){

thrownewRuntimeException("NoLooper;Looper.prepare()wasn'tcalledonthisthread.");

}

finalMessageQueuequeue=me.mQueue;

//Makesuretheidentityofthisthreadisthatofthelocalprocess,

//andkeeptrackofwhatthatidentitytokenactuallyis.

Binder.clearCallingIdentity();

finallongident=Binder.clearCallingIdentity();

for(;;){

Messagemsg=queue.next();//mightblock

if(msg==null){

//Nomessageindicatesthatthemessagequeueisquitting.

return;

}

//Thismustbeinalocalvariable,incaseaUIeventsetsthelogger

Printerlogging=me.mLogging;

if(logging!

=null){

logging.println(">>>>>Dispatchingto"+msg.target+""+

msg.callback+":

"+msg.what);

}

msg.target.dispatchMessage(msg);

if(logging!

=null){

logging.println("<<<<

}

//Makesurethatduringthecourseofdispatchingthe

//identityofthethreadwasn'tcorrupted.

finallongnewIdent=Binder.clearCallingIdentity();

if(ident!

=newIdent){

Log.wtf(TAG,"Threadidentitychangedfrom0x"

+Long.toHexString(ident)+"to0x"

+Long.toHexString(newIdent)+"whiledispatchingto"

+msg.target.getClass().getName()+""

+msg.callback+"what="+msg.what);

}

msg.recycleUnchecked();

}

}

抓重点看就好。

首先是调用myLooper方法获取到Looper对象,这里是没问题的,那就继续

MessageQueuequeue=me.mQueue

然后从Looper对象中取出关联的消息队列,

接着进入了一个死循环,调用messagequeue的next方法取出message对象。

这个next方法我没看懂,所以不贴源码出来分析了,反正next方法的作用就是取出message对象的。

有兴趣的同学自己去研究研究吧。

到这里可以总结一下:

通过Looper.prepare()来创建Looper(消息循环)对象,然后通过Looper.loop()来执行消息循环,Looper.prepare()和Looper.loop()通常是成对出现的。

好,回来继续

经过一系列的判断后会来到这里,很重点

msg.target.dispatchMessage(msg);

上面已经分析,msg.target就是handler,那么这行代码的意义就是调用handler的dispatchMessgage的方法去分发消息,

那么看到dispatchMessage的方法源码,相信谜底就要揭开了

publicvoiddispatchMessage(Messagemsg){

if(msg.callback!

=null){

handleCallback(msg);

}else{

if(mCallback!

=null){

if(mCallback.handleMessage(msg)){

return;

}

}

handleMessage(msg);

}

}

从上述的代码跟踪中,都没有发现给message的callback字段赋值,那么我们就先不搭理,默认callback为空,那么就一定会来到handleMessage方法。

message对象传递到了handleMessage方法。

/**

*Su

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1