在日常开发中我们经常用到hanlder来更新ui界面或者传递消息,他主要用于线程之间的通信,之前一直就这么用,对于他的深层原理并没有深究,现在这里来深入分析下。这篇文章将分析handler消息机制的四大要素,线程之间的通信过程,Threadlocal原理,HandlerThread的分析,更新UI等方面展开探究。
运行原理:
在android4.0之后,为了保证正主线程的流畅性,不能再主线程访问网络或者加载耗时间的数据,对于这写操作统一放到了子线程中进行,而在主线程中主要进行ui界面的更新,这时候我们就需要借助于handler消息机制在子线程与主线程之间相互通信这个过程主要涉及到四大要素:
- Message(消息)
- MessageQueue(消息队列)
- Looper(消息循环)
- Handler(消息发送和处理)
这四大要素的交互原理是:子线程访问网络获取到message,通过主线程的handler发送消息到主线程的消息队列MessageQueue,主线程的looper轮询器会一直轮询,当发现这个消息时,就把他取出来,交给handlerMessage方法处理,更新UI界面。
Message:
1、message的创建
通过阅读源码发现,message他实现了Parcelable接口,创建有两种方式一种是直接 new Message();另外一种是使用Message的静态方法obtain来获取,这两种方法的区别是第一种直接通过创建对象来获取message,这种方法不推荐,第二种是从message的消息池里面取得。 它类似一个线程池,创建了一个Message池,如果有闲置的Message就直接返回,不然就新建一个,用完以后,返回消息池,这种方法大大减少了当有大量Message对象而产生的垃圾回收问题吗,并且他还提供了大量形式的构造方法供我们来使用。Message.obtain()的消息池上限是50个。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
从以上源码中我们可以看出obtain方法的内部结构是一个消息池。
2、Message的常用方法;
(1)what:int类型的数据,用于指定消息的类型,标记消息从哪里来。
(2)arg0,arg1:两个int型值,一般用来传递简单的整型数据。
(3)obj:object类型的数据,用于传递复杂的数据。
(4)data:Bundle型,这个传递较多种数据的时候需要用到。
MessageQueue:
消息队列它是用来存放消息的一个队列结构,主要操作有两个:插入和读取,分别对应的方法就是:enqueueMessage(Message msg, long when)和next()。enqueueMessage方法就是将发送过来的消息插入到MessageQueue里面。而next方法是把插入的消息取出来,读取的时候是按照消息的先后顺序进行读取,在读取完成之后伴随着着消息的移除。他是一个死循环,返回一个Message对象作用于looper,没有Message时候,next方法就被阻塞住,同时looper也处于阻塞状态。
Looper:
looper是一个轮询器,它存在于主线程和子线程中,在主线程中,我们不需要手动创建,在系统启动的时候调用looper.prepareMainLooper()方法来创建主线程的looper和messageQueue,并且通过looper.looper()来启动主线程的消息循环。但是在子线程中,需要我们进行手动创建,不创建是没有的,并且创建好之后每个线程有且仅有一个不同的looper。他的作用的不断地循环,当发现消息队列里面有数据的时候就会立即从消息队列里面取出来,当没有消息时候就会阻塞。
1、创建子线程的looper
在子线程创建looper时候需要借助于Looper.prepare();方法,当创建完成之后需要调用Looper.loop()开启循环,在循环的时候执行的是Looper.prepare()和Looper.loop()之间的代码,在Looper.loop()之后的代码是不会被执行的。如下示例:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}).start();
2、looper的停止
Looper跳出死循环的唯一条件是消息队列的next方法返回null。Looper提供了quit、quitsafely来退出一个looper。他们两个是有所区别的,调用quit方法会直接停止,而quitsafely只是打了一个标记,当消息队列里被清空时候去执行退出操作。当被标记为退出状态时候,next返回null。Looper退出后,sendmessage发送的消息会失效。所以当执行结束后应该关闭looper,否则一直处于阻塞状态,当looper退出后,这个线程就被销毁掉了。
3、ThreadLocal
Looper中还有一个特殊的概念,那就是ThreadLocal,每一个线程,都会单独对应的一个looper,这个looper通过ThreadLocal来创建,保证每个线程只创建一个looper,looper初始化后就会调用looper.loop创建一个MessageQueue,这个方法在UI线程初始化的时候就会完成,我们不需要手动创建。ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。Handler内部如何获取到当前线程的Looper呢?这就要使用ThreadLocal了,ThreadLocal可以在不同的线程之中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是,子线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。
Handler:
handler它是一个在线程之间通讯的桥梁,充当了子线程与主线程之间的沟通的媒介。他不但存在于主线程,并且还存在于子线程,也就是说每个线程都存在handler,当我们创建handler时候,可以在其构造方法中指定他的线程,也可以不指定,当不指定,即构造方法为空的时候他默认是存在于当前线程。
1、Handler的创建
handler提供了多个构造方法来创建对象,如果我们传入空参,则和存在于当前前线程,另外我们指定线程时候:
Handler handler = new Handler(this.getMainLooper());//主线程的handler
Handler handler = new Handler(Looper.myLooper);//在子线程中获取子线程handler
然后复写HandlerMessage方法,获取其他线程发的值。
2、 HandlerThread详解
HandlerThread本质上还是一个thread,他继承自thread,内部封装了looper。HandlerThread与handler的区别是:handler与activity在同个线程中,而HandlerThread与activity在不同线程中。HandlerThread将loop转到子线程中处理,降低了主线程的压力,使主界面更流畅。使用步骤如下:
(1)创建一个HandlerThread,即创建了一个包含Looper的线程。
HandlerThread handlerThread = new HandlerThread("sohu.com");
handlerThread.start(); //创建HandlerThread后一定要记得start(),获取HandlerThread的Looper
(2)Looper looper = handlerThread.getLooper();创建Handler,通过Looper初始化
(3)Handler handler = new Handler(looper);
通过以上三步我们就成功创建HandlerThread。通过handler发送消息,就会在子线程中执行。
如果想让HandlerThread退出,则需要调用handlerThread.quit();。
3、handler中更新ui界面的方法(1)sendMessage发送
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
Toast.makeText(getApplicationContext(), string, Toast.LENGTH_LONG).show();
}
}
};
(2)使用post发送消息(延迟消息)
private Handler handler = new Handler();
Runnable runnable = new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(),string, Toast.LENGTH_LONG).show();
}
};
handler.post(runnable);
(3)使用runOnUiThread更新
new Thread(){
@Override
public void run() {
//在这里进行复杂的数据操作
// …………………………………………………………………………
runOnUiThread(new Runnable() {
@Override
public void run() {
//在这里进行更新UI的操作
Toast.makeText(getApplicationContext(),string, Toast.LENGTH_LONG).show();
}
});
}
}.start();
四大要素之间的关系
- 一个线程中只能有一个Looper,只能有一个MessageQueue,可以有多个Handler,多个Messge;
- 一个Looper只能维护唯一一个MessageQueue,可以接受多个Handler发来的消息;
- 一个Message只能属于唯一一个Handler;
- 同一个Handler只能处理自己发送给Looper的那些Message;
- 同一个线程Looper和MessageQueue是绑定的。
- 同一个线程可以有多个Handler,他们相互独立,互不干扰,各自处理各自的消息。不能在一个应用中同一个线程创建多个handler来接收每一个消息,他只能接收到各自handler发来的消息,虽然都是在同一线程。
线程间消息的发送
以上三种方法均是子线程向主线程发消息,那么主线程如何向子线程发消息以及子线程之间的通信呢?在上文提到在创建handler时候需要借助于looper,如下:
1、子线程向主线程发送消息:如上3所示
2、主线程向子线程发消息
(1)创建子线程
public class MyThread extends Thread{
@Override
public void run() {
Looper.prepare();
Looper looper = Looper.myLooper();
handler = new Handler(looper){
@Override
public void handleMessage(Message msg) {
}
};
Looper.loop();
}
}
(2)在onCreate方法中发送消息,在(1)方法中就可以接收到
MyThread myThread = new MyThread();
myThread.start();
handler.sendEmptyMessage(2000);
3、子线程向子线程发送消息
(1)创建HandlerThread方法并执行
HandlerThread myThread = new HandlerThread("test");
myThread.start();
(2)创建handler
final Handler handler = new Handler(myThread.getLooper()){
@Override
public void handleMessage(Message msg){
System.out.println("收到消息");
}
};
(3)然后再新建一个子线程来发送消息
new Thread(){
@Override
public void run() {
handler.sendEmptyMessage(2000);
}
}.start();
注意:handler使用不当可能会导致内存泄漏,所以在handler使用结束的时候需要在移除消息。handler.removeCallbacksAndMessages(null);即可移除消息。