游走Android系列之handler

来源:互联网 发布:溧阳市各乡镇经济数据 编辑:程序博客网 时间:2024/06/07 00:54

1、什么是handler?
 当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件,进行事件分发, 比如说, 你要是点击一个 Button ,Android会分发事件到Button上,来响应你的操作。 如果此时需要一个耗时的操作,例如: 联网读取数据, 或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象, 如果5秒钟还没有完成的话,会收到Android系统的一个错误提示 "强制关闭". 这个时候我们需要把这些耗时的操作,放在一个子线程中,因为子线程涉及到UI更新,Android主线程是线程不安全的,也就是说,更新UI只能在主线程中更新,子线程中操作是危险的. 这个时候,Handler就出现了。来解决这个复杂的问题 , 由于Handler运行在主线程中(UI线程中), 它与子线程可以通过Message对象来传递数据, 这个时候,Handler就承担着接受子线程传过来的(子线程用sedMessage()方法传递)Message对象,(里面包含数据) , 把这些消息放入主线程队列中,配合主线程进行更新UI。每个Handler实例,都会绑定到创建他的线程中(一般是位于主线程)。
在Android,这里的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper,这个是android的新概念。我们的主线程(UI线程)就是一个消息循环的线程。针对这种消息循环的机制,我们引入一个新的机制Handle,我们有消息循环,就要往消息循环里 面发送相应的消息,自定义消息一般都会有自己对应的处理,消息的发送和清除,消息的的处理,把这些都封装在Handle里面,注意Handle只是针对那些有Looper的线程,不管是UI线程还是子线程,只要你有Looper,我就可以往你的消息队列里面添加东西,并做相应的处理。
但是这里还有一点,就是只要是与UI相关的东西,就不能放在子线程中,因为子线程是不能操作UI的,只能进行数据、系统等其他非UI的操作。
2、与handler密切相关的Looper、Message和MessageQueue
handler:Handler可以发送Messsage和Runnable对象到与其相关联的线程的消息队列。handler与创建它的线程相关联,而且也只与创建它的线程相关联。handler运行在创建它的线程中,所以,如果在handler中进行耗时的操作,会阻塞创建它的线程。
Thread:所有与Handler相关的功能都是与Thread密不可分的,Handler会与创建时它所在的线程绑定;
Looper:消息循环,从MessageQueue中取出Message进行处理;默认情况下,一个线程并不和任何Looper绑定。当我们调用Looper.prepare()时,如果当前线程还没有和任何Looper绑定,那么将创建一个Looper让它和当前线程绑定。当我们调用Looper.loop()时,它将从当前线程的消息队列里取消息,处理消息,一直循环直到对该Looper调用quit()函数。
注意:Looper.loop()中是个while循环,只有对它所在线程的Looper调用了quit()函数,Looper.loop()函数才能完成,其后的代码才能得以运行。一个线程对应一个Looper,一个Looper对应一个消息队列MessageQueue。
Message:消息;
MessageQueue:消息队列,对消息进行管理,实现了一个Message链表;
HandlerThread:继承Thread,实例化时自动创建Looper对象,实现一个消息循环线程.
3、handler的有什么用处呢?
1)执行计划任务,你可以在预定的实现执行某些任务,可以模拟定时器。
2)线程间通信。在Android的应用启动时,会创建一个主线程,主线程会创建一个消息队列来处理各种消息。当你创建子线程时,你可以在你的子线程中拿到父线程中创建的Handler对象,就可以通过该对象向父线程的消息队列发送消息了。由于Android要求在UI线程中更新界面,因此,可以通过该方法在其它线程中更新界面。接受子线程发送的数据, 并用此数据配合主线程更新UI。andriod提供了 Handler 和 Looper 来满足线程间的通信。例如一个子线程从网络上下载了一副图片,当它下载完成后会发送消息给主线程,这个消息是通过绑定在主线程的Handler来传递的。
我们程序中可能存在一些耗时的操作,如果在UI线程中去执行这些操作,会导致UI阻塞无响应,这时,我们可以把它放在一个线程(工作线程)中去执行,执行完之后通知主线程来更新UI。
4、handler如何使用?
在主线程中使用handler非常简单,new一个Handler对象实现其handleMessage方法即可,在handleMessage中提供收到消息后相应的处理方法即可。
(1)子线程借用主线程的handler发送一条消息到主线程。
(2)这个消息会被主线程放入消息队列里面。
(3)主线程里面有一个轮询器looper会发现消息队列里面有一条消息,然后调用handler的消息处理方法handlermessage去处理这个消息。
(4)在handlermessage里面更新UI就可以了。
注意:直接在UI线程中开启子线程来更新TextView显示的内容,运行程序我们会发现,如下错 误:android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.翻译过来就是:只有创建这个控件的线程才能去更新该控件的内容。
5、handler的内部机制分析
android中有一种叫消息队列的说法,它遵循先进先出的原则。由于android的Handler机制,发送消息并不会阻塞线程,而接收消息会阻塞线程,当handler处理完一个Message对象才会接着去取下面一个消息进行处理。
平时编写程序时经常会遇到一些比较耗的任务,比如从网络读取数据,会导致运行它的线程阻塞,这些操作不能放在UI线程中,由此,必须开辟另外一个线程去执行这些任务。既然有了其它线程,就必不可免的要与主线程(UI线程)发生通信,如果子线程直接对主线程(UI线程)进行操作,由于主线程是负责界面元素管理的,一旦被中断或者发生其它意外情况,后果不堪设想,由此必须创建一种安全机制管理线程的通信,这就是handler,与handler相关的内容还有messageQueue和Looper。
messageQueue负责存储有关某一个线程(通常是主线程)的所有消息,而looper则负责管理消息队列,handler负责向消息队列发送消息和处理消息。
假设线程A创建了一个handlerA,则handlerA就负责对线程A的消息队列进行操作,比如,往A的消息队列里面发送一个消息,但是handler是为了线程通信诞生的,所以这个发送消息的线程一般不会是线程A,它可能会是在A里面创建的另一个线程B,线程B负责给A处理一些耗时的任务,处理完毕之后自然要通知线程A,这样就可以在线程B里面使用handlerA向线程A的消息队列里面发送一个消息,线程A里面通过重载方法建立对消息队列的处理方式,那么,消息队列里面的方法怎么取出来并且交到handler手上呢?这时,就是looper的作用了,looper会自动将消息取出来交给handler,处理完毕后,再去取下一个消息。如图所示:

