我们知道,Android里面的线程做了这样的分工:主线程不做耗时操作,子线程不更新UI。
这是因为Android系统是个单线程模型,系统给App分配的进程里,只有一个主线程,主线程是App进程的核心,所有的组件都在主线程里实现、调度和管理,组件的绘制、互动操作和生命周期回调都是主线程处理的,这个线程如果出现阻塞、或多线程死锁、或CPU饥饿,导致事件处理时间过长,甚至没有机会得到处理,系统就会处理为ANR,强制关闭App。
所以主线程不能做耗时操作,耗时操作需要我们在子线程里处理。同时,Android不允许子线程刷新UI,因为Android无法保证多个线程同步操作,这样子线程和主线程之间就需要做协调,比如子线程从网络服务器拿来数据要展示到UI,就需要告知主线程去向UI里刷新数据,这就需要Handler消息机制了。
主线程、Looper和消息队列
Handler消息机制是围绕一个消息队列展开的,系统启动App时,App进程会启动ActivityThread主线程,主线程启动时,会创建一个Looper来循环处理消息,ActivityThread的main函数主要就是让这个Looper做死循环,如果消息循环停止了,App也就停止了:
//ActivityThread源码
Looper.loop();
//主线程会一直loop,如果出了looper,就直接抛异常,关闭app
throw new RuntimeException("Main thread loop unexpectedly exited");
Looper自己会去创建和管理一个消息队列,然后就开始不断地处理队列里的消息,子线程向消息队列里发消息,就可以和主线程通信了。
消息队列为空时,主线程会休眠,释放CPU资源。(主线程大部分时间都在休眠)
但是,Android为了确保主线程安全,Looper的消息队列是ThreadLocal的,禁止其他线程访问,所以子线程不能直接向消息队列里发消息。
这样,就需要在主线程里放一个Handler,Handler在创建时,会引用所在线程的Looper,进而获得Looper中的消息队列,这样,我们就可以用Handler来帮忙处理消息了。
这样一来,处理消息的主力就是Handler了。
Message
为了配合Handler,Message里面有这样几个主要属性:
- what:int值,存放消息ID(Send方式)
- callback:Runnable对象(Post方式,所以如果消息的callback非空,就直接处理runnable)
- target:Handler对象,指向Handler(消息还是丢给Handler处理)
- data: Bundle对象,存放业务数据
- next:Message对象,存放队列中的下一个Message(链式)
这几个主要的属性在Handler消息机制中非常重要,Handler要使用这几个属性才能正常运转。
Handler
Handler有两个作用:
- 发消息,子线程通过Handler,向消息队列发消息
发消息有两种方式,send一个消息ID(msg.what),或者post一个Runnable(msg.callback),这两种方式最终都是包装为一个Message,通过一个sendMessageAtTime函数,把消息推进队列里。 - 处理消息,Looper通过Handler,逐个处理队列中的消息
Looper在逐个处理消息时,拿到每个msg后,会去执行msg.target.dispatchMessage(msg),这里面的target其实就是Handler,这里会做一个判断,如果msg.callback不为空,说明消息里有Runnable(post进来的),就调用msg.callback.run,整个过程其实就是在Handler里调用Runnable的run函数;
mHandler.post(new Runnable() {
@Override
public void run() {
}
});
如果msg.callback为空,说明没有Runnable(send进来的),就调用handlemessage,也就是执行Handler的handlemessage函数;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
这样,Handler消息机制就能正常运转了。
Handler消息机制多见于主线程UI刷新,不过这并不是它的主业,它其实是用来实现Android线程间通信的,所以只要是跨线程的通信,都需要用到Handler,我们可以自己在子线程里写Looper。
如果我们要在子线程里使用Looper,可以使用HandlerThread,它会帮我们维护一个Looper;如果我们直接用Thread,就需要自己在run函数里做Looper的prepare和loop。
HanderThread
HanderThread就是handler+thread+looper,它有这么几个特点:
1.本质上是一个Thread
2.已经准备好了Looper,可以做Looper循环
3.本身是工作线程,其looper不在UI主线程,可以在handleMessage方法中执行异步任务
4.虽然不能并发处理多任务(因为是一个线程,不是线程池),但是性能损耗小
5.因为已经有Looper,所以可以在线程里创建和使用Handler
其他机制
Android的其他异步处理机制,其实都与Handler机制有关:
- View.post(Runnable)/View.postDelayed(Runnable, long),这个机制一般也是走主线程的Handler处理,不过,如果View所在的视图树没有attach到window,就会自己维护一个RunQueue,在视图树performTraversals时,把RunQueue里的消息全部取出来,让视图树集中处理掉。
- Activity.runOnUiThread(Runnable),这个函数其实是也是走的Handler机制,如果当前线程就是UI线程,则立即执行;如果当前线程不是UI线程,就把Runnable发给UI线程的消息队列,排队执行。runOnUiThread的源码大概是这样的:
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
- AsyncTask,AsyncTask实际上是个封装了线程池和handler的轻量级异步框架,它虽然用一个线程池做异步处理,但是处理结果总要提交回主线程,实际上就是使用一个Handler(AsyncTask有一个InternalHandler对象)来向主线程发消息(MESSAGE_POST_RESULT),并在主线程处理消息(handleMessage),这就要求把Handler放在主线程里,以便绑定主线程的Looper和消息队列,所以AsyncTask需要在主线程里创建和调用。
有时候,AsyncTask持有的Activity对象可能会被销毁重建(如屏幕旋转),这就会导致AsyncTask指向的Activity对象无效,新的Activity无法接收AsyncTask的onPostExecute(),出现结果丢失的现象。
Handler的内存泄露
我们使用Handler时,如果用内部类或匿名内部类的方式去使用,就会遇到内部类持有外部对象导致的内存泄露(非静态内部类,在编译后的构造函数中会引用所在的外部类),解决方式有两种:
- 使用静态内部Handler类(+弱引用)。
- 在组件的onDestroy中,调用handler.removecallback()方法。
同样的,由于AsyncTask封装了handler,也会有同样的内存泄露问题,解决方法也是使用静态内部类+弱引用,或在onDestroy时及时调用asyncTask.cancel()方法,否则就会内存泄露,甚至导致崩溃(AsyncTask找不到要处理的View对象)。
引用
从Handler.post(Runnable r)再一次梳理Android的消息机制(以及handler的内存泄露)