Message
//消息的传递包括内容、标识和Handler对象
public class Message {
//消息内容
public Object obj;
//消息标识
public int what;
//handler对象
public Handler target;
}
MessageQueue
//消息队列的功能包含:
//1. 存储消息
//2. 获取消息
//3. 阻塞消息
public class MessageQueue {
//阻塞队列,最多存放50条
BlockingQueue<Message> blockingQueue = new ArrayBlockingQueue<>(50);
//存储一条消息
public void enqueueMessage(Message message) {
try {
blockingQueue.put(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//从消息队列中获取一条消息
public Message next() {
try {
//返回队列当中的第一条消息
return blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
Looper
//存在一个全局唯一的消息队列MessageQueue
//存在一个ThreadLocal,其中只有一个Looper对象,若没有则new一个
public class Looper {
//当前主线程
private static ThreadLocal<Looper> sThredLocal = new ThreadLocal<>();
//消息队列
public final MessageQueue mQueue;
//私有的构造方法
private Looper() {
//初始化线程的looper的时候去new一个消息队列
mQueue = new MessageQueue();
}
//创建一个全局唯一的主线程的Looper
public static void prepare() {
//当前app的ThreadLocal中只有一个Looper存在
if (sThredLocal.get() != null) {
throw new RuntimeException("每个Thread只能创建一个Looper");
}
//set一个looper对象
sThredLocal.set(new Looper());
}
//获取当前looper对象
public static Looper myLooper() {
return sThredLocal.get();
}
//从消息队列中获取目标消息
public static void looper() {
//从全局ThradLocal中获取唯一的Looper对象
Looper looper = myLooper();
//从Looper中获取消息队列
MessageQueue messageQueue = looper.mQueue;
//死循环取消息
for (; ; ) {
//消息队列中一直取消息
Message message = messageQueue.next();
if (message != null) {
//如果当前消息不为null,则发送消息
message.target.dispatchMessage(message);
}
}
}
}
Handler
public class Handler {
//全局唯一的Looper
private Looper looper;
//全局唯一的消息队列
private MessageQueue messageQueue;
//初始化工作,得到looper和其中的线程
public Handler() {
looper = Looper.myLooper();
messageQueue = looper.mQueue;
}
//回调发送过来的消息
public void handlerMessage(Message message) {
//在实现的Handler中重写该方法可以获得发送过来的消息
}
//发送消息
public void sendMessage(Message message) {
//……其他操作
//将消息放入消息队列
enqueueMessage(message);
}
//对message赋值当前handler
//往消息队列中存放一条消息
private void enqueueMessage(Message message) {
//赋值当前消息的handler
message.target = this;
//使用messagequeue将消息传入队列中
messageQueue.enqueueMessage(message);
}
//调用消息
public void dispatchMessage(Message message) {
//……其他操作
//调用空方法handlerMessage,如果外部重写则可以接收到该消息
handlerMessage(message);
}
}
消息机制流程
1. 创建全局唯一的Looper和MessageQueue
-
app启动
-
ActivityThread.main()
-
Looper.prepareMainLooper()
-
创建全局唯一的Looper对象
//调用 Looper.prepare(); //实现方法 public static void prepare(){ if(sThreadLocal.get()!=null){ //全局的ThreadLocal中只能存在一个looper对象 return; } sThreadLocal.set(new Looper()); }
-
创建全局唯一的MessageQueue对象
//全局唯一的消息队列 private MessageQueue mQueue; //在looper的构造函数中创建消息队列 private Looper(){ mQueue = new MessageQueue(); }
2. Activity中创建Handler
-
创建Handler
//activity中 Handler handler = new Handler() { //重写该方法 @Override public void handlerMessage(Message message) { super.handlerMessage(message); System.out.println(message.obj.toString()); } };
3. 消息发送
-
子线程中发送消息
//创建一个子线程 new Thread(new Runnable() { @Override public void run() { LhMessage message=new LhMessage(); message.what=1; message.obj="luhao message test"; //通过handler发送消息 handler.sendMessage(message); } }).start();
-
Handler中的发送方法
private void enqueueMessage(LhMessage message) { //赋值当前消息的handler message.lhHandler = this; //使用messagequeue将消息传入队列中 messageQueue.enqueueMessage(message); }
-
消息队列中将该消息存储起来
//存储一条消息 public void enqueueMessage(LhMessage message) { try { blockingQueue.put(message); } catch (InterruptedException e) { e.printStackTrace(); } }
-
源码中的写法
//activity中发送消息 mHanlder.sendMessage(message); //handler中发送消息 public boolean sendMessage(Message msg,long uptimeMillis){ MessageQueue queue = mQueue; …… return enqueueMessage(queue,,msg,uptimeMillis); } //handler中将消息放入消息队列 private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis){ msg.target = this; …… return queue.enqueueMessage(msg,uptimeMillis); } //messageQueue中将消息存储 boolean enqueueMessage(Message msg,long when){ …… mMessage = msg; …… }
4. 消息处理
-
发送消息
//ActivityThread.main()方法中 ActivityThread.main(){ Looper.loop(); }
-
Looper.loop()开启消息循环
public static void loop(){ //获得全局的Looper对象 final Looper looper = myLooper(); //获得全局消息变量 final MessageQueue queue = looper.mQueue; …… //开启循环 for(;;){ //从消息队列中不断获取消息 Message msg = queue.next(); …… //调用message中存储的Handler对象的dispatchMessage()方法 msg.target.dispatchMessage(message); …… } …… }
-
Handler中发送消息
public void dispatchMessage(Message msg){ if(){ …… }else{ …… } handlerMessage(msg); } public void handlerMessage(Message message){ //这里是null方法,在activity中的handler中重写后就能够得到回调 }
消息阻塞和延时
Looper的阻塞主要是靠 MessageQueue来实现的。
在MessageQuese的next()方法中进行阻塞。
在MessageQueue的enqueueMessage()方法中进行唤醒。
主要依赖 native 层的Looper依靠epoll机制进行的。
简单理解阻塞和唤醒
就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。
这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
阻塞
- nextPollTimeOutMillis的意义
==0;//不阻塞
==-1;//一直阻塞直到被唤醒
0;//具体的延时时间,毫秒
- 源码:MessageQueue.next()
public final class MessageQueue {
//当前最顶层的Message
Message mMessages;
//底层方法,非静态的回调
private native void nativePollOnce(long ptr, int timeoutMillis);
//本机代码使用
@SuppressWarnings("unused")
private long mPtr;
//构造方法中会初始化
MessageQueue(boolean quitAllowed) {
// 保存native层的MessageQueue引用
mPtr = nativeInit();
}
//next方法中做阻塞流程
Message next(){
//消息阻塞时间,默认值0,不阻塞
int nextPollTimeoutMillis=0;
//底层处理参数
final long ptr = mPtr;
//死循环,一直循环消息队列
for(;;){
//底层方法,判断当前消息是否需要阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
//同步锁
synchronized (this) {
//获得当前时间
final long now = SystemClock.uptimeMillis();
//上一个消息为null
Message prevMsg = null;
//当前消息为全局最顶层的消息
Message msg = mMessages;
//如果当前队列里面没有消息,则一直阻塞,
//即, nextPollTimeoutMillis = -1;
if (msg != null) {
//如果消息的入队时间大于当前时间,说明需要延时操作
if (now < msg.when) {
//下一条消息还没有准备好。设置一个超时,当它准备好时唤醒。
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//将当前消息拿出去,并且将下一条消息往上移
if (prevMsg != null) {
//上一条消息存在,将上一条消息的下一条消息替换成当前消息的下一条消息
prevMsg.next = msg.next;
} else {
//上一条消息不存在,全局最顶层消息替换成当前消息的下一条
//这里相当于将消息队列往上顶的操作
mMessages = msg.next;
}
//将当前消息的下一条消息置空,因为这里当前消息的下一条消息已经移动到了队列的最顶,所以当这条消息发送出去的时候,它的下一条消息也应该置空,否则会造成oom
msg.next = null;
//返回当前消息队列最顶层的这条消息
return msg;
}
} else {
//没有更多的消息了,这里一直阻塞着,不让循环继续下去
nextPollTimeoutMillis = -1;
}
}
}
}
}
-
底层方法:nativeInit();
-
android_os_MessageQueue_nativeInit()
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) { // 构建MessageQueue NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); if (!nativeMessageQueue) { jniThrowRuntimeException(env, "Unable to allocate native queue"); return 0; } nativeMessageQueue->incStrong(env); //返回到Java层 return reinterpret_cast<jlong>(nativeMessageQueue); }
-
NativeMessageQueue()
NativeMessageQueue::NativeMessageQueue() : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) { //获取该线程关联的Looper mLooper = Looper::getForThread(); if (mLooper == NULL) { // 创建一个Looper对象 mLooper = new Looper(false); // 将该Looper保存到线程的TLS中 Looper::setForThread(mLooper); } }
-
Looper()
Looper::Looper(bool allowNonCallbacks) : mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false), mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false), mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) { // 构建一个唤醒事件的文件描述符fd mWakeEventFd = eventfd(0, EFD_NONBLOCK); AutoMutex _l(mLock); rebuildEpollLocked();// 重构epoll事件,见2.5 }
-
-
底层方法:nativePollOnce(ptr,nextPollTimeoutMillis)
唤醒
-
MessageQueue.enqueueMessage(Message msg, long when)
boolean enqueueMessage(Message msg, long when) { //消息中的handler不能为null if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } //消息也不能使正在使用中的消息 if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } //同步锁 synchronized (this) { //发送消息到一个死线程上处理程序 if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); msg.recycle(); return false; } //将消息设置成使用中 msg.markInUse(); //设置消息延迟时间 msg.when = when; //赋值全局最顶层的消息 Message p = mMessages; //是否需要唤醒 boolean needWake; //当前消息为null,延迟时间为0,最顶层的消息的还姓时间大于当前消息的延迟时间 if (p == null || when == 0 || when < p.when) { //新的消息,如果阻塞,则唤醒事件队列。 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; prev.next = msg; } // 如果需要唤醒,则调用底层方法唤醒 if (needWake) { nativeWake(mPtr); } } return true; }
-
底层方法:nativeWake(mPtr)
延时入队
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;
延时入队主要指enqueueMessage()消息入列是以上代码对message对象池得重新排序,遵循规则(when从小到大)。
此处for死循环推出情况分两种:
第一种:p==null表示对象池中已经运行到了最后一个,无需再循环。
第二种:碰到下一个消息when小于前一个,立马出循环(不管对象池中所有message是否遍历完),进行从新排序。
常见问题
不能在子线程中更新UI
mThread是UI线程 ,这里检查当前线程是不是UI线程。
//mThread是UI线程 ,这里检查当前线程是不是UI线程。
void checkThread(){
if(mThread != Thread.currentThread()){
throw new CalledFromWrongThreadException("only the original thread that created a view hierarchy can touch its views.");
}
}
在Activity的生命周期中
- onCreate
- UI处于创建过程,对用户来说界面还不可视。
- 不能算是更新UI,只能说是配置UI,或者是设置UI的属性。
- ViewRootImpl没被创建,所以这个时候不会调用ViewRootImpl.checkThread()。
- onStart
- 界面可视了。
- onResume
- 界面可以交互了。
- ViewRootImpl被创建。
- 这个时候去交互界面才算是更新UI。
- setContentView
- 只是建立了View树,并没有进行渲染工作。
- 建立了View树,因此我们可以通过findViewById()来获取到View对象。
- 没有进行渲染视图的工作,也就是没有执行ViewRootImpl.performTransversal()
- 同样View中也不会执行onMeasure()
- 真正的渲染工作是在onResume之后。
- 如果直接在onresume中获取View.getHeight()/View.getWidth()得到的结果总是0。
主线程用Looper死循环不会引发ANR
过程:在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里。
- 此时主线程会释放CPU资源进入休眠状态
- 直到下个消息到达或者有事务发生
- 通过往pipe管道写端写入数据来唤醒主线程工作
- 这里采用的epoll机制,是一种IO多路复用机制
Handler中的Looper不能直接new
在Handler构造方法里面new Looper,无法保证保证Looper的唯一性。
只有用Looper.prepare()才能保证唯一性。
具体在Looper.prepare()方法中。
MessageQueue要放在Looper的私有构造方法中初始化
一个线程只绑定一个Looper。
所以在Looper构造方法里面初始化就可以保证MessageQueue是唯一的。
Thread对应一个Looper对应一个MessageQueue。
Handler.post的逻辑在Looper所在的线程中执行
Looper.loop()方法中
从MessageQueue中拿出Message,并且执行其逻辑
这是在Looper中执行的,所以由looper的线程决定
MessageQueue.next()会因为发现了延迟消息,而进行阻塞。后加入的非延迟消息是通过什么方式及时发送的呢?
通过消息的唤醒机制,延时入队排序,将message.when从小到大排列,再去发送消息