深入理解Android中的Handler异步通信机制
一、问题:在android启动后会在新进程里创建一个主线程,也叫ui线程(非线程安全)这个线程主要负责监听屏幕点击事件与界面绘制。当application需要进行耗时操作如网络请求等,如直接在主线程进行容易发生anr错误。所以会创建子线程来执行耗时任务,当子线程执行完毕需要通知ui线程并修改界面时,不可以直接在子线程修改ui,怎么办?
解决方法:message queue机制可以实现子线程与ui线程的通信。
该机制包括handler、message queue、looper。handler可以把消息/runnable对象发给looper,由它把消息放入所属线程的消息队列中,然后looper又会自动把消息队列里的消息/runnable对象广播到所属线程里的handler,由handler处理接收到的消息或runnable对象。
1、handler
每次创建handler对象时,它会自动绑定到创建它的线程上。如果是主线程则默认包含一个message queue,否则需要自己创建一个消息队列来存储。
handler是多个线程通信的信使。比如在线程a中创建ahandler,给它绑定一个alooper,同时创建属于a的消息队列amessagequeue。然后在线程b中使用ahandler发送消息给alooper,alooper会把消息存入到amessagequeue,然后再把amessagequeue广播给a线程里的ahandler,它接收到消息会进行处理。从而实现通信。
2、message queue
在主线程里默认包含了一个消息队列不需要手动创建。在子线程里,使用looper.prepare()方法后,会先检查子线程是否已有一个looper对象,如果有则无法创建,因为每个线程只能拥有一个消息队列。没有的话就为子线程创建一个消息队列。
3、完整创建handler机制
handler类包含looper指针和messagequeue指针,而looper里包含实际messagequeue与当前线程指针。
下面分别就ui线程和worker线程讲解handler创建过程:
首先,创建handler时,会自动检查当前线程是否包含looper对象,如果包含,则将handler内的消息队列指向looper内部的消息队列,否则,抛出异常请求执行looper.prepare()方法。
- 在ui线程中,系统自动创建了looper 对象,所以,直接new一个handler即可使用该机制;
- 在worker线程中,如果直接创建handler会抛出运行时异常-即通过查‘线程-value'映射表发现当前线程无looper对象。所以需要先调用looper.prepare()方法。在prepare方法里,利用threadlocal<looper>对象为当前线程创建一个looper(利用了一个values类,即一个map映射表,专为thread存储value,此处为当前thread存储一个looper对象)。然后继续创建handler,让handler内部的消息队列指向该looper的消息队列(这个很重要,让handler指向looper里的消息队列,即二者共享同一个消息队列,然后handler向这个消息队列发送消息,looper从这个消息队列获取消息)。然后looper循环消息队列即可。当获取到message消息,会找出message对象里的target,即原始发送handler,从而回调handler的handlemessage() 方法进行处理。
handler机制
4、核心
- handler与looper共享消息队列,所以handler发送消息只要入列,looper直接取消息即可。
- 线程与looper映射表:一个线程最多可以映射一个looper对象。通过查表可知当前线程是否包含looper,如果已经包含则不再创建新looper。
5、基于这样的机制是怎样实现线程隔离的,即在线程中通信呢。
核心在于每一个线程拥有自己的handler、message queue、looper体系。而每个线程的handler是公开的。b线程可以调用a线程的handler发送消息到a的共享消息队列去,然后a的looper会自动从共享消息队列取出消息进行处理。反之一样。
子线程向主线程发送消息
子线程之间通信
二、上面是基于子线程中利用主线程提供的handler发送消息出去,然后主线程的looper从消息队列中获取并处理。那么还有另外两种情况:
1、主线程发送消息到子线程中;
采用的方法和前面类似。要在子线程中实例化ahandler并设定处理消息的方法,同时由于子线程没有消息队列和looper的轮询,所以要加上looper.prepare(),looper.loop()分别创建消息队列和开启轮询。然后在主线程中使用该ahandler去发送消息即可。
2、子线程a与子线程b之间的通信。
三、handler里面有什么实用的api吗?
请记住:
handler只是简单往消息队列中发送消息而已(或者使用post方式)
它们有更方便的方法可以帮助与ui线程通信。
如果你现在看看handler的api,可以清楚看到这几个方法:
- post
- postdelayed
- postattime
代码示例
这里的代码都是很基础的,不过你可以好好看看注释。
示例1:使用handler的“post”方法
public class testactivity extends activity { // ... // all standard stuff @override public void oncreate(bundle savedinstancestate) { // ... // all standard stuff // we're creating a new handler here // and we're in the ui thread (default) // so this handler is associated with the ui thread handler mhandler = new handler(); // i want to start doing something really long // which means i should run the fella in another thread. // i do that by sending a message - in the form of another runnable object // but first, i'm going to create a runnable object or a message for this runnable mrunnableonseparatethread = new runnable() { @override public void run () { // do some long operation longoperation(); // after mrunnableonseparatethread is done with it's job, // i need to tell the user that i'm done // which means i need to send a message back to the ui thread // who do we know that's associated with the ui thread? mhandler.post(new runnable(){ @override public void run(){ // do some ui related thing // like update a progress bar or textview // .... } }); } }; // cool but i've not executed the mrunnableonseparatethread yet // i've only defined the message to be sent // when i execute it though, i want it to be in a different thread // that was the whole point. new thread(mrunnableonseparatethread).start(); } }
如果根本就没有handler对象,回调post方法会比较难办。
示例2:使用postdelayed方法
近期本站新介绍的特性中,我每次都要模拟edittext的自动完成功能,每次文字改变后都会触发一个api的调用,从服务器中检索数据。
我想减少app调用api的次数,所以决定使用handler的postdelayed方法来实现这个功能。
本例不针对平行处理,只是关于handler给消息队列发送消息还有安排消息在未来的某一点执行等。
// the below code is inside a textwatcher // which implements the ontextchanged method // i've simplified it to only highlight the parts we're // interested in private long lastchange = 0; @override public void ontextchanged(final charsequence chars, int start, int before, int count) { // the handler is spawned from the ui thread new handler().postdelayed( // argument 1 for postdelated = message to be sent new runnable() { @override public void run() { if (nochangeintext_inthelastfewseconds()) { searchandpopulatelistview(chars.tostring()); // logic } } }, // argument 2 for postdelated = delay before execution 300); lastchange = system.currenttimemillis(); } private boolean nochangeintext_inthelastfewseconds() { return system.currenttimemillis() - lastchange >= 300 }
最后我就把“postattime”这个方法作为联系留给读者们了,掌握handler了吗?如果是的话,那么可以尽情使用线程了。
上一篇: c#字符串使用正则表达式示例
下一篇: 详解Java常用工具类—泛型
推荐阅读
-
深入理解Android中的Handler异步通信机制
-
深入理解Android 5.0中的Toolbar
-
深入理解Android中的Window和WindowManager
-
深入理解Android 5.0中的Toolbar
-
Android的线程通信:消息机制原理(Message,Handler,MessageQueue,Looper),异步任务AsyncTask,使用JSON
-
深入理解Android中的Window和WindowManager
-
Android 中ThreadLocal的深入理解
-
Android 中ThreadLocal的深入理解
-
Android 图片缓存机制的深入理解
-
深入Android Handler与线程间通信ITC的详解