布景
今日突然听到近邻在评论同步屏障,听到这个姓名,我依稀记得 Handler
里边是有同步屏障机制的,可是具体的原理怎样有点模糊不清呢?就像一个明星,你明明看着面熟,便是想不起来他叫啥,让我这样的强迫症患者无比难受,所以抽时间来扒一扒同步屏障。
同步屏障机制
1. 直奔主题,同步屏障机制这几个字听起来很牛逼,能粗浅的解释一下,先让大家理解它的作用是啥不?
同步屏障实际上便是字面意思,能够理解为建立一道屏障,隔离同步音讯,优先处理音讯行列中的异步音讯进行处理,所以才叫同步屏障。
2. 第二个问题,同步音讯又是啥呢?异步音讯和同步音讯有啥不一样呢?
要回答这个问题,咱们就得了解一下 Message
,Message
的音讯种类分为三种:
- 一般音讯(同步音讯)
- 异步音讯
- 同步屏障音讯
咱们平常运用 Handler
发送的音讯根本都是一般音讯,中规中矩的排到音讯行列中,轮到它了再乖乖地出来履行。
考虑一个场景,我现在往 UI 线程发送了一个音讯,想要制作一个要害的 View,可是现在 UI 线程的音讯行列里边音讯现已爆满了,我的这条音讯迟迟都没有办法得到处理,导致这个要害 View 制作不出来,用户运用的时分很恼怒,一气之下给出差评这是什么废物 app,卡的要死。
此时,同步屏障就派上用场了。假如音讯行列里边存在了同步屏障音讯,那么它就会优先寻觅咱们想要先处理的音讯,把它从行列里边取出来,能够理解为加急处理。那同步屏障机制怎样知道咱们想优先处理的是哪条音讯呢?假如一条音讯假如是异步音讯,那同步屏障机制就会优先对它处理。
3.那要如何设置异步音讯呢?怎样的音讯才算一条异步音讯呢?
Message
现已供给了现成的标记位 isAsynchronous
用来标志这条音讯是不是异步音讯。
4.能看看源码了解下官方究竟怎样完成的吗?
看看怎样往音讯行列 MessageQueue
中刺进同步屏障音讯吧。
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
// 当前音讯行列
Message p = mMessages;
if (when != 0) {
// 根据when找到同步屏障音讯刺进的方位
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
// 刺进同步屏障音讯
if (prev != null) {
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
// 前面没有音讯的话,同步屏障音讯变成队首了
mMessages = msg;
}
return token;
}
}
在代码要害方位我都做了注释,简单来说呢,其实就像是遍历一个链表,根据 when
来找到同步屏障音讯应该刺进的方位。
5.同步屏障音讯好像只设置了when,没有target呢?
这个问题发现了华点,了解 Handler
的朋友都知道,刺进音讯到音讯行列的时分,体系会判别当前的音讯有没有 target
,target
的作用便是标记了这个音讯终究要由哪个 Handler
进行处理,没有 target
会抛异常。
boolean enqueueMessage(Message msg, long when) {
// target不能为空
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
...
}
问题 4 的源码剖析中,同步屏障音讯没有设置过 target
,所以它肯定不是经过 enqueueMessage()
增加到音讯行列里边的啦。很明显便是经过 postSyncBarrier()
办法,把一个没有 target
的音讯刺进到音讯行列里边的。
6.上面我都理解了,下面该说说同步屏障究竟是怎样优先处理异步音讯的吧?
OK,刺进了同步屏障音讯之后,音讯行列也还是正常出队的,显然在行列获取下一个音讯的时分,可能对同步屏障音讯有什么特殊的判别逻辑。看看 MessageQueue
的 next
办法:
Message next() {
...
// msg.target == null,很明显是一个同步屏障音讯
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());
}
...
}
办法代码很长,看源码最首要还是看要害逻辑,也没必要一行一行的啃源码。这个办法中相信你一眼就发现了
msg.target == null
,前面刚说过同步屏障音讯的 target
便是空的,很显然这儿便是对同步屏障音讯的特殊处理逻辑。用了一个 do...while
循环,音讯假如不是异步的,就遍历下一个音讯,直到找到异步音讯,也便是 msg.isAsynchronous() == true
7.原来如此,那假如音讯行列中没有异步音讯咋办?
假如行列中没有异步音讯,就会休眠等候被唤醒。所以 postSyncBarrier()
和 removeSyncBarrier()
有必要成对出现,否则会导致音讯行列中的同步音讯不会被履行,出现假死情况。
8.体系的 postSyncBarrier() 貌似也没供给应外部拜访啊?这咱们要怎样运用?
的确咱们没办法直接拜访 postSyncBarrier()
办法创立同步屏障音讯。你可能会想到不让拜访我就反射调用呗,也不是不能够。
但咱们也能够另辟蹊径,尽管没办法创立同步屏障音讯,可是咱们能够创立异步音讯啊!只要体系创立了同步屏障音讯,不就能找到咱们自己创立的异步音讯啦。
体系供给了两个办法创立异步 Handler
:
public static Handler createAsync(@NonNull Looper looper) {
if (looper == null) throw new NullPointerException("looper must not be null");
// 这个true便是代表是异步的
return new Handler(looper, null, true);
}
public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {
if (looper == null) throw new NullPointerException("looper must not be null");
if (callback == null) throw new NullPointerException("callback must not be null");
return new Handler(looper, callback, true);
}
异步 Handler
发送的便是异步音讯。
9.那体系什么时分会去增加同步屏障呢?
有对 View 的作业流程比较了解的朋友想必现已知道了,在 ViewRootImpl
的 requestLayout
办法中,体系就会增加一个同步屏障。
不了解也不要紧,这儿我简单说一下。
(1)创立 DecorView
当咱们启动了 Activity 后,体系终究会履行到 ActivityThread
的 handleLaunchActivity
办法中:
final Activity a = performLaunchActivity(r, customIntent);
这儿咱们只截取了重要的一行代码,在 performLaunchActivity
中履行的便是 Activity 的创立逻辑,因而也会进行 DecorView
的创立,此时的 DecorView
只是进行了初始化,增加了布局文件,对用户来说,依然是不行见的。
(2)加载 DecorView 到 Window
onCreate
完毕后,咱们来看下 onResume
对应的 handleResumeActivity
办法:
@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
...
// 1.performResumeActivity 回调用 Activity 的 onResume
if (!performResumeActivity(r, finalStateRequest, reason)) {
return;
}
...
final Activity a = r.activity;
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
// 2.获取 decorview
View decor = r.window.getDecorView();
// 3.decor 现在还不行见
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 4.decor 增加到 WindowManger中
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
...
}
注释 4 处,DecorView
会经过 WindowManager
履行了 addView()
办法后加载到 Window
中,而该办法实际上是会终究调用到 WindowManagerGlobal
的 addView()
中。
(3)创立 ViewRootImpl 目标,调用 setView() 办法
// WindowManagerGlobal.ddView()
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);
WindowManagerGlobal
的 addView()
会先创立一个 ViewRootImpl
实例,然后将 DecorView
作为参数传给 ViewRootImpl
,经过 setView()
办法进行 View 的处理。setView()
的内部首要便是经过 requestLayout
办法来恳求开端丈量、布局和制作流程。
(4)requestLayout() 和 scheduleTraversals()
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
// 首要办法
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
// 1.将mTraversalScheduled标记为true,表明View的丈量、布局和制作过程现已被恳求。
mTraversalScheduled = true;
// 2.往主线程发送一个同步屏障音讯
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 3.注册回调,当监听到VSYNC信号到达时,履行该异步音讯
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
看到了吧,注释 2 的代码了解的很,体系调用了 postSyncBarrier()
来创立同步屏障了。那注释 3 是啥意思呢?mChoreographer
是一个 Choreographer
目标。
要理解 Choreographer
的话,还要理解 VSYNC
。
咱们的手机屏幕改写频率是 1s 内屏幕改写的次数,比如 60Hz、120Hz 等。60Hz表明屏幕在一秒内改写 60 次,也便是每隔 16.6ms 改写一次。屏幕会在每次改写的时分宣布一个VSYNC
信号,通知CPU进行制作核算,每收到 VSYNC
,CPU 就开端处理各帧数据。这时 Choreographer
就上场啦,当有 VSYNC
信号到来时,会唤醒 Choreographer
,触发指定的作业。它供给了一个回调功能,让业务知道 VSYNC
信号来了,能够进行下一帧的制作了,也便是注释 3 运用的 postCallback
办法。
当监听到 VSYNC
信号后,会回调来履行 mTraversalRunnable
这个 Runnable
目标。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// View的制作进口办法
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
在这个 Runnable
里边,会移除同步屏障。然后调用 performTraversals
这个View 的作业流程的进口办法完成对 View 的制作。
这回理解了吧,体系会在调用 requestLayout()
的时分创立同步屏障,比及下一个 VSYNC
信号到来时才会履行相应的制作任务并移除同步屏障。所以在等候 VSYNC
信号到来的期间,就能够履行咱们自己的异步音讯了。
参阅
requestLayout居然涉及到这么多知识点
关于Handler同步屏障你可能不知道的问题
“总算懂了” 系列:Android屏幕改写机制—VSync、Choreographer 全面理解