Android是大量的消息驱动方式来进行交互,Android某种意义上也可以说成是一个以消息驱动的系统。消息机制涉及MessageQueue/Message/Looper/Handler这4个类。
第三方框架的引入让Handler越来越没"用武之地"。然后掌握handler原理以及使用还是很有必要的。。阅读源码、面试装逼必不可少!也是程序员进阶的必经之路。
Android提供的一套消息机制
假若子线程允许访问 UI,则在多线程并发访问情况下,会使得 UI 控件处于不可预期的状态。
传统解决办法:加锁,但会使得UI访问逻辑变的复杂,其次降低 UI 访问的效率。
子线程直接更新UI抛出的异常:
找到对应源码
ViewRootImol.java
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
在更新UI的时候都会调用ViewRootImpl的checkThread()方法。
checkThread 方法判断更新UI的线程是否是ViewRootImpl的mThread 线程,不是的话就会抛出异常。
Message :分为硬件产生的消息和软件生成的消息
(拥有handler对象)
作用:可以携带少量信息,数据的载体
Message Queue :消息队列
(队列中拥有N个Message )
作用:用来存放通过Handler发过来的Message,按照先进先出执行
Looper :不断循环执行(Looper.loop),负责循环执行消息
(一个线程拥有一个Looper ,一个Looper拥有Message Queue )
作用:
1、Looper 所在的线程中循环取出Message Queue的Message,解决多线程并发问题
2、将取出的Message交付给相应的Handler去处理
Handler :Message的发送者、处理者
(拥有一个线程的线程Looper 和Message Queue 对象)
作用:
1、提供sendMessage方法,将任意线程的消息放置到队列中
2、提供dispatchMessage方法,在Looper 所在的线程处理消息
App启动后会找ActivityThread main()方法(入口原来在这呢!)
ActivityThread.java
public static void main(String[] args) {
...
//初始化UI线程的Looper对象
Looper.prepareMainLooper();
...
//循环处理消息(死循环)
Looper.loop();
}
线程调用 Looper.loop()要放在方法的最后面
当在UI线程 调用Looper.loop()后、UI线程将处于一个死循环中!所有硬件产生的消息(如按钮、触摸)和软件生成的消息都会被加到死循环中依次执行。
UI线程本身就是个死循环,随时等待着消息的到来。这也是为什么程序启动后任何时间发送的消息UI线程都能够处理的原因。
一个线程只有一个Looper 对象,一个Looper 对象中包含一个Message Queue对象
1、Looper.prepare() 初始化Looper
private static void prepare(boolean quitAllowed) {
//sThreadLocal.get()会根据不同线程返回对应的Looper,一线程一个Looper
if (sThreadLocal.get() != null) {
//prepare方法调用二次直接抛异常
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
ThreadLocal: 线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。感兴趣的可以看一下它的get和set源码。
2、Looper.loop() 循环处理Message Queue里的Message
public static void loop() {
...
//进入死循环
for (;;) {
//next()方法用于取出消息队列里的消息 (可能会阻塞线程)
Message msg = queue.next();
if (msg == null) {
//消息为空退出循环
return;
}
...
//调用对应的handler处理消息
msg.target.dispatchMessage(msg);
//释放消息占据的资源
msg.recycle();
}
}
loop()进入循环模式,不断重复下面的操作,直到没有消息时退出循环
Handler的构造方法
//--------------------------------------------无惨构造方法--------------------------------------------
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
...
//从当前线程的TLS中获取Looper对象
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//通过Looper获取消息队列
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
//--------------------------------------------有参数的构造方法--------------------------------------------
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
通过构造方法可以发现、handler的创建时需要绑定一个Looper对象的(handler需要绑定在一个线程上,消息处理回调也就在绑定的线程中执行)。一般有2中方式指定绑定的线程:
1. 不指定Looper对象,那么这个Handler绑定到了创建这个线程的线程上,消息处理回调也就在创建线程中执行.
2. 通过Loop.myLooper()得到当前线程的Looper对象/通过Loop.getMainLooper()可以获得当前进程的主线程的Looper对象。
在任意线程中我们可以通过Handler发送消息到Message queue中,发送消息分为sendMessage()和post(Runnable r)2大类:
---------------------sendMessage()---------------------
//发送一个Message
sendMessage(Message msg)
//发送一个延迟处理的Message
sendMessageDelayed(Message msg, long delayMillis)
//发送一个指定时间处理的Message
sendMessageAtTime(Message msg, long uptimeMillis)
//发送一个优先处理的Message
sendMessageAtFrontOfQueue(Message msg)
sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what, long delayMillis)
---------------------post(Runnable r)---------------------
//其实也是发送一个Message
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
postDelayed(Runnable r, long delayMillis)
postAtFrontOfQueue(Runnable r)
}
源码太多就不贴了,通过源码可以发现
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//Message.target = 发送它的handler
msg.target = this;
//是否异步处理
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//把Message添加到MessageQueue
return queue.enqueueMessage(msg, uptimeMillis);
}
Looper.loop()取出Message会调用msg.target.dispatchMessage(msg)方法msg.target就是Handler
public void dispatchMessage(Message msg) {
//msg.callback是一个Runable对象
//post方式才Message有的Runable
if (msg.callback != null) {
handleCallback(msg);
} else {
//handler构造方法传递的Callback(可选)
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
//屏蔽handleMessage方法
return;
}
}
//一般我们重写的handleMessage
handleMessage(msg);
}
}
public void handleMessage(Message msg) {
//由开发者重写方法
}
由上面的代码可以总结出:
//1、直接创建
Message message1 = new Message();
//2、从消息池复用废弃的Message,这里得到Message已经清空所有的数据
Message message2 = Message.obtain();
3.5.1 利用HandlerThread(推荐)
//1: 创建handlerThread对象
HandlerThread handlerThread = new HandlerThread("线程名称");
handlerThread.start();
//2: 创建Handler
Handler handler = new Handler(handlerThread.getLooper());
//3: 发送消息
handler.sendEmptyMessage(1);
```
**3.5.2 在普通Thread启动**
```java
class MyThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
// 1: 创建Handler
mHandler = new Handler() {
public void handleMessage(Message msg) {
//TODO 处理即将发送过来的消息
}
};
//放在最后
Looper.loop();
}
}
//2: 启动LooperThread线程
MyThread thread = new MyThread();
thread.start();
//3: 发送消息
MyThread.mHandler.sendEmptyMessage(1);
};
注意:这种方式不要把Handler实例化放在run()方法外面、否则可能Looper.prepare()还没执行就new Handler会导致程序崩溃
问题1:子线更新UI的方式有哪几种?
问题2:子线程一定不能更新UI嘛?
其实surfaceView Progress 内部也是通过handler去更新进度的
问题3:为什么非UI线程不能跟新UI?
假若子线程允许访问 UI,则在多线程并发访问情况下,会使得 UI 控件处于不可预期的状态。
传统解决办法:加锁,但会使得UI访问逻辑变的复杂,其次降低 UI 访问的效率。
问题4:Looper.loop()是死循环没有导致程序卡死?(见2.1启动App)
异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待。
一句话:程序运行的本身就是在这个死循环中、所以不存在卡死之说。
消息发送执行流程图
图片来自于:http://gityuan.com/2015/12/26/handler-message-framework/