Android 消息机制详解
什么是Handler
Handler的主要作用是在线程之间传递消息。
每个Handler会和每个线程以及线程对应的消息队列相绑定。之后消息就可通过Handler在线程之间传递。
使用流程
使用方式一
// 步骤1:在主线程中 创建Handler类对象
private Handler mhandler = new Handler(){
// 通过复写handlerMessage()从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需执行的UI操作
}
};
// 步骤2:创建消息对象
Message msg = Message.obtain(); // 实例化消息对象
msg.what = 1; // 消息标识
msg.obj = "AA"; // 消息内容存放
// 步骤3:在工作线程中 通过Handler发送消息到消息队列中
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
mHandler.sendMessage(msg);
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
使用方式二
// 步骤1:在主线程中创建Handler实例
private Handler mhandler = new mHandler();
// 步骤2:在工作线程中 发送消息到消息队列中 & 指定操作UI内容
// 需传入1个Runnable对象
mHandler.post(new Runnable() {
@Override
public void run() {
... // 需执行的UI操作
}
});
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
框架简单分析
Android的消息机制主要是指Handler的运行机制
handler工作时核心组件间的关系如下图:
以上模型的解释:
-
以Handler的sendMessage方法为例,当发送一个消息后,会将此消息加入消息队列MessageQueue中。
-
Looper负责去遍历消息队列并且将队列中的消息分发给对应的Handler进行处理。
-
在Handler的handleMessage方法中处理该消息,这就完成了一个消息的发送和处理过程。
这里从图中可以看到参与消息处理有四个主要对象,它们分别是 Handler, Message, MessageQueue,Looper。
消息处理流程的源码分析
创建Looper对象和MessageQueue对象
我们都知道,ActivityThread就是Android的主线程或UI线程,ActivityThread的main方法是整个APP的入口
当App启动时 创建全局唯一Looper对象和全局唯一MessageQueue消息对象
ActivityThread.java
public static void main(String[] args) {
...
Looper.prepareMainLooper(); // 进行准备工作
...
Looper.loop(); // 消息处理
}
Looper.java
public static void prepareMainLooper() {
prepare(false);
...
}
private static void prepare(boolean quitAllowed) {
// 创建全局唯一的Looper对象
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
ThreadLocal.java
public void set(T value) { // 将线程与looper绑定
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
创建Handler对象
Activity中创建Handler
Handler.java
// 重写handleMessage()调用此方法
public Handler(Callback callback) {
this(callback, false); // 即调用 Handler(Callback callback, boolean async)
}
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper(); // myLooper()中只有一行代码: sThreadLocal.get();
mQueue = mLooper.mQueue;
...
}
Looper.mylooper() 实质就是取当前线程的looper,这个Looper是在prepare()的时候创建并通过threadLocal存储起来的
Looper.java中有如下代码:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
另外, 我们在开发中还会用到Looper.getMainLooper(), 它是用来取主线程的looper
/**
* Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
发送消息
// 我们调用 sendMessage(msg) 时候, 会调用sendMessage(msg, 0)
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
...
return enqueueMessage(queue, msg, uptimeMillis);
}
// 把消息放入队列, 并进行排序
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
...
return queue.enqueueMessage(msg, uptimeMillis); // 消息排序
}
处理消息
public static void loop() {
final Looper me = myLooper(); // sThreadLocal.get(); 获取Looper对象
final MessageQueue queue = me.mQueue; // 获取消息队列
...
for (;;) {
Messagemsg = queue.next(); // might block
...
msg.target.dispatchMessage(msg);
...
}
}
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
消息阻塞与唤醒的源码分析
消息阻塞和延时
Looper 的阻塞主要是靠 MessageQueue 来实现的,在MessageQuese的next() 进行阻塞,在 MessageQueue的enqueueMessage() 进行唤醒。主要依赖 native 层的 Looper 依靠 epoll 机制进行的。
MessageQueue.java
Message next() {
...
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 阻塞和延时在这里进行
nativePollOnce(ptr, nextPollTimeoutMillis);
...
}
}
从源码可知, 阻塞和延时,主要是 nativePollOnce(ptr, nextPollTimeoutMillis) 调用naive方法操作管道,
具体由 nextPollTimeoutMillis 的值决定是否需要阻塞, 为0的时候表示不阻塞,为-1的时候表示一直阻塞直到被唤醒,其他时间表示延时。
消息的排序和唤醒
在MessageQueue的enqueueMessage()进行排序和唤醒
MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
...
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// ----- 此处进行消息排序 -----
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// ----- 此处进行消息唤醒 -----
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
对message对象池的重新排序,遵循规则(when从小到大)。
此处for死循环退出情况分两种
- p==null表示对象池中已经运行到了最后一个,无需再循环。
- 碰到下一个消息when小于前一个,立马推出循环(不管对象池中所有message是否遍历完),进行从新排序。
简单理解阻塞和唤醒
就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。
这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。
所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
从阻塞到唤醒消息的切换
如下图:
Native消息机制
参考文档
常见问题分析
mThread是UI线程,这里会检查当前线程是不是UI线程。那么为什么onCreate里面没有进行这个检查呢。
这个问题原因出现在Activity的生命周期中,在onCreate方法中,UI处于创建过程,对用户来说界面还不可视,直到onStart方法后界面可视了,再到onResume方法后界面可以交互。
从某种程度来讲,在onCreate方法中不能算是更新UI,只能说是配置UI,或者是设置UI的属性。这个时候不会调用到ViewRootImpl.checkThread(),因为ViewRootImpl没被创建。而在onResume方法后,ViewRootImpl才被创建。这个时候去交互界面才算是更新UI。
setContentView只是建立了View树,并没有进行渲染工作(其实真正的渲染工作是在onResume之后)。
也正是建立了View树,因此我们可以通过findViewById() 来获取到View对象,但是由于并没有进行渲染视图的工作,也就是没有执行ViewRootImpl.performTransversal。同样View中也不会执行onMeasure() ,如果在onResume() 方法里直接获取View.getHeight() /View.getWidth() 得到的结果总是0。
简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,
此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制。
如果在Handler构造方法里面new Looper,怕是无法保证保证Looper唯一,只有用Looper.prepare()才能保证唯一性,具体见prepare() 方法。
因为一个线程只绑定一个Looper,所以在Looper构造方法里面初始化就可以保证mQueue也是唯一的
即Thread对应一个Looper 对应一个 mQueue。
由Looper所在线程决定的。
逻辑是在Looper.loop()方法中,从MsgQueue中拿出msg,并且执行其逻辑,这是在Looper中执行的,因此有Looper所在线程决定。
见:排序和唤醒
-
Handler的dispatchMessage()分发消息的处理流程?
msg.callback 在mHandler1.post() 中使用
mCallback在new Handler是通过接口回调
post()和sendMessage()都是发送消息,加入消息队列得方式也是一样,区别在于处理消息得方式。通过跟踪源码,容易区分。 -
Handler为什么会导致内存泄漏, 如何处理
非静态内部类持有外部类的引用(通常为Activity)就可能会引起内存泄漏。
- 把内部类声明成static:
- 静态内部类不持有外部类的引用
- 静态内部类不依赖外部类。静态内部类是可以独立存在的,即使外部类消亡了,内部类还是可以存在的。 编译之后的文件名为: 外部类$内部类
- 在外部类销毁的时候调用
moveCallbackAndMessage
来移除回调和消息 - 可以使用弱引用来解决
// 使用弱引用来引用外部类的实例
private static class MyHandler extends Handler{
//持有弱引用HandlerActivity,GC回收时会被回收掉.
private final WeakReference<HandlerActivity> mActivty;
public MyHandler(HandlerActivity activity){
mActivty =new WeakReference<HandlerActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
HandlerActivity activity=mActivty.get();
super.handleMessage(msg);
if(activity!=null){
//执行业务逻辑
}
}
}