深入探讨Android异步精髓Handler.docx
- 文档编号:15438508
- 上传时间:2023-07-04
- 格式:DOCX
- 页数:27
- 大小:465.10KB
深入探讨Android异步精髓Handler.docx
《深入探讨Android异步精髓Handler.docx》由会员分享,可在线阅读,更多相关《深入探讨Android异步精髓Handler.docx(27页珍藏版)》请在冰点文库上搜索。
深入探讨Android异步精髓Handler
深入探讨Android异步精髓Handler
前言
众所周知,Android的UI是在其主线程中进行刷新的,所以Google建议开发人员切勿在主线程中进行耗时的操作否则很容易导致应用程序无响应(ANR)。
鉴于此几乎接近硬性的要求,我们常把耗时的操作(比如网络请求)置于子线程中进行;但是子线程不能直接访问UI。
至此,这个矛盾就凸显出来了:
∙主线程可以刷新UI,但不能执行耗时操作
∙子线程可以执行耗时操作,但是不能直接刷新UI
嗯哼,那有没有一个东西可以调和并化解这个矛盾呢?
当然是有的,Google采用Handler把主线程和子线程精巧地联系起来——子线程中进行耗时的业务逻辑,然后利用Handler通知主线程刷新UI。
除此以外,还有别的方式可以实现类似的操作么?
答案是肯定的,我们也可以利用AsyncTask或者IntentService进行异步的操作。
这两者又是怎么做到的呢?
其实,在AsyncTask和IntentService的内部亦使用了Handler实现其主要功能。
抛开这两者不谈,当我们打开Android源码的时候也随处可见Handler的身影。
所以,Handler是Android异步操作的核心和精髓,它在众多领域发挥着极其重要甚至是不可替代的作用。
在此,对Handler的工作原理和实现机制进行系统的梳理。
ThreadLocal简介及其使用
对于线程Thread大家都挺熟悉的了,但是对于ThreadLocal可能就要陌生许多了。
虽然我们对于它不太了解,但是它早在JDK1.2版本中就已问世并且被广泛的使用,比如Hibernate,EventBus,Handler都运用了ThreadLocal进行线程相关的操作。
如果单纯地从ThreadLocal这个名字来看,它带着浓浓的“本地线程”的味道;然而,喝一口之后才发现根本就不是这个味儿。
其实,ThreadLocal并不是用来操作什么本地线程而是用于实现不同线程的数据副本。
当使用ThreadLocal维护变量时,它会为每个使用该变量的线程提供独立的变量副本;每一个线程都可以独立地改变自己的副本并且不会影响其它线程所持有的对应的副本。
所以,ThreadLocal的实际作用并不与它的名字所暗含的意义相吻合,或许改称为ThreadLocalVariable(线程本地变量)会更合适一些。
接下来,我们通过一个实例来瞅瞅ThreadLocal的使用方式
/**
*原创作者:
*谷哥的小弟
*
*博客地址:
*
*/
privatevoidtestThreadLocal(){
mThreadLocal.set("东京热");
newHotThread1().start();
newHotThread2().start();
hot3=mThreadLocal.get();
try{
Thread.sleep(1000*4);
Log.i(TAG,"HotThread1获取到的变量值:
"+hot1);
Log.i(TAG,"HotThread2获取到的变量值:
"+hot2);
Log.i(TAG,"MainThread获取到的变量值:
"+hot3);
}catch(Exceptione){
}
}
privateclassHotThread1extendsThread{
@Override
publicvoidrun(){
super.run();
mThreadLocal.set("北京热");
hot1=mThreadLocal.get();
}
}
privateclassHotThread2extendsThread{
@Override
publicvoidrun(){
super.run();
mThreadLocal.set("南京热");
hot2=mThreadLocal.get();
}
}
查看输出结果:
HotThread1获取到的变量值:
北京热
HotThread2获取到的变量值:
南京热
MainThread获取到的变量值:
东京热
在这段代码中使用ThreadLocal保存String类型的数据,并且在主线程和两个子线程中为ThreadLocal设置了不同的值,然后再将这些值分别取出。
结合输出日志可以发现:
在不同的线程中访问了同一个ThreadLocal对象,但是通过mThreadLocal.get()得到的值却是不一样的;也就是说:
它们之间没有发生相互的影响而是保持了彼此的独立。
明白了ThreadLocal的这个特性之后,我们再去理解Looper的工作机制就会容易得多了。
Looper、线程、消息队列的关系
Google官方建议开发人员使用Handler实现异步刷新UI,我们在平常的工作中也很好地采纳了这个提议:
首先在主线程中建立Handler,然后在子线程中利用handler.sendMessage(message)发送消息至主线程,最终消息在handleMessage(Messagemsg){}得到相应的处理。
这个套路,大家都再熟悉不过了;现在换个角度,我们试试在子线程中建立Handler
privateclassLooperThreadextendsThread{
@Override
publicvoidrun(){
super.run();
Handlerhandler=newHandler();
//doingsomething
}
}
此处的代码很简单:
LooperThread继承自Thread,并且在其run()方法中新建一个Handler。
嗯哼,再运行一下,喔哦,报错了:
Can’tcreatehandlerinsidethreadthathasnotcalledLooper.prepare().
咦,有点出师不利呢,刚开始试就出错了…….没事,生活不就是无尽的挫折和希望嘛,这点小事嘛也不算。
既然是在调用Handler的构造方法时报的错那就从该构造方法的源码入手,一探究竟:
publicHandler(){
this(null,false);
}
publicHandler(Callbackcallback){
this(callback,false);
}
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");
}
}
mLooper=Looper.myLooper();
if(mLooper==null){
thrownewRuntimeException
("Can'tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()");
}
mQueue=mLooper.mQueue;
mCallback=callback;
mAsynchronous=async;
}
请注意第20行代码:
如果mLooper==null那么系统就会抛出刚才的错误:
Can’tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()。
这句话的意思是:
如果在线程内创建handler必须调用Looper.prepare()。
既然这个提示已经提示了我们该怎么做,那就加上这一行代码:
privateclassLooperThreadextendsThread{
@Override
publicvoidrun(){
super.run();
Looper.prepare();
Handlerhandler=newHandler();
System.out.println("addcode:
Looper.prepare()");
//doingsomething
}
}
嘿嘿,果然不再报错了,运行一下:
既然Looper.prepare()解决了这个问题,那我们就去瞅瞅在该方法中做了哪些操作:
/**Initializethecurrentthreadasalooper.
*Thisgivesyouachancetocreatehandlersthatthenreference
*thislooper,beforeactuallystartingtheloop.Besuretocall
*loop()aftercallingthismethod,andenditbycallingquit().
*/
publicstaticvoidprepare(){
prepare(true);
}
privatestaticvoidprepare(booleanquitAllowed){
if(sThreadLocal.get()!
=null){
thrownewRuntimeException("OnlyoneLoopermaybecreatedperthread");
}
sThreadLocal.set(newLooper(quitAllowed));
}
从这段源码及其注释文档我们可以看出:
1.在prepare()中利用一个Looper来初始化当前线程或者说初始化一个带有Looper的线程。
请注意第14行代码,它是这段源码的核心,现对其详细分析:
sThreadLocal.set(newLooper(quitAllowed));
在该行代码中一共执行了两个操作
(1)构造Looper
privateLooper(booleanquitAllowed){
mQueue=newMessageQueue(quitAllowed);
mThread=Thread.currentThread();
}
在Looper的构造方法中初始化了一个消息队列MessageQueue和一个线程Thread。
从这可看出:
一个Looper对应着一个消息队列以及当前线程。
当收到消息Message后系统会将其存入消息队列中等候处理。
至于Looper,它在Android的消息机制中担负着消息轮询的职责,它会不间断地查看MessageQueue中是否有新的未处理的消息;若有则立刻处理,若无则进入阻塞。
(2)将此Looper保存到sThreadLocal中。
此处的sThreadLocal是定义在Looper类中的一个ThreadLocal类型变量
staticfinalThreadLocal
Looper是framework中的一个类,sThreadLocal是它的一个staticfinal变量。
当在某一个Thread中执行Looper.prepare()时系统就会将与该Thread所对应的Looper保存到sThreadLocal中。
不同的线程对着不同的Looper,但它们均由系统保存在sThreadLocal中并且互不影响,相互独立;并且可以通过sThreadLocal.get()获取不同线程所对应的Looper.
2.在调用prepare()方法后需要调用loop()方法开始消息的轮询,并且在需要的时候调用quit()方法停止消息的轮询
3.假若再次执行Looper.prepare()系统发现sThreadLocal.get()的值不再为null于是抛出异常:
OnlyoneLoopermaybecreatedperthread,一个线程只能创建一个Looper!
小结:
1.一个线程对应一个Looper
2.一个Looper对应一个消息队列
3.一个线程对应一个消息队列
4.线程,Looper,消息队列三者一一对应
所以,在一个子线程中使用Handler的方式应该是这样的:
classLooperThreadextendsThread{
publicHandlermHandler;
publicvoidrun(){
Looper.prepare();
mHandler=newHandler(){
publicvoidhandleMessage(Messagemsg){
}
};
Looper.loop();
}
}
看到这个范例,有的人可能心里就犯嘀咕了:
为什么我们平常在MainActivity中使用Handler时并没有调用Looper.prepare()也没有报错呢?
这是因为UI线程是主线程,系统会自动调用Looper.prepareMainLooper()方法创建主线程的Looper和消息队列MessageQueue
Message的发送和处理过程
在讨论完Looper、线程、消息队列这三者的关系之后我们再来瞅瞅Android消息机制中对于Message的发送和处理。
平常最常用的方式:
handler.sendMessage(message)——>发送消息
handleMessage(Messagemsg){}——>处理消息
先来分析消息的入队。
Handler可以通过post()、postAtTime()、postDelayed()、postAtFrontOfQueue()等方法发送消息,除了postAtFrontOfQueue()之外这几个方法均会执行到sendMessageAtTime(Messagemsg,longuptimeMillis)方法,源码如下:
publicbooleansendMessageAtTime(Messagemsg,longuptimeMillis){
MessageQueuequeue=mQueue;
if(queue==null){
RuntimeExceptione=newRuntimeException(
this+"sendMessageAtTime()calledwithnomQueue");
Log.w("Looper",e.getMessage(),e);
returnfalse;
}
returnenqueueMessage(queue,msg,uptimeMillis);
}
publicfinalbooleansendMessageAtFrontOfQueue(Messagemsg){
MessageQueuequeue=mQueue;
if(queue==null){
RuntimeExceptione=newRuntimeException(
this+"sendMessageAtTime()calledwithnomQueue");
Log.w("Looper",e.getMessage(),e);
returnfalse;
}
returnenqueueMessage(queue,msg,0);
}
privatebooleanenqueueMessage(MessageQueuequeue,Messagemsg,longuptimeMillis){
msg.target=this;
if(mAsynchronous){
msg.setAsynchronous(true);
}
returnqueue.enqueueMessage(msg,uptimeMillis);
}
在这里可以看到sendMessageAtTime()内部又调用了enqueueMessage(),在该方法内的重要操作:
∙第一步:
给msg设置了target,请参见代码第25行
此处的this就是当前Handler对象本身。
在这就指明了该msg的来源——它是由哪个Handler发出的,与此同时也指明了该msg的归宿——它该由哪个Handler处理。
不难发现,哪个Handler发出了消息就由哪个Handler负责处理。
∙第二步:
将消息放入消息队列中,请参见代码第29行
在enqueueMessage(msg,uptimeMillis)中将消息Message存放进消息队列中,距离触发时间最短的message排在队列最前面,同理距离触发时间最长的message排在队列的最尾端。
若调用sendMessageAtFrontOfQueue()方法发送消息它会直接调用该enqueueMessage(msg,uptimeMillis)让消息入队只不过时间为延迟时间为0,也就是说该消息会被插入到消息队列头部优先得到执行。
直觉告诉我们此处的消息队列mQueue就是该线程所对应的消息队列。
可是光有直觉是不够的甚至是不可靠的。
我们再回过头瞅瞅Handler的构造方法,从源码中找到确切的依据
publicHandler(Callbackcallback,booleanasync){
if(FIND_POTENTIAL_LEAKS){
finalClass
extendsHandler>klass=getClass();
if((klass.isAnonymousClass()||klass.isMemberClass()||klass.isLocalCss())&&
(klass.getModifiers()&Modifier.STATIC)==0){
Log.w(TAG,"ThefollowingHandlerclassshouldbestaticorleaksmightoccur");
}
}
mLooper=Looper.myLooper();
if(mLooper==null){
thrownewRuntimeException
("Can'tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()");
}
mQueue=mLooper.mQueue;
mCallback=callback;
mAsynchronous=async;
}
(1)获取Looper,请参见代码第10行
(2)利用Looper的消息队列为mQueue赋值,请参见代码第15行
(3)为mCallback赋值,,请参见代码第16行
(4)为mAsynchronous赋值,,请参见代码第17行
嗯哼,看到了吧,这个mQueue就是从Looper中取出来的。
在之前我们也详细地分析了Looper、线程、消息队列这三者的一一对应关系,所以此处的mQueue正是线程所对应的消息队列。
看完了消息的入队,再来分析消息的出队。
请看Looper中的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==ll){
//Nomessageindicatesthatthemessagequeueisquitting.
return;
}
//Thismustbeinalocalvariable,incaseaUIeventsetsthelogger
finalPrinterlogging=me.mLogging;
if(logging!
=null){
logging.println(">>>>>Dispatchingto"+msg.target+""+
msg.callback+":
"+msg.what);
}
finallongtraceTag=me.mTraceTag;
if(traceTag!
=0){
Trace.traceBegin(traceTag,msg.target.getTraceName(msg));
}
try{
msg.target.dispatchMessage(msg);
}finally{
if(traceTag!
=0){
Trace.traceEnd(traceTag);
}
}
if(logging!
=null){
logging.println("<<<< } //Makesurethatduringthecourseofdispatchingthe //identityofthethreadwasn'tcorrupted. finallongnewIdent=Binder.clearCallingIdentity();
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 深入 探讨 Android 异步 精髓 Handler