Handler运行机制中必须明白的几个问题
2017-04-23 11:40
525 查看
概述
我在看完Handler的源码后有两个感觉,一是貌似明白了很多东西,二是当问到具体问题时感觉还是模模糊糊。下面我们就带着问题再看一次源码,力争把这块知识点搞的明明白白。问题有:
在UI线程中有几个Looper对象?有几个MessageQueue对象?有几个Handler对象?有几个Message对象?
怎么保证只有一个Looper对象的?
怎么保证只有一个MessageQueue对象的?
为什么发送消息在子线程,而处理消息就变成主线程了,在哪儿跳转的?
looper对象只有一个,在分发消息时怎么区分不同的handler?
能不能在子线程中创建Handler对象?
怎么在子线程中得到主线程中handler对象?
如果上面七个问题你都能清晰的回答,那么恭喜你,你对Handler的理解已经很透彻了。如果你有时间,还是希望你能继续往下看,可以给我找错误,找到我理解偏差的地方,找到有奖哦。或者您也可以提出问题让我回答,大家共同学习。
在UI线程中有几个Looper对象?有几个MessageQueue对象?有几个Handler对象?有几个Message对象?
在UI线程中只有一个Looper对象,只有一个MessageQueue对象。但可以有很多个handler对象。可以有很多个Message对象。怎么保证只有一个Looper对象的?
在Handler机制中使用Looper对象的地方有三个,一是在ActivityThread类中使用Looper.prepareMainLooper()创建Looper对象,二是在hanlder类中使用Looper对象,三是在Looper的loop方法中使用Looper对象处理消息。若这三个Looper对象是同一个就证明了在UI线程只有一个Looper对象。一,Looper.prepareMainLooper()创建Looper对象
prepareMainLooper方法的原码是:public static void prepareMainLooper() { prepare(false);//创建Looper对象,并保存到ThreadLocal中 synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper();//得到Looper对象,并保存到Looper类的静态字段中 } }
下面看prepare方法的原码:
private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
分析:在第一次调用sThreadLocal.get()方法得到的一定是null,所以此时的重点是创建Looper对象,并放入sThreadLocal中储存起来。
这儿要明确两点:
1. sThreadLocal是Looper类的静态字段,所以只有一个sThreadLocal对象。
2. prepare方法在UI线程被调用,所以只有在Ui线程才能从sThreadLocal对象中获取到looper对象。
下面看myLooper方法的原码:
public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
分析:这个方法很简单,目的就是得到UI线程中的Looper对象。注意这个方法是静态方法,得到的Looper对象就是在UI线程中创建的Looper对象。
二,在Hanlder类中使用Looper对象
我们知道在创建Handler对象时使用的是无参构造,但无参构造方法调用了下面的构造方法:public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
分析:由原码可知,Handler类中有mLooper字段和mQueue字段。mLooper字段赋值时通过Looper.myLooper()方法,我们从上面知道这个方法返回的值就是UI线程中创建的Looper对象,所以此时的Looper对象和Ui线程中创建的Looper对象是同一个。
三,在Looper的loop方法中使用Looper对象
在Looper类的loop方法中获取Looper对象的代码是:final Looper me = myLooper();
很明显通过myLooper方法得到的Looper对象就是UI线程中创建的Looper对象。
怎么保证只有一个MessageQueue对象的?
首先我们找一下MessageQueue对象是在哪儿创建的?我们知道在在Looper的prepare方法中创建了Looper对象,并放入到ThreadLocal中,具体代码是:
sThreadLocal.set(new Looper(quitAllowed));
那么我们来看一下Looper的构造方法:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
在Looper的构造方法中创建了MessageQueue对象,并赋值给mQueue字段。因为Looper对象只有一个,那么Messagequeue对象肯定只有一个。
下面我们再多学习一点,找到使用MessageQueue使用的地方。MessageQueue被两个地方使用,一是在handler的sendMessage中发送消息。二是在looper类的loop方法中取出消息。
在看Handler的构造方法中我们知道,在构造方法中通过Looper.myPrepare方法得到mLooper对象,又通过mLooper.mQueue得到Messagequeue对象。所以此时这个MessageQueue对象就是在Looper的构造方法中创建的对象。
在Looper的loop方法中也是首先通过Looper.myPrepare()方法得到Looper对象,然后得到MessageQueue对象。
为什么发送消息在子线程,而处理消息就变成主线程了,在哪儿跳转的?
发送消息使用的是Handler的sendMessage方法,这个方法最终调用的是enqueueMessage方法,enqueueMessage方法的原码是:private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
这个方法中调用的是MessageQueue的enqueueMessage方法,只要queue对象是UI线程中的MessageQueue对象,那么就能被Ui线程中的Looper从消息队列中取出来,然后就在主线程执行了。我们知道Handler对象中的mQueue就是UI线程中的消息队列对象,所以在处理消息时就是主线程了。
looper对象只有一个,在分发消息时怎么区分不同的handler?
关于这个问题主要看三点:一是Message类中有个字段target,这个字段是Handler类型的。
二在Handler的enqueueMessage方法中有这么一句代码:msg.target = this;即把handler对象与Message绑定在了一起。
三在Looper类的looper方法中分发消息的代码是:msg.target.dispatchMessage(msg);
此时我们就明白了:在发送消息时handler对象与Message对象绑定在了一起。在分发消息时首先取出Message对象,然后就可以得到与它绑定在一起的Handler对象了。
能不能在子线程中创建Handler对象?
这个问题并不是简单的能不能的问题,考察的是对Handler的深入理解。答案肯定是可以的,但是仅仅使用无参构造是不可以的,还需要做其他的操作。一,仅仅使用无参构造为什么不能创建Handler对象?
我们知道Handler的无参构造最终调用了是另一个构造方法,下面还是看这个构造方法的原码:public Handler(Callback callback, boolean async) { mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } }
分析:在上面代码中我们看到,如果得到的Looper对象是null就会抛出异常。为什么在子线程中Looper.myLooper方法会返回null呢?原因就是ThreadLocal的特性了。Looper.myLooper方法的原码是从ThreadLocal中得到Looper对象,而在Looper.prepare方法中Looper对象是在UI线程中放入到ThreadLocal中的,所以在子线程中是得不到Looper对象的。在handler中没有looper对象就会抛出异常。
为什么在handler中没有looper对象就会抛出异常?
我们知道handler发送消息最终调用的是MessageQueue的enqueueMessage方法,如果没有Looper对象,肯定得不到MessageQueue对象,在发送消息时一定会抛出nullPointException。所以系统在前面就进行了拦截,只要没有Looper对象就不让代码继续执行。怎么在子线程中创建Handler对象?
在UI线程之所以可以直接创建创建Handler对象,是因为在Ui线程已经有了Looper对象,所以只要我们在子线程中创建Looper对象后就可以创建handler对象了。使用示例如下:new Thread(new Runnable() { @Override public void run() { Looper.prepare();//创建looper对象 Looper.loop();//开启子线程中的消息循环 Handler handler = new Handler(){//创建Handler对象 @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; } }).start();
此时就在子线程中维护了一套消息循环系统。
注意:这套消息循环系统与UI线程中的Handler机制没有任何关系,这儿的消息不能与Ui线程中的消息队列进行通信。但子线程中的消息循环系统也有很大的作用,当需要处理很多消息时可以让消息按序列执行。在图片加载框架Picasso中就在子线程中维护了一套消息循环系统,感兴趣的小伙伴可以自行查看Picasso的原码。
怎么在子线程中得到主线程中handler对象?
其实handler对象没有主线程和子线程之分,有区分的是Looper对象,如果Looper对象是主线程中的,那么handler就是主线程中的。Handler有下面一个构造方法:
public Handler(Looper looper) { this(looper, null, false); }
这个构造方法中接收一个Looper对象。只要我们能到子线程中得到主线程的Looper对象,那么就可以实现在子线程中得到主线程中的handler对象了。
在Looper类中有getMainLooper方法,这个方法的源码是:
public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; } }
这个方法返回的是在主线程中创建过的Looper对象。所以这个方法无论在哪儿调用得到的都是主线程中的Looper对象,此时我们就可以在子线程中创建主线程的handler对象了,代码如下:
new Thread(new Runnable() { @Override public void run() { Handler handler = new Handler(Looper.getMainLooper());//得到UI线程中的handler对象 handler.post(new Runnable() { @Override public void run() { //这儿写逻辑代码 } }); } }).start();
注:使用这种方法可以轻松的从子线程跳转到UI线程,完全不依赖于Activity或Application。图片加载框架Picasso,网络请求控件Volley都是通过这种方法在工具类内部实现了更新UI。
相关文章推荐
- OKHttp原码分析(八)之必须明白的几个问题
- 转载:ASP.NET运行机制 和 图片盗链问题
- 十分棘手的问题,使用vs2005在文件系统机制下开发出来的web程序,配置到iis就不能正常运行?
- JAVA初学者都必须理解的几个问题
- 关于运行地址和加载地址的几个很多初学者模糊不清的问题我在这里总结一下
- 聊聊软件与吃饭(二)- 谈谈甲方选择软件供应商必须要分析的几个问题
- DataGrid 分页必须考虑的几个问题
- JAVA初学者必须搞懂的几个问题!
- 网页设计必须注意和掌握的几个问题
- 在Vista的IIS系统中运行ASP的几个配置问题
- 安装SQL200的遇到的问题:以前的某个程序安装已在安装计算机上穿件挂起的文件操作。运行安装程序之前必须重新启动计算机
- 在windows应用程序中运行asp.net程序的几个问题
- 几个可运行的,可以说明问题的Ajax实例
- ASP.NET运行机制和图片盗链问题
- 二:理解ASP.NET的运行机制(例:基于HttpHandler的URL重写)
- ASP.NET运行机制和图片盗链问题
- sql server 安装中出现的问题:以前的某个程序安装已在安装计算机上创建挂起的文件操作。运行安装程序之前必须重新启动计算机!。
- 创业前必须思考的几个问题
- 大型C++项目必须注意的几个小问题
- 布线规划要点-开始设计前必须考虑的几个问题