handler

与Handler绑定的有两个队列,一个为消息队列,另一个为线程队列。Handler可以通过这两个队列来分别:
发送、接受、处理消息–消息队列;
启动、结束、休眠线程–线程队列;
Android OS中,一个进程被创建之后,主线程(可理解为当前Activity)创建一个消息队列,这个消息队列维护所有顶层应用对象(Activities, Broadcast receivers等)以及主线程创建的窗口。你可以在主线程中创建新的线程,这些新的线程都通过Handler与主线程进行通信。通信通过新线程调用Handler的post()方法和sendMessage()方法实现,分别对应功能:
post() 将一个线程加入线程队列;
sendMessage() 发送一个消息对象到消息队列;
当然,post()方法还有一些变体,比如postDelayed()、postAtTime()分别用来延迟发送、定时发送;
消息的处理,在主线程的Handler对象中进行;具体处理过程,需要在new Handler对象时使用匿名内部类重写Handler的handleMessage(Message msg)方法;
线程加入线程队列可以在主线程中也可以在子线程中进行,但都要通过主线程的Handler对象调用post()。
我们知道每个Activity都有一个Looper,所以主线程在接收Message是不需要调用Looper.prepare()和Looper.loop(),但是主线程以外的线程是不带Looper的,当线程要接收来自主线程的消息是就需要调用Looper.prepare()和Looper.loop()。
消息队列属于某个Looper对象,每个Looper对象通过ThreadLocal.set(new Looper())跟一个Thread绑定了,Looper对象所属的线程在Looper.Loop()方法中循环执行从MessageQueue队列读取Message对象,并把Message对象交由Handler处理,调用Handler的dispatchMessage方法。
现在我们再来看一下使用Handler的基本实现代码:

