Handler 是将一个任务切换到指定的线程中去执行,这个切换线程指的是任何线程,不单单是主线程。
在 Looper.loop() 方法中,会调用 MessageQueue 的 next() 方法来获取新消息,然后会调用 msg.target.dispatchMessage(msg) 来处理消息。其中,msg 是 Message 类型,源码是:
public final class Message implements Parcelable {
//其他字段
Handler target; //target 处理
Runnable callback; //Runnable 类型的 callback
//代码省略
}
从源码中可以看到,target 是 Handler 类型,实际上就是转了一圈,通过 Handler 将消息传递给消息队列,消息队列又将消息分发给 Handler 来处理。在 Handler 代码中,消息处理调用了 Handler 的 dispatchMessage 方法,相关代码如下:
public void handleMessage(Message msg) {
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
从上述程序中可以看到,dispatchMessage 只是一个分发的方法,如果 Runnable 类型的 callback 为空则执行handlerMessage 来处理消息,该方法为空,我们一般将更新 UI 的代码写在该函数中;如果 callback 不为空,则执行 handleCallback 来处理,该方法会调用 callback 的 run 方法。其实这是 Handler 分发的两种类型,比如我们 post(Runnable callback)就不为空,此时就会执行 Runnable 的 run 函数;当我们使用 Handler 来 sendMessage 时通常不会设置 callback ,因此,也就执行 handlerMessage 这个分支。下面我们来看看通过 Handler 来 post 一个 Runnable 对象的实现代码。
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);//将消息插入到消息队列中
}
从上述程序中可以看到,在 post(Runnable r)时,会将 Runnable 包装成 Message 对象,并且将 Runnable 对象设置给 Message 对象的 callback 字段,最后会将该 Message 对象插入消息队列。sendMessage 也是类似实现的:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
因此不管是 post 一个 Runnable 还是 Message ,都会调用 sendMessageDelayed(msg,time)方法,然后该 Message 就会添加到消息队列中,当消息队列中取出该消息时就会调用 callback 的 run 方法或者 Handler 的 handlerMessage 来执行相应的操作。
更新主线程(UI线程)的 Handler的实例:
//实例1
final Handler handler1 = new Handler(){//在UI线程中创建
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//更新 UI 的操作
Log.i("handleMessage","更新UI操作");
}
};
//子线程
new Thread(){
@Override
public void run() {
super.run();
try {//模拟阻塞
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler1.sendEmptyMessage(123);
}
}.start();
//实例2
Handler handler2 = new Handler();//在UI线程中创建
//子线程
new Thread(){
@Override
public void run() {
super.run();
try {//模拟阻塞
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler2.post(new Runnable() {
@Override
public void run() {
Log.i("post 方法",""+currentThread().getName());
Toast.makeText(TestActivity.this,"post 方法:"+currentThread().getName()+"更新UI操作",Toast.LENGTH_LONG).show();
}
});
}
}.start();
① 调用 Looper 的 prepare() 方法为当前线程创建 Looper 对象,创建 Looper 对象时,它的构造器会创建与之配套的 MessageQueue。
② 有了 Looper 之后,创建 Handler 子类的实例,重写 handlerMessage() 方法,该方法负责处理来自其他线程的消息。
③ 调用 Looper 的 loop() 方法启动 Looper。
这个是所有 Handler 使用的步骤,那么平常我们使用怎么 new Handler 就可以了呢?其实在android 主线程中在程序入口ActivityThread.main 方法中初始化了,这也就是在主线程中默认可以使用Handler的原因。ActivityThread.main的源码如下:
public static void main(String[] args) {
//代码省略
Looper.prepareMainLooper();//Loope初始化r
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();//开启loop循环
throw new RuntimeException("Main thread loop unexpectedly exited");
}
所以在一般我们不使用主线程的Looper 的时候,就要按照上述的3个步骤进行编写就可以了。
android 中我们经常做的是在子线程中发消息给主线程(UI线程),然后在主线程中更新 UI 操作。那么我们可以从一个子线程到另一个子线程进行消息的传递吗?答案是肯定可以的,下面列出源码:
private Handler handler;//Handler 对象
//第一个子线程
class MyThread1 extends Thread{
public MyThread1(String name){super(name);}
@Override
public void run() {
super.run();
Looper.prepare();
Looper looper = Looper.myLooper();//在子当前线程中初始化 Looper
Log.i("当前子线程是----->","step 1:"+currentThread().getName());
handler = new Handler(looper){//以当前 Looper构建 Handler 对象
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i("当前子线程是----->","step 2:"+currentThread().getName()+":接收到数据为:"+msg.what);
}
};
Looper.loop();
}
}
new MyThread1("thread1").start();//开启线程
//第二个子线程
class MyThread2 extends Thread{
public MyThread2(String name){super(name);}
@Override
public void run() {
super.run();
Log.i("当前子线程是----->","step 3:"+currentThread().getName()+" ");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("当前子线程是----->","step 4:"+currentThread().getName()+" 10s后要发送数据");
handler.sendEmptyMessage(1);
}
}
new MyThread2("thread2").start();//开启线程
运行结果:
当前子线程是----->: step 1:thread1
当前子线程是----->: step 3:thread2
//10s 后
当前子线程是----->: step 4:thread2 10s后要发送数据
当前子线程是----->: step 2:thread1:接收到数据为:1
官方文档中有如下:
An ANR will be triggered for your app when one of the following conditions occur:
大致意思为:
对于线程是一段可以执行的代码,当可执行代码执行完后,线程生命周期便该终止了,线程退出。对于android 中的主线程,我们是绝对不希望会被运行一段时间,自己就退出的,那么如何保证能一直存活呢?简单的做法就是可执行代码时能一直执行下去,死循环变能保证不会被退出。 那么既然是死循环又如何去处理其他事务呢?答案是创建新线程的方式。真正会卡死主线程的操作是在回调方法 onCreate/onStart/onDesotry/onResume等操作时间过长,会导致掉帧,甚至发生 ANR,而Looper.loop 本身不会导致应用卡死。
下面是ActivityThread.main()中:
public static void main(String[] args) {
....
//创建Looper和MessageQueue对象,用于处理主线程的消息
Looper.prepareMainLooper();
//创建ActivityThread对象
ActivityThread thread = new ActivityThread();
//建立Binder通道 (创建新线程)
thread.attach(false);
Looper.loop(); //消息循环运行
throw new RuntimeException("Main thread loop unexpectedly exited");
}
thread.attach(false);便会创建一个Binder线程,该Binder线程通过 Handler 将Message 发送给主线程。 主线程死循环一直运行会导致消耗CPU资源严重吗?然而并不是,这里涉及到Linux pipe/epoll机制,简单来说,当MessageQueue中没有消息时,主线程便会释放CPU资源进入休眠状态,直到下一个消息的到达。
其实抛开那些原理和底层代码,简单来说:
首先将这两个概念分开来看的话,其实 “Looper死循环” 和 ANR 这两之间一点联系都没有,完全两码事。
其一:Looper上的阻塞,前提是没有输入事件,MessageQueue为空,Looper空闲状态,线程进入阻塞,释放CPU执行权,等待唤醒。
其二:UI耗时导致卡死,前提是要有输入事件,MessageQueue不为空,Looper正常轮询,线程并没有阻塞,但是该事件执行时间过长(5秒),而且与此期间其他的事件(按键按下,屏幕点击…)都没办法处理(卡死),然后就ANR异常了。
站在巨人的肩膀上:
Android源码设计模式解析与实战 ——何红辉 关爱民
Android中为什么主线程不会因为Looper.loop()里的死循环卡死?