重学Handler。
Looper
Looper起到了循环的作用,主要涉及到几个关键方法,prepare()、loop()、构造方法、quit()
prepare()
是一个静态方法,通过prepare方法来构造一个looper对象,并且放到threadLocal中去,如果threadLocal中已经有looper对象了,那么就会抛出异常。所以prepare方法在每个线程中只可以执行一次。
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed)); // 构造looper对象,放到ThreadLocal中
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); //创建MessageQueue对象
mThread = Thread.currentThread(); //记录当前线程.
}
ThreadLocal
Thread Local Storage,简称TLS,线程本地存储区。每个线程都有自己私有的存储区域,各个线程间不可以相互访问。
TheadLocal主要有几个方法:get()、set()、remove()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 根据key获取对应的Value,
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); // map结构,key为TheadLocal
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this); // 从map中移除当前对象
}
为什么要使用ThreadLocalMap,因为一些map比如:hashmap对它的entry都是强引用的,而ThreadLocalMap中对entry是弱引用WeakReference包装了key值,及threadLocal是被弱引用的。
详见 ThreadLocal弱引用与内存泄漏分析。
loop()
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity(); //确保在权限检查时基于本地进程,而不是调用进程。
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // 可能会阻塞
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
msg.target.dispatchMessage(msg);// 分发message
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
msg.recycleUnchecked(); // 回收message,这里会复用msg
}
}
loop方法主要是以下工作
- 从messageQueue中获取下一个message
- 获取message的target,分发给target
- 回收message
Handler
是我们最常接触到的类,主要负责发送message,和处理message
构造
构造时会检查当前线程的looper,或者要求传入looper。然后获取messageQueue
发送消息
// 最终发送消息会走到这里,
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this; // 指定target为自己
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true); // 设置成异步消息
}
return queue.enqueueMessage(msg, uptimeMillis); // 将消息放到MessageQueue中,等待执行
}
sendMessageAtFrontOfQueue()
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
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, 0); // 通过把时间设置成O,来把消息放到队列前端
}
处理消息
/**
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg); // 执行对应的runnable,也是在looper线程执行的,因为此方法由looper调用的
} else {
if (mCallback != null) { // 如果设置了callback,交由callback执行,callback是构造时传入
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg); // 空实现,一般由子类继承重写
}
}
MessageQueue
消息的队列,负责维护消息队列,消息的入队和出队
next()
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis); //阻塞,等待唤醒,nextPollTimeoutMillis为超时时间
synchronized (this) { // 同步代码块,防止多线程入队操作
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; // 将要执行的最近的一个任务
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.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false; // 当前没有阻塞
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg; // return 需要执行的msg对象
}
} else { // 不存在要执行的任务
// No more messages.
nextPollTimeoutMillis = -1; // 无限期的等待下去
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) { //没有要执行的msg,又没有idleHandler需要执行
// No idle handlers to run. Loop and wait some more.
mBlocked = true; // 阻塞吧
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle(); // 执行idleHandler的queueIdle方法。
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler); // 如果idleHandler返回了false,那就移除这个IdleHandler
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0; // 调用idleHandler时,可能已经传递了一条新消息,因此请返回并再次查找未决消息,而无需等待。
}
}
enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
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已经在使用了
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) { // 插入头结点,sendMessageAtFrontOfQueue会插入到这里
// New head, wake up the event queue if blocked.
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.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { //找到msg在链表中的位置
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 // msg的下一个结点
prev.next = msg; // msg的上一个结点
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr); // 是否唤醒next中的阻塞
}
}
return true;
}
postSyncBarrier()
主要是插入一个同步屏障消息,让所有的异步msg先执行。
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis()); // 这里可以看出,是使用了当前时间,所以sendMessageAtFrontOfQueue可以比同步屏障更先执行
}
removeSyncBarrier()
删除同步屏障
Message
链表结构,是存储消息内容的实体
消息池
会将用过的消息进行缓存
同步消息、异步消息、同步屏障消息
一般同步消息和异步消息没区别,但是同步屏障存在时,异步消息会先执行
同步屏障的作用
当设置了同步屏障之后,next函数将会忽略所有的同步消息,返回异步消息。换句话说就是,设置了同步屏障之后,Handler只会处理异步消息。再换句话说,同步屏障为Handler消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息。
通常我们使用Handler发消息时,这些消息都是同步消息,如果我们想发送异步消息,那么在创建Handler时使用以下构造函数中的其中一种(async传true),这样发送的所有消息都为异步消息。(此种方式较为罕见)
public Handler(boolean async);
public Handler(Callback callback, boolean async);
public Handler(Looper looper, Callback callback, boolean async);
同步屏障的应用
Android应用框架中为了更快的响应UI刷新事件在ViewRootImpl.scheduleTraversals中使用了同步屏障
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//设置同步障碍,确保mTraversalRunnable优先被执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//内部通过Handler发送了一个异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
sendMessageAtFrontOfQueue可以比同步屏障更先执行
从MessageQueue的next方法可以看出,是否存在同步屏障是通过判断当前要执行的message是否为同步屏障消息(即message.target为nul)来实现的。而sendMessageAtFrontOfQueue可以直接把message插入到MessageQueue的头部,所以直接就将当前要执行的Message替换成了自己。这样执行next的时候肯定会将当前的Front Message返回,这样就打破了同步屏障。
HandlerThread test = new HandlerThread("test");
test.start();
Handler handler = new Handler(test.getLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
Logger.i("handleMessage() what" + msg.what);
return true;
}
});
int token= handler.getLooper().getQueue().postSyncBarrier(); // 设置同步屏障
handler.sendEmptyMessageDelayed(1, 0); //发送同步Message
handler.sendEmptyMessageDelayed(2, 0); //发送同步Message
Message msg = handler.obtainMessage(4);
msg.setAsynchronous(true);
handler.sendMessageDelayed(msg, 0); //发送异步Message
handler.sendMessageAtFrontOfQueue(handler.obtainMessage(3)); //发送同步Front Message
// handler.getLooper().getQueue().removeSyncBarrier(token);
以上代码输出结果为:
handleMessage() what3
handleMessage() what4
- 因为同步屏障,同步消息1和2没有被执行。
- 因为sendMessageAtFrontOfQueue执行时间小于同步屏障消息,所有会被执行
IdleHandler
Handler空闲时回调,可以延时执行一个任务,比如获取view高度,显示dialog等
View.postDelayed()原理
public boolean postDelayed(Runnable action, long delayMillis) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) { // 当view已经attached,调用attachInfo的handler进行执行
return attachInfo.mHandler.postDelayed(action, delayMillis);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().postDelayed(action, delayMillis); // 将runnable缓存起来,等待attach
return true;
}
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue(); //此对象中存在一个数组,封装了runnable和delay时间
}
return mRunQueue;
}
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler); // 执行所有挂起的runnable
mRunQueue = null;
}
}
那么dispatchAttachedToWindow()方法是在什么时候执行的呢?我们可以通过重写onAttachedToWindow()方法,在onAttachedToWindow中打印出来调用栈(通过Exception打印)
2021-05-26 14:41:00.478 16540-16540/com.ericcode.diary W/System.err: java.lang.Exception
2021-05-26 14:41:00.478 16540-16540/com.ericcode.diary W/System.err: at com.ericcode.diary.ui.view.MyTextView.onAttachedToWindow(MyTextView.kt:14)
2021-05-26 14:41:00.478 16540-16540/com.ericcode.diary W/System.err: at android.view.View.dispatchAttachedToWindow(View.java:20658)
2021-05-26 14:41:00.479 16540-16540/com.ericcode.diary W/System.err: at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3529)
2021-05-26 14:41:00.479 16540-16540/com.ericcode.diary W/System.err: at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3529)
2021-05-26 14:41:00.479 16540-16540/com.ericcode.diary W/System.err: at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2527)
2021-05-26 14:41:00.479 16540-16540/com.ericcode.diary W/System.err: at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2060)
2021-05-26 14:41:00.479 16540-16540/com.ericcode.diary W/System.err: at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8482)
2021-05-26 14:41:00.479 16540-16540/com.ericcode.diary W/System.err: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1077)
2021-05-26 14:41:00.480 16540-16540/com.ericcode.diary W/System.err: at android.view.Choreographer.doCallbacks(Choreographer.java:897)
2021-05-26 14:41:00.480 16540-16540/com.ericcode.diary W/System.err: at android.view.Choreographer.doFrame(Choreographer.java:826)
2021-05-26 14:41:00.480 16540-16540/com.ericcode.diary W/System.err: at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1062)
2021-05-26 14:41:00.480 16540-16540/com.ericcode.diary W/System.err: at android.os.Handler.handleCallback(Handler.java:938)
2021-05-26 14:41:00.480 16540-16540/com.ericcode.diary W/System.err: at android.os.Handler.dispatchMessage(Handler.java:99)
2021-05-26 14:41:00.480 16540-16540/com.ericcode.diary W/System.err: at android.os.Looper.loop(Looper.java:233)
2021-05-26 14:41:00.480 16540-16540/com.ericcode.diary W/System.err: at android.app.ActivityThread.main(ActivityThread.java:8053)
2021-05-26 14:41:00.480 16540-16540/com.ericcode.diary W/System.err: at java.lang.reflect.Method.invoke(Native Method)
2021-05-26 14:41:00.480 16540-16540/com.ericcode.diary W/System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
2021-05-26 14:41:00.480 16540-16540/com.ericcode.diary W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)
通过调用栈可以看出:dispatchAttachedToWindow()是由ViewRootImpl.doTraversal()调起。而在ViewRootImpl.doTraversal中,设置同步屏障,发送异步消息,来保证渲染逻辑优先执行。所以在View.postDelayed()中传入的runnable,是在view完成绘制后才执行的。
所以这几个方法执行的顺序为:dispatchAttachedToWindow()->onMeasure()->onLayout()->onDraw()->View.post()中的runnable
同步屏障真正的位置在doTraversal()->performTraversals()->scheduleTraversals()
AIASST-1831:
问题原因:由子线程与主线程同时刷新ui导致barrier没有被移除使handler接受不到回调.
barrier存在会阻塞同步消息,在scheduleTraversals时放置barrier,并在执行doTraversal或unscheduleTraversals时去掉,目的是为了保证ui刷新实时性.
1.复现问题时,barrier=9745存在
ACTIVITY com.xiaomi.aiasst.service/.aicall.activities.SettingsActivity 89bbc25 pid=19343
Looper (main, tid 2)
{a033026}
Message 0: { when=-9m42s837ms barrier=9745 }
Message 1:
{ when=-9m42s831ms callback=rx.android.schedulers.LooperScheduler$ScheduledAction obj=rx.android.schedulers.LooperScheduler$HandlerWorker@f6b4467 target=android.os.Handler }
Message 2:
{ when=-9m42s133ms what=13 target=com.xiaomi.aiasst.service.aicall.impl.presenter.SettingInCallPresenter$6 }
Message 3:
{ when=-9m41s131ms what=12 target=com.xiaomi.aiasst.service.aicall.impl.presenter.SettingInCallPresenter$6 }
2.原因是在相近时间两个线程请求scheduleTraversals,放置了barrier,在doTraversal只会移除最新的barrier9746:
07-31 10:07:29.801 1000 19343 19343 D ViewRootImpl[SettingsActivity]: postSyncBarrier mTraversalBarrier = 9745
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: postSyncBarrier mTraversalBarrier = 9746
07-31 10:07:29.812 1000 19343 19343 D ViewRootImpl[SettingsActivity]: doTraversal removeSyncBarrier mTraversalBarrier = 9746.
3.线程19801中进行invalidate操作:
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: java.lang.RuntimeException: ViewRootImpl[SettingsActivity]
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.ViewRootImpl.scheduleTraversals(ViewRootImpl.java:1814)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.ViewRootImpl.invalidate(ViewRootImpl.java:1542)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.ViewRootImpl.onDescendantInvalidated(ViewRootImpl.java:1535)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6004)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6004)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6004)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6004)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6004)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6004)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6004)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.ViewGroup.invalidateChild(ViewGroup.java:6022)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.View.invalidateInternal(View.java:18225)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.View.invalidate(View.java:18185)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at android.view.View.invalidate(View.java:18167)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at com.xiaomi.aiasst.service.aicall.view.RecordingWaveView.update(RecordingWaveView.java:243)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at com.xiaomi.aiasst.service.aicall.view.RecordingWaveView.addVolumes(RecordingWaveView.java:227)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at com.xiaomi.aiasst.service.aicall.view.AudioRecordGroup.updateWave(AudioRecordGroup.java:30)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at com.xiaomi.aiasst.service.aicall.impl.presenter.SettingInCallPresenter.updateMaxAmplitude(SettingInCallPresenter.java:384)
07-31 10:07:29.801 1000 19343 19801 D ViewRootImpl[SettingsActivity]: at com.xiaomi.aiasst.service.aicall.impl.presenter.SettingInCallPresenter$PrologueAudioRecordThread.run(SettingInCallPresenter.java:838)
4.子线程中更新ui没有crash是因为基线临时移除了线程检查限制
http://10.221.160.141:8888/source/xref/miui-r-umi-dev/frameworks/base/core/java/android/view/ViewRootImpl.java#1689
参考
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!