// 主线程中新建一个handlernormalHandler = new Handler() {public void handleMessage(android.os.Message msg) {btnSendMsg2NormalHandler.setText("normalHandler");Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]--normalHandler handleMessage run...", Thread.currentThread().getName()));}};...//发送消息到hanldermyThreadHandler.sendEmptyMessage(0); 


sendEmptyMessage到handleMessage的过程,途中经过Looper.MessageQueue队列,转由Looper所在的线程去处理了,这是一个异步的过程,当然Looper所在的线程也可以是sendEmptyMessage所在的线程。
Looper和Handler到底什么关系呢?
我在前面一直强调在主线程中使用handler,为什么要这么说呢,因为你在自己new一个新线程中去像我前面那样简单建立一个Handler,程序执行是会报错的:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:121)
at com.cao.android.demos.handles.HandleTestActivity$MyThread$1.<init>(HandleTestActivity.java:86)
at com.cao.android.demos.handles.HandleTestActivity$MyThread.run(HandleTestActivity.java:86)
为什么在主线程中不会报错,而在自己新见的线程中就会报这个错误呢?很简单,因为主线程它已经建立了Looper,看一下ActivityThread的源码:

public static final void main(String[] args) {SamplingProfilerIntegration.start();Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);Looper.loop();if (Process.supportsProcesses()) {throw new RuntimeException("Main thread loop unexpectedly exited");}thread.detach();String name = (thread.mInitialApplication != null)? thread.mInitialApplication.getPackageName(): "<unknown>";Slog.i(TAG, "Main thread of " + name + " is now exiting");}


在main函数中它已经做了这个事情了,为什么要调用 Looper.prepareMainLooper(); Looper.loop();我们可以进去看一下,在prepareMainLooper方法中新建了一个looper对象,并与当前进程进行了绑定,而在Looper.loop方法中,线程建立消息循环机制,循环从MessageQueue获取Message对象,调用 msg.target.dispatchMessage(msg);进行处理msg.target在myThreadHandler.sendEmptyMessage(0)设置进去的,因为一个Thead中可以建立多个Hander,通过msg.target保证MessageQueue中的每个msg交由发送message的handler进行处理,那么Handler又是怎样与Looper建立联系的呢,在Handler构造函数中有这样一段代码:

mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}mQueue = mLooper.mQueue;在新建Handler时需要设置mLooper成员,Looper.myLooper是从当前线程中获取绑定的Looper对象:public static final Looper myLooper() {return (Looper)sThreadLocal.get();}


若Looper对象没有创建,就会抛异常"Can't create handler inside thread that has not called Looper.prepare()"
这跟我前面讲的是一致的。所以我们在一个新线程中要创建一个Handler就需要这样写:

class MyThread extends Thread {public void run() { Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]-- run...", Thread.currentThread().getName()));// 其它线程中新建一个handlerLooper.prepare();// 创建该线程的Looper对象,用于接收消息,在非主线程中是没有looper的所以在创建handler前一定要使用prepare()创建一个LoopermyThreadHandler = new Handler() {public void handleMessage(android.os.Message msg) {Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]--myThreadHandler handleMessage run...", Thread.currentThread().getName()));}};Looper.myLooper().loop();//建立一个消息循环,该线程不会退出}}


在其它线程中Handler使用主线程的Looper的情况:
在新线程中要新建一个Handler需要调用Looper.prepare();也有另一种方法就是使用主线程中的Looper,那就不必新建Looper对象了:

