android之handler切换线程终极篇
前言:最近部门招人的时候,对于我的一个问题(在handler的机制流程里,哪部分原理是真正的起到了切换线程的作用?)很多候选人都是说不出来,或者表达不清楚(虽然他们对handler的整体机制原理说的头头是道)。所以,才有了我今天这篇老生常谈的文章,但是我今天主要不是给大家介绍handler机制原理或者怎么去使用它,而是想帮大家弄明白咱们的handler究竟是怎么实现了线程的切换。
作为文章的开始,我想先来纠正一个很多人习以为常的说法:handler是用来实现子线程到主线程的切换去更新ui的。
这个说法是没问题的,但是我想强调的是这不是handler的唯一作用,Handler的主要作用就是将一个任务切换到指定的线程中去执行,这个切换线程指的是任何线程,不单是切换到主线程!!!
好了,啰嗦的话说完了,下面开始今天的正文了。。。
我先带着大家回顾一下handler的机制吧(以下内容都是个人想法和认知,如有问题请指出)
先说一下Handler机制的主要作用(可不是全部作用呦):
我们知道android设备作为一台移动设备,不管是内存或者还是它的性能都会受到一定的限制:大量的使用CPU的资 源(一般是指CPU做了大量耗时的操作)会导致手机变得很卡甚至会出现程序无法响应的情况,即出现ANR异常(Application Not Responding)。比如:在UI线程中如果5秒之内没有相应的输入事件或者是BroadcastReceiver中超过10秒没有完成返回的话就会触发ANR异常。这样就要求我们必须要在写代码的时候格外注意不能将大量耗时的操作放在UI线程中去执行,例如网络访问、数据库操作、读取大容量的文件资源、分析位图资源等…
正式由于Android系统的这些特性导致开发者在进行开发的时候必须要将比较耗时的操作远离UI线程(ActivityThread),单独的将这些操作放入到子线程中进行执行,在子线程完成代码执行之后将等到的结果数据返回到UI线程(android系统规定在子线程中不允许访问UI),同时UI线程根据这些返回的数据更新界面UI,从而实现了人机之间友好的交互。
对于咱们的Handler来说主要作用就是将一个任务切换到指定的线程中去执行。而这样的特性正好可以用来解决在子线程中无法访问UI的矛盾。
一,Handler机制之MessageQueue介绍:
MessageQueue通常翻译为“消息队列”,它内部存储了一组数据,以队列的形式对外提供插入和删除操作。虽然被称之为消息队列,但是实际上它的数据结构却是采用的单链表的结构来存储消息列表(单链表在插入和删除操作上效率比较高)。
MessageQueue主要包含两个操作:插入(enqueueMessage)和读取(next)。
插入:enqueueMessage()方法是往消息队列中插入一条数据
读取:next()方法用于消息的读取,读取操作本身也会伴随着消息的删除操作,即从消息队列中取出一条数据并将该数据从消息队列中删除。
这里需要指出的是,next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直堵塞,有新消息的事后,next方法会返回这条消息并从链表中删除该消息。
我再补充一点:MessageQueue 存在的原因很简单,就是同一线程在同一时间只能处理一个消息,同一线程代码执行是不具有并发性,所以需要队列来保存消息和安排每个消息的处理顺序。多个其他线程往UI线程发送消息,UI线程必须把这些消息保持到一个列表(它同一时间不能处理那么多任务),然后挨个拿出来处理,这种设计很简单,我们平时写代码其实也经常这么做。每一个Looper线程都会维护这样一个队列,而且仅此一个,这个队列的消息只能由该线程处理。
二,Handler机制之Looper介绍:
Looper可以理解为消息循环,因为MessageQueue只是一个存储消息队列,不能去处理消息,所以需要Looper无限循环的去查看是否有新的消息,如果有的话就处理消息,否则就一直等待(阻塞)。每一个异步线程,都维护着唯一的一个Looper,每一个Looper会初始化(维护)一个MessageQueue,之后进入一个无限循环一直在读取MessageQueue中存储的消息,如果没有消息那就一直阻塞等待。
Looper中有2个比较重要的方法:
Prepare();
Loop();对于Looper.prepare()方法请看以下源码:
public static void prepare() { prepare(true); } 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)); }
通过源码我们可以看到该方法主要做了如下工作:
检查是否实例化了ThreadLocal,确保每个线程的Looper只有一个。
如果已经实例化ThreadLocal,则将所在线程中Looper对象存储进去。
再对Looper.loop()方法小小的简介:
在下面的源码中,我们可以看到通过之前存储在threadLocal对象中的looper对象得到了该线程中对应looper对象中维护的MessageQueue消息队列,之后进入了一个无限循环,对消息队列中的数据进行读取,并且会把消息通过msg.target.dispatchMessage(msg);进行处理,对于msg.target来说,其实它就是与之绑定的handler实例。
请看下面的源码:
public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); } }
以上这些就是我对Looper的介绍了,可以总结为:
与当前的线程绑定,保证一个线程只有一个Looper实例,同时一个Looper实例只有一个MessageQueue。
loop方法不断的从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。
再次补充一点:我们知道一个线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便会终止,线程就会退出,那么做为App的主线程,如果代码段执行完了会怎样?,那么就会出现App启动后执行一段代码后就自动退出了,这是很不合理的。所以为了防止代码段被执行完,只能在代码中插入一个死循环,那么代码就不会被执行完,然后自动退出,怎么在在代码中插入一个死循环呢?那么Looper出现了,在主线程中调用
Looper.prepare()...Looper.loop()就会变当前线程变成Looper线程(可以先简单理解:无限循环不退出的线程),
Looper.loop()方法里面有一段死循环的代码,所以主线程会进入
while(true){...}的代码段跳不出来,但是主线程也不能什么都不做吧?其实所有做的事情都在
while(true){...}里面做了,主线程会在死循环中不断等其他线程给它发消息(消息包括:Activity启动,生命周期,更新UI,控件事件等),一有消息就根据消息做相应的处理,Looper的另外一部分工作就是在循环代码中会不断从消息队列挨个拿出消息给主线程处理。
三,Handler机制之Handler介绍:(这里我也会介绍handler是怎样和Looper以及MessageQueue联系起来的)
对于handler来说工作主要是消息的发送和接收过程。消息的发送可以通过post和send等一系列的方法来实现。但是需要说明的是,通过post的方法最后还是通过send来实现的。
通常我们在使用handler之前都会进行初始化,比如说比较常见的更新UI线程,我们会在声明的时候直接初始化。那么问题来了,handler是怎样和Looper以及MessageQueue联系起来的呢?它在子线程中发送的消息怎么发送到了MessageQueue中呢?
通过下面的系统源码我们就可以得到想要的答案!!!
首先我们先来看下Handler的构造函数:
public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class 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与looper的绑定,以及获得该looper对象中的消息队列等操作。同时在对mLooper进行判空的时候我们就可以发现一句熟悉的异常:”Can’t create handler inside thread that has not called Looper.prepare()”,而这个异常就是在子线程中我们在没有调用Looper.prepare()方法而直接实例化handler时所报的异常信息了。
如下图的异常信息:
友情提示:之所以咱们在主线程并没有调用Looper.prepare()方法而直接实例化handler时没有报错的原因是:Android首先在ActivityThread类的main的函数里面做了处理,UI线程本来只是一个普通线程,在这里会把UI线程转换成Looper线程。
请看main方法的入口源码:
public final class ActivityThread { public static final void main(String[] args) { ...... Looper.prepareMainLooper(); ...... ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } ...... Looper.loop(); ...... } }
接下来就到了handler发送消息的过程啦:
handler.sendMessage(new Message());
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
我们可以看到最后调用的是handler的enqueueMessage方法,在该方法中我们发现了一条熟悉的语句:msg.target=this;这句话就是对msg.target赋值为handler对象本身,正好对应上在looper.loop()方法中msg.target.dispatchMessage(msg);同时看到最后我们会发现最后调用的就是queue.enqueueMessage方法,而这个方法就是我们前面介绍的消息队列中进行插入消息的那个方法。
这样一来,我们就明确了handler发送的消息其实最后是进入了消息队列中,那插入消息到消息队列之后的操作是什么呢?
在前面已经说过Looper . Loop()方法是通过无限循环对消息队列进行读取消息,得到消息之后通过消息的target属性中的disPatchMessage()方法回调handlerMessage等方法。而handlerMessage方法就是我们在UI线程中实例化handler的过程中重写的handlerMessage方法。
对于disPatchMessage()方法的源码看下面:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
在源码中我们可以发现在这个函数中其实有两种处理:
一个是执行handleCallback(msg);另外一个就是执行handleMessage(msg);
对应的源码如下:
private static void handleCallback(Message message) { message.callback.run(); }
public void handleMessage(Message msg) { }
在我们实例化handler的时候,我们知道如果采用post方法,一般要new 一个Runnable对象,代码一般如下:
handler = new Handler(); handler.post(new Runnable() { @Override public void run() { //to do change UI } });
或者是下面的形式:
handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case xxx: //to do change UI break; } } };
而所谓的disPatchMessage所要做的就是对这两种操作进行回调,之后执行开发者自行重写的代码,这样就实现了从一个线程中发送消息,在另外一个线程收到消息的全过程。
在介绍的最后,我对handler机制的全过程的总结为:
首先Looper.prepare()会在当前线程保存一个looper对象,并且会维护一个消息队列messageQueue,而且规定了messageQueue在每个线程中只会存在唯一的一个。
Looper.loop()方法会使线程进入一个无限循环,不断地从消息队列中获取消息,之后回调msg.target.disPatchMessage方法。
我们在实例化handler的过程中,会先得到当前所在线程的looper对象,之后得到与该looper对象相对应的消息队列。
当我们发送消息的时候,即handler.sendMessage或者handler.post,会将msg中的target赋值为handler自身,之后加入到消息队列中。
在第三步实现实例化handler的过程中,我们一般会重写handlerMessage方法(使用post方法需要实现run方法),而这些方法将会在第二步中的msg.target.disPatchMessage方法中被回调,从而实现了message从一个线程到另外一个线程的传递。
下图是我看到的一篇文章里面的图片,觉得挺有意思的,也挺直观的,就给大家拿过来看看:
到这里,对于handler的机制的回顾就结束了,那么对于文章开篇的问题答案是什么呢?在整个机制中到底是哪个地方的原理使得handler达到了切换线程执行的效果呢?
请看下面的答案:
简单来说Handler是用于同一个进程的线程间通信。
Looper让主线程无限循环地从自己的
MessageQueue拿出消息处理,所以我们就可以知道处理消息肯定是在主线程中处理的,那么是怎样在其他的线程往主线程的队列里放入消息呢?道理其实很简单,我们知道在同一进程中线程和线程之间资源是共享的,也就是对于任何变量在任何线程都是可以访问和修改的,只要考虑并发性做好同步就行了,那么只要拿到MessageQueue 的实例,就可以往主线程的MessageQueue放入消息,主线程在轮询的时候就可以在主线程处理这个消息。那么是怎么拿到主线程 MessageQueue的实例呢,当然是可以拿到的(在主线程下
mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),Google 为了统一添加消息和消息的回调处理,又专门构建了Handler类,你只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用(获取方式
mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),Handler 在sendMessage的时候就通过这个引用往消息队列里插入新消息。Handler 的另外一个作用,就是能统一处理消息的回调。这样一个Handler发出消息又确保消息处理也是自己来做,这样的设计非常的赞。具体做法就是在队列里面的Message持有Handler的引用(哪个handler 把它放到队列里,message就持有了这个handler的引用),然后等到主线程轮询到这个message的时候,就来回调我们经常重写的Handler的
handleMessage(Message msg)方法。
所以,结果就是:Handler 对象在哪个线程下构建(Handler的构造函数在哪个线程下调用),那么Handler就会持有这个线程的Looper引用和这个线程的消息队列的引用。因为持有这个线程的消息队列的引用,意味着这个Handler对象可以在任意其他线程给该线程的消息队列添加消息,也意味着Handler的handlerMessage 肯定也是在该线程执行的。
上一篇: DataTable.Compute用法
下一篇: 扫描危险网址和文件的七种在线工具