背景
Handler 是贯穿于整个应用的消息机制。在咱们印象中,Handler 就是处理子线程和主线程桥梁的作用,其实Android中很多都是用着Handler机制,比如屏幕的刷新,打开Activity,按返回键,等等,都是通过Handler来发消息和处理消息的。现在咱们带着几个问题来说一下Handler。MessageQueue,Message 和 Looper 的关系。
问题
- 为什么Android要设计一个只有主线程来更新UI的机制呢?
- 一个线程中可以有几个Handler 几个Looper ,几个 MessageQueue?
- 一个线程只能有一个Looper,怎样保证的呢?怎样和 MessageQueue 绑定在一起
- Looper是怎样从消息队列里面取消息的
- Handler 内存泄漏的原因以及处理
- 子线程创建Handler的需要处理什么?为什么主线程不需要处理?
- Handler是如何先主线程发送消息?
- 如果创建 Message?
- View.post() 、 Handler.sendMessage() 、Handler.postDelayed()、Handler.sendMessage()和sendMessageDelayed的区别
- 当 sendMessageDelay() 的时候,是存的时候限制,还是从MessageQueue里取的时候限制?
- 子线程真的不能更新UI吗?
- Handler的同步屏障机
- Looper 死循环为什么不会让主线程卡死?
Handler消息机制源码追踪
Handler机制就是个典型的生产者消费者模式,只不过是 MessageQueue 的大小是无限制的。这里面有4个兄弟在处理,分别是 Handler ,Looper,MessageQueue 和 Message
- Handler的作用主要是向 MessageQueue 队列里面发消息 或者 删除消息 ,还有就是用来处理消息的。
- Looper 相当于一个永动机,无限循环的从 MessageQueue 的头部去取消息,来给 Handler 去处理
- MessageQueue 就是一个用来存储 Message的一个容器,是一个单链表的容器
- Message 就是消息,用来被处理,和被发送
Handler的创建以及发送,下面都是Android-29的源码
创建
// 这是咱们最常见的创建方式,即我们在什么线程创建 就是什么线程的Handler,与什么线程的Looper和MessageQueue去绑定
public Handler() {
this(null, false);
}
// 这是直接跟传入进来的Looper去绑定
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
// 拿出当前的线程的Looper,如果当前的Looper 为null ,也就是还没有创建Looper的话,直接 抛异常
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
// 取出Looper中的 MessageQueue
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
发送
// 发送一个消息
public final boolean sendEmptyMessage(int what){
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
// 创建 Message ,用 Message的静态方法区创建。
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
// 这个方法 最终也是走到 sendMessageDelayed 到 sendMessageAtTime
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
// 最终调用到这个地方,SystemClock.uptimeMillis()是指开机的时间
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 无论从那种方式调用,都会走到这里
public boolean sendMessageAtTime(@NonNull 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);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// 赋值this就是Handler ,给 Message.target
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 这里调用了 MessageQueue 的插入方法
return queue.enqueueMessage(msg, uptimeMillis);
}
处理
// 当 从 MessageQueue 里面取出来消息的时候,会调用 这个方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
// 处理方法,这里是空的,主要是让咱们自己实现,去处理
public void handleMessage(Message msg) {
}
这里的总结:无论是 调用 Handler 里面的任何发送Message都会走到 sendMessageAtTime 方法,最终走到 enqueueMessage,调用到 MessageQueue 的 enqueueMessage方法。View.post()方法其实也是调用Handler.post 方法。其实 Handler还有个 sendMessageAtFrontOfQueue 这个方法是放到 MessageQueue 的前面,优先执行,但是我们要慎用这个方法,有可能导致 MessageQueue 饿死。
然后看Looper的源码
// 构造方法是私有的,传入的参数 代表 是否可以允许退出,绑定 此Looper中的 MessageQueue, 所以说 MessageQueue 和 Looper 是 以及线程。所以都是 一一对应的,一个线程里面只有一个 MessageQueue 一个 Looper
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
public static void prepare() {
prepare(true);
}
// sThreadLocal 使用了 final 修饰的,证明 sThreadLocal 是在同一个Looper中全局唯一,不能被修改的
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// 准备 Looper
private static void prepare(boolean quitAllowed) {
// 从 sThreadLocal 里面取,如果是不为null ,也就是已经准备好了Looper,再调用此方法直接抛异常,所以 一个线程只有一个Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 如果没有直接把 Looper 设置到 sThreadLocal里面,以后用的时候是直接从 sThreadLocal 这个里面拿的Looper
sThreadLocal.set(new Looper(quitAllowed));
}
// 开始轮训
public static void loop() {
final Looper me = myLooper();
// 如果Looper 为null 直接抛异常
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// 死循环 去轮训 从 MessageQueue里面取 Message
for (;;) {
// 死循环
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
/**
* 省略代码
*/
try {
// 刚才咱们在上面也已经说了,这里的 target就是 Handler,所以到取到 Message 之后,调用handler的方法
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
}
/**
* 省略代码
*/
// 回收Message
msg.recycleUnchecked();
}
}
总结: 这里调用Looper的构造方法时,会创建一个MessageQueue,还有把 Looper 保存到 ThreadLoacl ,让一个线程 只有一个Looper,一个 MessageQueue,当取到 Looper 从 MessageQueue 里面取到 Message 之后,会调用 Message.target.dispatchMessage(msg),也就是 Handler的 dispatchMessage(),最终调用到 Handler的 handleMessage方法。
然后看 MessageQueue 的源码
// 构造方法
MessageQueue(boolean quitAllowed) {
// 是否可以退出
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
// 向 MessageQueue 里面添加 Message, 第二个参数代表 开机时间+delay的时间
boolean enqueueMessage(Message msg, long when) {
// 这里拿到target.也就是 handler
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");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
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.
// 也就是四插入到MessageQueue的头部,新的头部,如果 此 MessageQueue 正在被阻塞,就唤醒这个 MessageQueue
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
// 插入到 MessageQueue 中间 或者 是最后,通常这里我们不必去唤醒此MessageQueue的,因为MessageQueue没有阻塞,除非头部有一个同步屏障,并且是异步消息,才会唤醒
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 又是for循环去添加 Message 到那个节点,从这里看 就像个单链表结构
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);
}
}
return true;
}
Message next() {
/**
* 省略代码
*/
// for 循环去取
for (;;) {
/**
* 省略代码
*/
// 又是同步去取 Message
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 咱们可以发现 这里竟然有个 msg.target ==null 的情况
// 其实这就是系统调用的,同步屏障。如果有同步屏障,就会轮训查找 msg.target==null 的Message
// 在 MessageQueue 中查找下一个异步消息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
// 当这个 Message 时间还没到的时候,设置超时时间以准备就绪时唤醒
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
/**
* 省略代码
*/
}
}
// 发送一个同步屏障的消息,即 target==null的消息,优先去执行
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
//这个token 就是用来删除的 同步屏障 用的
final int token = mNextBarrierToken++;
// 这里创建了一个 Message 但是没有给 target赋值,所以这里发起的同步屏障
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
/**
* 省略代码
*/
return token;
}
}
// 移除同步屏障
public void removeSyncBarrier(int token) {
/**
* 省略代码
*/
}
总结:当MessageQueue 存取消息都是有同步代码块的,防止多线程操作时 出现问题。当有同步屏障时优先执行的,当handler==null的时候会有同步屏障,比如屏幕刷新,View的绘制等等。比如当发送一个ViewRootImpl里面的scheduleTraversals方法就会发出同步屏障。postDelay的Message的时候,是在取的时候 去 处理这个时间。
然后看 Message 的源码
// 用静态方法区创建,因为 android 里面的消息太多了,不只是咱们自己创建的,还有很多系统内部去创建的。所以这里弄了一个缓存。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
// 回收这个 Message。把所有的值 制成默认值
void recycleUnchecked() {
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
总结: Message 的创建 google 建议我们用 obtain 方法去创建,这样可以解决内存
回答一下上面的问题
-
为什么Android要设计一个只有主线程来更新UI的机制呢?
- Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行.假设如果在一个Activity当中,有多个线程去更新UI,页面就会错乱,如果更新UI时都加锁,就会很耗性能。 所以,android给我们提供了一套去主线程更新UI。相当于伪锁吧
- 并不是主线程,是创建的线程View只能由创建的线程更新,自线程也可以弹dialog,只能自线程去更新
- 当RootViewImpl没有创建之前,都是可以更新的,只有在RootViewImpl后才会有这个校验
- 比如当一个TextView 固定宽高,在自线程中setText(),是不会触发的,因为宽高没有变,不会涉及到requestLayout。
-
一个线程中可以有几个Handler 几个Looper ,几个 MessageQueue?
一个线程中可以有很多个Handler ,但是只能有一个Looper和一个MessageQueue, -
一个线程只能有一个Looper,怎样保证的呢?怎样和 MessageQueue 绑定在一起
android 是通过 ThreadLoacl,来保证Looper的唯一性,在Looper的构造方法中,初始化了 MessageQueue,并且绑定在一起 -
Looper是怎样从消息队列里面取消息的
Looper 会死循环的从 MessageQueue里面取消息,如果没有消息就会挂起。
- Handler 内存泄漏的原因以及处理
通过源码可以看出 Message.target 就是 Handler。如果messsage没有处理完,所以 Handler 也不会释放,而在Activity中 非静态内部类 会 引用到外部类,所以此时 Activity 也不会被释放,可以用 弱引用包裹起来,也可以用静态handler,也可以在 Activity的OnDestroy方法中移除 MessageQueue里面的Message
- 子线程创建Handler的需要处理什么?为什么主线程不需要处理?
当我们在子线程中 new Handler的时候,会抛出 以下异常,所以我们需要先调用 Looper.prepare()和Looper.loop()方法
Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()"
7. 那为什么主线程并不用去初始化Looper呢?
其实在我们android的入口类中 即 ActivityThread 类中的 main方法就是,android的入口。已经为咱们创建了主线程的Looper
public static void main(String[] args) {
Looper.prepareMainLooper();
...
Looper.loop();
}
8. Handler是如何在子线程中向主线程发送消息?
说简单点,就是当我们在主线程创建Handler的时候,这个Handler已经和主线程的Looper和主线程的MessageQueue已经绑定了。 用主线程的Handler在子线程中使用,往主线程的 MessageQueue 里面发送 Message 都是没问题的。
- 如果创建 Message?
使用 Message.obtain() ,可以减少内存开销
-
View.post() 、 Handler.sendMessage() 、Handler.postDelayed()、Handler.sendMessage()和sendMessageDelayed的区别
我感觉是没啥区别的,最终都会走到 sendMessageAtTime中,要是有区别的话,是在 View.post()里面和 Handler.post里面都有个Runable,只是处理的时候在这个Runable里面 -
当 sendMessageDelay() 的时候,是存的时候限制,还是从MessageQueue里取的时候限制? 是取的时候来处理,当不到时间的时候,记录这个时间以便唤醒
-
Handler的同步屏障机制
当View的刷新,界面的刷新等等 会发一个同步消息,来保证这个消息先去执行
由于下面两个问题延伸比较长,所以起了个目录
子线程真的不能更新UI吗?
其实子线程也可以去刷新UI的,比如你在 Activity的onCreate()方法中,找到TextView,直接在子线程中赋值,是没问题的。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_name = findViewById(R.id.tv_name);
new Thread(){
@Override
public void run() {
super.run();
tv_name.setText("子线程更新了");
}
}.start();
当在线程中sleep(1000)然后再更新就会报错
会报下面的错误
上面说只有原始线程才能更新此UI,也就是创建的UI线程才能更新UI。所以说 主线程才能更新主线程的UI。子线程可以更新子线程的UI,比如你在子线程
那为什么直接在Activity的onCreate中 子线程更新没问题呢?
其实检测是否是当前线程是在 ViewRootImpl中
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 这个就是检测当前是否是当前创建Ui的线程
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
// 这里就是使用Handler 发送同步消息,来更新UI。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
然后ViewRootImp是在 Activity.onCreate() 中还没有创建,可以追溯 ViewRootImp的源码查看什么时候创建的。我们看 ViewRootImp的构造方法,只有一个构造方法,
public ViewRootImpl(Context context, Display display) {}
然后我们全局搜索关键字 new ViewRootImpl 这个关键字,也就是创建时。我们可以看到只有两个类去实例化了这个类。当我们看到 WindowMangerGlobal,就知道和 WindowMange 和 Window有关系,所以我们直接看这个里面的代码。咱们的View都是添加到 Window上的。咱们每个Activity 都至少有一个Window,然后window 是 由WindowManger添加的,咱们都知道当Activity在onResume,中才会显示第一帧,此时在这里创建了ViewRootImpl。具体可查看具体分析
如果View的大小是固定的,比如Textview,在setText的时候,没有必要触发requestLayout,也就不会检测侧线程
Looper 死循环为什么不会让主线程卡死?
我感觉这是是两个不同的问题, 在android 系统中本身就是一个死循环的过程,当这个死循环停止,app也就退出了。比如 MessageQueue 中不只是处理咱们的消息,还会处理系统的消息。轮训涉及到Linux pipe/epoll机制。当 MessageQueue 里面没有消息的时候,会在管道头那里阻塞,当有Message的时候,会唤醒 ,然后继续轮训。 其实当发生ANR的时候,此时系统也会发送一个Message,然后主线程处理来弹出来 ANR的弹窗