Android Handler 、 Looper 、Message
声明:此文非本人原创,为整理网络资料加自己的一些注解所得。
参考:
http://blog.csdn.net/xukunhui2?viewmode=contents
http://blog.csdn.net/u012439416/article/details/52541671
https://blog.csdn.net/fightingxia/article/list/2
Binder主要用于跨进程通信,Handler主要用于进程内部进行通信,或者说进程内部不同线程之间进行通信,即是不同线程之间互相发送消息。
Handler:线程之间的切换
线程1:创建Handler对象mHandler,Looper对象、及MessageQueue(为主线程时只需创建Handler对象mHandler,其他本身已有)。接收message入队messageQueue,当其message出队时会通过Looper调用handle的handleMessage执行相应的操作。
线程2:用mHandler对象向线程1发送Message信息。
1、成员介绍
Message:主要功能是进行消息的封装,同时可以指定消息的操作形式;
Looper:消息循环泵,用来为一个线程跑一个消息循环。每一个线程最多只可以拥有一个。
MessageQueue:就是一个消息队列,存放消息的地方。以单链表的结构进行存储,每一个线程最多只可以拥有一个。
Handler:消息的处理者,handler 负责将需要传递的信息封装成Message,发送给Looper,继而由Looper将Message放入MessageQueue中。当Looper对象看到MessageQueue中含有Message,就将其广播出去。该handler 对象收到该消息后,调用相应的handler 对象的handleMessage()方法对其进行处理。
ThreadLocal:保证线程内数据安全。
2、同线程各成员的关系及数量
①一个线程中只能有一个Looper,只能有一个MessageQueue,可以有多个Handler,多个Messge;
②一个Looper只能维护唯一一个MessageQueue,可以接受多个Handler发来的消息;
③一个Message只能属于唯一一个Handler;
④同一个Handler只能处理自己发送给Looper的那些Message;
一、Handler
1.定义:
主要接受子线程发送的数据, 并用此数据配合主线程更新UI。
解释:当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件, 进行事件分发, 比如说, 你要是点击一个 Button ,Android会分发事件到Button上,来响应你的操作。如果此时需要一个耗时的操作,例如: 联网读取数据, 或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象, 如果5秒钟还没有完成的话,会收到Android系统的一个错误提示 "强制关闭"。 这个时候我们需要把这些耗时的操作,放在一个子线程中,因为子线程涉及到UI更新,Android子线程是线程不安全的。由于Handler运行在主线程中(UI线程中),它与子线程可以通过Message对象来传递数据, 这个时候,Handler就承担着接受子线程传过来的(子线程用sendMessage()方法传递)Message对象(里面包含数据) , 把这些消息放入主线程队列中,配合主线程进行更新UI。
一个Handler会允许你发送和处理Message或者Runnable对象关联到一个线程的消息队列MessageQueue中,每一个Handler的实例都会关联一个单一的线程和那个线程的消息队列中。当你创建一个新的Handler,它会绑定到你创建的线程和这个线程消息队列中。并且指向它,它会让消息传递到关联好它的消息队列中,当它从消息队列出队的时候执行它。当你的应用程序的进程被创建的时候,它的主线程专门用来处理正常运行的主线程的消息队列,(也就是说UI主线程有自己的消息队列,所以我们没必要在UI主线程中处理自己的消息)它关心的是管理顶层的应用对象(activities, broadcast receivers, etc)和他们创建的窗口。你可以创建你自己的线程,然后通过Handler与主线程沟通。就像上述说的通过post和sendMessage的方式,Runnable和Message会有计划的执行在Handler的消息队列中适时的进行处理。
2.Handler一些特点
handler可以分发Message对象和Runnable对象到主线程中, 每个Handler实例,都会绑定到创建他的线程中(一般是位于主线程),它有两个作用:
(1)安排消息或Runnable 在某个主线程中某个地方执行;
(2)安排一个动作在不同的线程中执行。
Handler中分发消息的一些方法
post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable long)
sendEmptyMessage(int)只发送一个带有what属性的消息。
sendMessage(Message)
sendMessageAtTime(Message,long)只发送一个带有what属性的消息,并且在一个指定的时间内(单位是ms)去发送。
sendMessageDelayed(Message,long)
以上post类方法允许你排列一个Runnable对象到主线程队列中,
sendMessage类方法, 允许你安排一个带数据的Message对象到主线程队列中,等待更新。
- post和sendMessage本质上是没有区别的,只是实际用法中有一点差别
- post也没有独特的作用,post本质上还是用sendMessage实现的,post只是一种更方便的用法而已
3.handler实例
子类需要继承Hendler类,并重写handleMessage(Message msg) 方法, 用于接受线程数据。
private Handler mHandler = new Handler() {
@Override
public void handleMessage(android.os.Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0://更新界面的数据
break;
}
}
}
mHandler.removeMessages(0);
二、Message
定义一个message包含描述信息和任意的数据对象发送给Handler。这个对象包含两个额外的int类型的属性和一个Object类型的属性,它可以让你不需要去做一些强制类型转换的操作。
- arg1 和 arg2 都是Message自带的用来传递一些轻量级存储int类型的数据,比如进度条的数据等。这个数据是通过Bundle的方式来转载的。
- obj 是Message自带的Object类型对象,用来传递一些对象。兼容性最高避免对齐进行类型转换等。
- replyTo 是作为线程通信的时候使用.
- what 用户自定义的消息码让接受者识别消息种类,int类型。
【注意】: 获得Message的构造方法最好的方式是调用Message.obtain() 和 Handler.obtainMessage()方法。而不是直接用 new Message的方式来获得Message对象。
调用obtain()方法来从消息池中获得一个消息的对象的。然后在通过参数传递来封装指定的Handler和需要携带的数据。如果使用这些重载的方法建议完成数据封装之后调用sendToTarget()方法。
Message message = Message.obtain();
Bundle bundle = new Bundle();
data.putStringArray("str", new String[]{"AHui", "AHui1", "AHui2"});
message.setData(bundle);
message.obj = obj;
message.what = 0;
message.arg1 = 1;
message.arg2 = 3;
message.sendToTarget(); // 完成发送消息的动作
message 从handler 类获取,从而可以直接向该handler 对象发送消息
handler.sendMessage(message); //直接调用 handler 的发送消息方法发送消息。
android 中发送消息不管是Message中的几种重载的obtain()方式,还是Handler中的几种重载的sendMessage最终都是通过Handler.sendMessage来发送的,而Handler中的几种sendMessage()重载方法最终都会调用到sendMessageAtTime()方法来完成消息的入队操作。
Looper类介绍
Looper与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue.
Queue.loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。,在这个线程中调用prepare()方法来启动一个循环,然后调用loop()就可以处理消息至到循环停止。
1.Looper.prepare(); 会初始化当前的线程关联一个Looper.创建了一个MessageQueue(消息队列)在一个线程中只能调用一次
2.Looper.loop()获取Looper对象,然后将消息从Looper中取出,然后赋值给MessageQueue,让MessageQueue去管理,接着在While(true)这个死循环里面一直在轮转的取消息和分发消息(从Message msg = queue.next();和msg.target.dispatchMessage(msg);)这两句代码读出。
3.mChildHandler.getLooper().quit();
三种启用线程的方法
Android的CPU分配的最小单元是线程,Handler一般是在某个线程里创建的,因而Handler和Thread就是相互绑定的,一一对应。 而Runnable是一个接口,Thread是Runnable的子类。所以说,他俩都算一个进程。 HandlerThread顾名思义就是可以处理消息循环的线程,他是一个拥有Looper的线程,可以处理消息循环。与其说Handler和一个线程绑定,不如说Handler是和Looper一一对应的。Handler是沟通Activity 与Thread/runnable的桥梁。而Handler是运行在主UI线程中的,它与子线程可以通过Message对象来传递数据
1.通过继承Thread类,并改写run方法来实现一个线程
public class MyThread extends Thread {
//继承Thread类,并改写其run方法
public void run(){
}
}
启动 new MyThread().start();
或者 new Thread() {
public void run() {
}
}.start();
2.创建一个Runnable对象
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
3.通过Handler启动线程
private Handler mHandler = new Handler();
private Runnable mRunnable = new Runnable() {
public void run() {
mHandler.postDelayed(mRunnable, 3000); //给自己发送消息,自运行
}
};
mHandler.post(mRunnable);
Thread主要函数有一个成员专门用于存储线程的ThreadLocal的数据。
run()//包含线程运行时所执行的代码
start()//用于启动线程
sleep()/sleep(long millis)//线程休眠,交出CPU,让CPU去执行其他的任务,然后线程进入阻塞状态,sleep方法不会释放锁
yield()//使当前线程交出CPU,让CPU去执行其他的任务,但不会是线程进入阻塞状态,而是重置为就绪状态,yield方法不会释放锁
join()/join(long millis)/join(long millis,int nanoseconds)//等待线程终止,直白的说 就是发起该子线程的线程 只有等待该子线程运行结束才能继续往下运行
wait()//交出cpu,让CPU去执行其他的任务,让线程进入阻塞状态,同时也会释放锁
interrupt()//中断线程,自stop函数过时之后,我们通过interrupt方法和isInterrupted()方法来停止正在运行的线程,注意只能中断已经处于阻塞的线程
getId()//获取当前线程的ID
getName()/setName()//获取和设置线程的名字
getPriority()/setPriority()//获取和设置线程的优先级 一般property用1-10的整数表示,默认优先级是5,优先级最高是10,优先级高的线程被执行的机率高
setDaemon()/isDaemo()//设置和判断是否是守护线程
currentThread()//静态函数获取当前线程
Thread线程主要状态
(1) New 一旦被实例化之后就处于new状态
(2) Runnable 调用了start函数之后就处于Runnable状态
(3) Running 线程被cpu执行 调用run函数之后 就处于Running状态
(4) Blocked 调用join()、sleep()、wait()使线程处于Blocked状态
(5) Dead 线程的run()方法运行完毕或被中断或被异常退出,线程将会到达Dead状态
线程同步
(1)synchronized同步函数或同步代码块
(2)使用特殊域变量(volatile)实现线程同步
private volatile int count = 1000;
a.volatile关键字为域变量的访问提供了一种免锁机制,
b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
对于Handler来说,它需要获取当前线程的Looper,通过ThreadLocal就可以轻松实现Looper在线程中的存取.如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper,这样一来就必须提供一个类似于LooperManager的类了,这就是ThreadLocal的好处。所以, 当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。比如Looper、ActivityThread以及AMS等都用到了ThreadLocal。