四大核心对象
1. Message 消息(数据载体)
2. MessageQueue 消息队列(存储Handler发来的消Message、Runnable)
3. Looper 轮询器,循环不断地从MessageQueue取消息,交给相应的Handler处理
4. Handler 发送消息和处理消息
Looper
有两个核心方法 prepare() 和 loop()
- prepare()
prepare()内部调用prepare(true),用来给当前线程创建looper
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) { //一个线程只能有一个looper
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));//若当前线程还没looper,就给它设一个
}
- loop()
调用loop(),线程正式开启无限循环
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // 取下一个消息,没有消息时阻塞
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
//Handler发消息的方法中,都会将msg.target赋值为this,即target就是发这个msg的handler对象
msg.target.dispatchMessage(msg); //让相应handler来处理消息
...
}
}
从这两个方法可以看出Looper的主要作用:
1.与当前线程绑定,保证一个线程只有一个Looper实例,而Looper内部也只有一个MessageQueue。
2.不断取消息,交给发消息的的那个handler去处理
Handler
Handler有多个构造方法,取其中的一个来看源码
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();//获取当前线程的looper对象
if (mLooper == null) { //当前线程没有looper,抛异常
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
如源码所示,要使用Handler,就先得给当前线程创建一个Looper。
问:在Activity中,明明直接new一个Handler就可以正常使用了,哪需要创建looper?
答:我们经常说的UI thread其实就是ActivityThread,也就是主线程。我们都知道主线程可以使用Handler进行异步通信,因为主线程中已经创建了Looper,而这个Looper就是在ActivityThread的main方法中创建的。如果其他线程需要使用Handler通信,就要自己去创建Looper。
(根据Looper.prepare()源码,如果多此一举地调用prepare()给主线程创建Looper实例,会抛出异常!)
ActivityThread.main()源码:
public static final void main(String[] args) {
SamplingProfilerIntegration.start();
……
Looper.prepareMainLooper();//在这里创建主线程Looper
if (sMainThreadHandler == null) {
sMainThreadHandler = new Handler();
}
ActivityThread thread = new ActivityThread();
thread.attach(false);
……
Looper.loop();//开始循环
……
thread.detach();
……
}
实例:子线程处理消息
private Hanlder mHandler;
//Semaphore用来控制多个线程之间的通信
private Semaphore mHandleSemaphorer = new Semaphore(0);//0个信号量
private void init(){
new Thread(){
public void run(){
Looper.prepare(); //给子线程设置一个Looper
mHandler = new Handler(){ //此时,Handler构造方法会自动与当前子线程的looper绑定
@override
public void handleMessage(Message msg){ //子线程处理消息、任务 } };
mHanlderSemaphore.release();//释放1个信号量(执行到这里,说明上面的mHandler初始化完毕)
Looper.loop(); //开始无限循环取消息 } }.start();
}
//当多个并发线程访问同一个Handler对象,需要保证只有1个线程执行该方法
//否则,n个线程被acquire阻塞,而mHanlder初始化完毕后只释放了1个信号量,会导致n-1个线程永远被阻塞
private synchronized void addTask(){
if(mHanlder == null){ //mHandler还没初始化,需要等待初始化完成
try {
//有信号就放行;没有则阻塞线程(acquire相当于wait;release相当于notify)
mHanlderSemaphore.acquire();//等上面的mHandler初始化完成,释放了信号,这里就会放行
} catch(Exception e){}
}
mHanlder.sendEmptyMessage(11);//上面放行了,mHanlder才能开始发消息
}
在Android中实现线程池,可以通过这个实例中的子线程,处理连续的耗时操作(如:加载图片)。
值得注意的是线程安全问题:多个线程同时操作mHandler,有可能会在mHandler还没完成初始化时就被其他线程调用。
用Java提供的并发类Semaphore可以解决多线程通信的问题。
给Handler绑定一个Looper,Handler就可以不断接收从Looper中分发过来的消息,然后在绑定这个Looper的线程中处理消息。
要给Handler绑定一个looper有2个常用方式:
1.直接在子线程的Looper.prepare()后创建Handler实例(如上面实例代码)
2.直接调用Handler的带参构造方法: Handler(Looper looper) 。
问题:
在子线程里创建了Looper,然后在主线程new Handler(looper)进行初始化时,会导致空指针异常。
原因:
多线程共享同一个Looper变量时,子线程的Looper还没初始化完毕,主线程就已经调用new Handler(Looper)。
可以用HandlerThread代替Thread来解决这个问题。示例代码如下:
private void test(){ <pre name="code" class="java"> //1.创建HandlerThread(将这个HandlerThread命名为"handler thread")
HandlerThread thread = new HandlerThread("handler thread");
thread.start();//这个HandlerThread线程的run方法中会自动创建1个Looper
//2.把HandlerThread的Looper传给handler(不会再产生空指针异常了)
Hanlder handler = new Handler(thread.getLooper()){
@override
public void handleMessage(Message msg){
System.out.println("current thread is -- " + Thread.currentThread());
//打印结果为"current thread is -- Thread[handler thread,5,main]"
}
};
} HandlerThread的run方法会自动帮我们创建Looper,不需要我们动手;
getLooper()内部会先判断Looper是否为null,如果为null就调用wait(),等它初始化完成后调用notifyAll()来唤醒。
前面我们用Looper.prepare()和loop()来为子线程创建Looper,而HandlerThread类已经帮我们实现了带Looper的子线程,使用它会更加简便。
HandlerThread常常用来处理耗时操作。
根据Looper.loop()源码,Looper取出消息后,会调用msg.target.dispatchMessage(msg),即调用目标handler自身的dispatchMessage(msg)。
我们来看看dispatchMessage源码
public void dispatchMessage(Message msg) {
if (msg.callback != null) { //callback是传给Handler的Runnable任务
handleCallback(msg); //执行Runnable的run方法
} else {
if (mCallback != null) { //mCallback是Hanlder(Callback)中传来的Callback对象
//执行handleMessage(),返回true就return,拦截掉后面的handler.handleMessage()
if (mCallback.handleMessage(msg)) {
return;//这里return,那么下面的handleMessage方法就不会执行
}
}
handleMessage(msg);//调用hanlder.handleMessage方法
}
}
看来,在Hanlder中,Runnable任务的会更优先地被处理,其次是Callback.handleMessage();
Handler.hanldeMessage方法不是必须被执行的,所以没被写成抽象方法来让我们强制实现它;而且可以通过Callback.handleMessage方法返回true把它拦截掉。
Handler的三种用法
1. sendMessage();
sendMessageDelayed(); //延时发消息
2. post(Runnable); //执行任务(Handler绑定哪个线程的Looper,就在那个线程中执行任务)
postDelayed(Runnable, long); //延时执行任务
3. removeCallbacks(Runnable); //移除指定的任务
removeMessage(int); //移除指定的消息
---- 补充 ----
Android中更新UI的几种方式
1. 用主线程Handler的方法: sendMessage、post
2. Activity的runOnUiThread(runnable)
内部会判断当前线程是否为UI线程,是就直接执行;不是就用Activity自带的主线程handler.post(runnable)来执行
3. view.post(runnable) 同样是在主线程中执行
view.postDelay(runnable)
问:以上几种方式都是在UI线程中更新UI,那么在非UI线程中可不可以更新UI呢?
答:在Activity.onCreate()中创建的子线程可以更新UI(更新UI前不能进行太多繁琐的操作,也不能sleep一段时间)
原因如下:
- 首先,非UI线程更新UI为什么会报错?
所有更新UI的操作最终都会调用 View.invalidate(),该方法调用了 ViewParent.invalidateChild(),而这个ViewParent实际上是1个 ViewRootImpl 类的引用,
所以,这个invalidateChild()其实是调用 ViewRootImpl.invalidateChild(),它会调用 checkThread()来检查更新UI的线程是否为UI线程。
这个方法链可以理解为:
View.invalidate() → invalidate(true) → ViewRootImpl.invalidateChild() → ViewRootImpl.invalidateChildInParent() → ViewRootImpl.checkThread()
通常我们在非UI线程中更新UI时,就会报错(如下图所示),可以看出报错的正是 ViewRootImpl.checkThread()。
ViewRootImpl 是一个隐藏类,只能翻Framework层源码才能找到。其中 checkThread()源码如下:
void checkThread() {
if (mThread != Thread.currentThread()) { //判断UI线程和当前线程是否为同一个线程
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
这方法源码很简单,就是判断该线程是不是UI线程,不是就抛出异常。
- 为什么可以在onCreate()中用非UI线程更新UI?
一句话解释:
系统执行 onCreate() 时,上面所说的 ViewRootImpl 还没被创建呢,所以它的 checkThread() 就不会被调用;
那么,ViewRootImpl 什么时候创建完成呢?
在 onResume() 被执行后 ViewRootImpl 才会被生成,也就是说,不仅能在 onCreate() 中用子线程更新UI,onStart()和onResume()也都可以(不过之后跳到其他Activity再跳回来时同样可能会报错,因为此时的ViewRootImpl已经创建好了)
最后提醒一句,虽然有时可以用非UI线程更新UI,但并不推荐这么做!!!
参考文章: 为什么我们可以在非UI线程中更新UI