threadMainLoopHandler =new Handler(Looper.getMainLooper()){public void handleMessage(android.os.Message msg) {Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]--threadMainLoopHandler handleMessage run...", Thread.currentThread().getName())); }//该handleMessage方法将在mainthread中执行};这时注意不要在handleMessage做太多的操作,因为它在主线程中执行,会影响主线程执行ui更新操作。使用Message.callback回调public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}


从dispatchMessage定义可以看出,如果Message对象自带callback对象,handler不会执行handleMessage方法而是执行message.callback中定义的run方法,当然callback还是在handler关联的looper所绑定的线程中执行的。实际上Handler.post(Runnable r)方法就是把r添加到一个msg.callback的,也就是说,下面两种写法,没有什么区别:
1.使用Message.callback

Message msg = Message.obtain(myThreadHandler,new Runnable() { @Override public void run() { Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]--myThreadHandler.Message.callback.run", Thread.currentThread().getName())); } }); myThreadHandler.sendMessage(msg); 

2.使用Handler.post

myThreadHandler.post(new Runnable() { @Override public void run() { Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]--myThreadHandler.Message.callback.run", Thread.currentThread().getName())); } }); 


 

3.Handler对Activity finish影响。
在开发的过程中碰到一个棘手的问题,调用Activity.finish函数Acitivity没有执行生命周期的ondestory函数,后面查找半天是因为有一个handler成员,因为它有一个delay消息没有处理,调用Activity.finish,Activity不会马上destory,所以记得在Ativity finish前清理一下handle中的未处理的消息,这样Activity才会顺利的destory.

6、存在的问题
在onCreate(),onStart(),onResume()中通过子线程来修改UI不会抛出异常 。
7、总结
Android中的Looper类,是用来封装消息循环和消息队列的一个类,用于在android线程中进行消息处理。handler其实可以看做是一个工具类,用来向消息队列中插入消息的。
(1)Android的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper。主线程(UI线程)就是一个消息循环的线程,其他线程创建的时候默认是没有这个Looper的。
(2)Looper.myLooper(); //获得当前的Looper
Looper.getMainLooper () //获得UI线程的Lopper
(3)Handle的初始化函数(构造函数),如果没有参数,那么他就默认使用的是当前的Looper,如果有Looper参数,就是用对应的线程的Looper。
(4)如果一个线程中调用Looper.prepare(),那么系统就会自动的为该线程建立一个消息队列,然后调用 Looper.loop();之后就进入了消息循环,这个之后就可以发消息、取消息、和处理消息。

(5) Looper类用来为一个线程开启一个消息循环。默认情况下android中新诞生的线程是没有开启消息循环的。(主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。)Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。
(6) 通常是通过Handler对象来与Looper进行交互的。Handler可看做是Looper的一个接口,用来向指定的Looper发送消息及定义处理方法。
默认情况下Handler会与其被定义时所在线程的Looper绑定,比如,Handler在主线程中定义,那么它是与主线程的Looper绑定。
mainHandler = new Handler() 等价于new Handler(Looper.myLooper()).
Looper.myLooper():获取当前进程的looper对象,类似的 Looper.getMainLooper() 用于获取主线程的Looper对象。
(7) 在非主线程中直接new Handler() 会报如下的错误:
E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception
E/AndroidRuntime( 6173): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
原因是非主线程中默认没有创建Looper对象,需要先调用Looper.prepare()启用Looper。
(8) Looper.loop(); 让Looper开始工作,从消息队列里取消息,处理消息。
注意:写在Looper.loop()之后的代码不会被执行,这个函数内部应该是一个循环,当调用mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。
(9) 基于以上知识,可实现主线程给子线程(非主线程)发送消息。
8、参考
http://my.oschina.net/u/156598/blog/87454
http://wangzhen19900908.iteye.com/blog/1539306