目录
1.一些渲染相关的问题
2.Android渲染流程
3. 结合源码分析流程-ViewRootImpl、Choreograher、 Surface、RenderThread、GraficBuffer、SurfaceFlinger
4.Perfetto上帝视角串联流程
5.问题解答
6.资料
回顾自己的工作经历, 从嵌入式开发,到Android开发,再到音视频开发、AI视觉算法等,随着行业的发展也不断的迭代自己,每个领域都很有收获,都值得持续深入研究,感恩每一份经历。最近做了一个决定重新回到Android开发,接下来学习和输出会重点在Android领域,对自己未来几年的规划是,分阶段在性能优化、Android运行机制、JVM内存管理、Linux通信机制、AI端侧应用及代码生成等课题方面学习和输出,在技术上重点提升深度,同时在业务上进行管理及产品运营方面学习和输出,拓展广度边界。以终为始,保持学习的心态,做一个终身学习者,不断更新迭代。
本篇开始我们进行性能优化方面的系列学习和输出,工欲善其事,必先利其器,google开源了一个支持多平台的性能分析工具perfetto,让我们可以用上帝视角帮助我们对性能优化和系统运行机制做深入的探索。关于perfetto的使用以及Systrace系列文章,这里安利下高爷的Android Performance系列文章https://www.androidperformance.com/,非常值得结合实践多次阅读。
一、一些渲染相关的问题
带着问题和思考进行学习和探索,是一种高效的学习方式。关于Android渲染方面有下面几个问题,可以看看自己是否有答案
-
invalidate和requestlayout对后续绘制渲染有什么不同?
-
为什么View刚被加载时想获取其的宽高,需要view.post(Runnalbe)中获取?
-
黄油计划提出的Vsync和三缓冲机制为什可以大大改善Android的交互和流畅度体验?
-
Vsync信号是谁发出的?需要App主动请求吗?界面没有变化是否会有Vsync信号?App是否响应Vsync信号取决于什么?
-
调用android绘制三部曲(measure、layout和draw)的performTraversals是怎么触发的?
-
Android的同步屏障和异步消息是什么,它们如何在渲染机制中发挥作用的?
-
SurfaceView/TextureView渲染视频时频繁的刷新界面,为什么UI界面不卡顿?
-
android 5.0引入的硬件加速RenderThread是如何提升渲染效率的?指令归类重排序是什么?
-
动画可以在RenderThread上执行吗
-
使用OpengGLES渲染时进行eglSwapBuffer切换前后台Buffer依赖Vsync信号吗?
-
App进程将views树构建为DisplayList给到GPU进行渲染到Surface上作为BufferQueue的生产者,SurfaceFlinger是BufferQueue的消费者获取Surface转为layer进行不同layer的合成,那么App进程和SurfaceFlinger进程是如何通信的?
-
VSYNC信号是通过什么方式传递?
-
整体上介绍下Android绘制渲染流程,App开发者熟悉渲染流程后对于日常开发有什么帮助?
二、Android渲染流程
View的属性变化后(eg:setbackgroundcolor)内部会调用invalidate(),Invalidate本身不会做任何绘制,而是告诉视图结构(View Hierarchy)需要被绘制了,它会从下到上传递失效状态,最终传递到ViewRootImpl#invalidataChild,它调用了ViewRootImpl#scheduleTraversals
这里有个注意点,invalidate必须是UI线程调用的,要在非UI线程调用要使用PostInvalidate
View的requestlayout,会进行measure,layout和draw,其内部最终也是调用ViewRootImpl#scheduleTraversals
View的invalidate或requestlayout触发Vsync信号监听,当Choreographer收到Vsync信号后,Traversals会 执行一帧的input响应,animations响应以及measure、layout和draw ,
开启硬件加速的情况下会UIThread不会真正的进行绘制,而是把绘制相关的命令封装为渲染命令结构体Displaylist,这些绘制命令sync到RenderThread线程,最终转为OpenGLES/Vulkan/skia-gpu的命令,进行GPU渲染。
下面我们再详细看下UIThread把Displaylist同步给RenderThread后的流程
每个View持有一个RenderNode对象(对应DisplayList),当View发生变化(invalidate或者requestlayout)时,会更新或者重新生成包含需要执行绘制操作(如:限制绘制区域的ClipRect;填充绘制一个区域的Fill等)的Displaylist。
Sync是把记录UI绘制操作的DisplayList信息从UIThread同步给与GPU打交道的RenderThread,同时也会同步 Damage Area(损坏区域)和 上传非硬件位图(android8以后不用上传 Bitmap只在native层分配内存)。
为了优化GPU性能,会对DLOPs(DisplayList Opratations)进行归类重排序,把同一类放在一起结合损坏区域Damage Area,降低切换成本及重复执行。
然后通过GetBuffer从SurfaceFlinger获取Buffer,RenderThread向GPU发送一系列gl命令(如果使用的是opengles,Vulkan同理),这些命令通常来说就是一些点、线、三角形、文本以及bitmapcopy(android O之后Bitmap直接在GPU分配内存,可以省去bitmapcopy操作)等操作。
在渲染完后调用eglSwapBuffer,将渲染到离屏缓冲区(offscreen buffer)中的内容交换到前台缓冲区(这个环节依赖Vsync信号)
OpenGL渲染流程一般不需要我们自己做,RenderThread.cpp内部会自己处理。但是对于音视频渲染以及特效场景需要自己通过SurfaceView或TextureView提供的surface创建EGL环境,自己创建或参考GLSurfaceView进行GL渲染线程创建和渲染。有需要可以参考之前的文章 : GLSurfaceView源码解析&EGL环境(https://blog.youkuaiyun.com/u011570979/article/details/109567243)
RenderThread完成渲染后,会将渲染到Surface的buffer,queue到BufferQueue,然后S ufaceFlinger作为消费者从BufferQueue中dequeueBuffer作为layer。
BufferQueue是连接App进程Surface和SurfaceFlinger进程Layer的纽带,Native层Surface实现了ANativeWindow结构体,ANativeWindow持有一个IGraphicBufferProducer,用于和BufferQueue进行交互.
当APP进程图形数据渲染到Surface时,实际上渲染到了BufferQueue中一个GraphicBuffer,然后通过IGraphicBufferProducer把GraphicBuffer提交到BufferQueue.
SurfaceFlinger负责合成所有的Layer并送给Display显示,其中Layer合成支持两种方式:OpenGLES或者HWComposer,
OpenGLES是把图层合成到FrameBuffer,然后把FrameBuffer提交给HWComposer进行剩余的合成和显示工作,HWComper通过HWC模块合成部分图层和FrmaeBuffer,最终显示到Display上.
这里有个问题:BufferQueue(Layer)为什么有些直接发送到HWComposer合成,有些还要经过GPU合成一个新的BufferQueue(Layer)才能给到HWComposer?
有两方面的原因:
一方面可能是,不同厂商支持的HWCComposer最多处理的图层Layer不同,比如最多支持HWComposer最多支持6个图层的合并工作,但是SurfaceFlinger送过来8个,那么剩余的2个SurfaceFlinger会标记为GPU合成(OpenGLES或者Vulkan),GPU合成剩余的2个图层后再交给HWComposer进行合并;
另外一种可能性是,某些操作HWComposer不支持直接写入(SurfaceFlinger会询问HWComposer是否支持直接合成),那么SurfaceFlinger处理这部分再交给HWComposer。
App进程和系统SurfaceFlinger进程进行通信把Buffer给queue到BufferQueue中给到SurfaceFlinger消费,这里通信方式不是Binder而是匿名共享内存,原因是因为传输内容过大,为了便于管理Android系统把匿名共享内存抽象为SharedClient,没有SharedClient最多可以有31个SharedBufferStack(每个对应一个surface,即一个app最多可以有31个surface)
三、源码分析
3.1 View#invalidate/requestlayout到ViewRootImpl#scheduleTraversals
View#invalidate
-->View#invalidateInternal
-->ViewGroup#invalidateChild
-->...逐级递归调用 ViewParent#invalidateChildInParent...
-->ViewRootImpl#invalidate
-->ViewRootImpl#scheduleTraversals
ViewGroup#setLayoutParams/ViewGroup#setBackgroundDrawable/View#setPadding/RecyclerView#setAdapter/RecyclerView#setLayoutManager/RecyclerView#addItemDecoration/RecyclerView#onChanged等等
-->...逐级递归调用 mParent.requestLayout...
-->ViewRootImpl#requestLayout
-->ViewRootImpl#scheduleTraversals
ViewParent最顶层就是ViewRootImpl,所以最终都调用到ViewRootImpl#scheduleTraversals
调用View#invalidate会将View的Rect标记为PFLAG_DIRTY,然后在下一个Vsync进行刷新ViewTree时,ViewRootImpl从最顶层的View逐步向下分发View#draw,通常不会触发measure和layout,ondraw也只会在具有PFLAG_DIRTY的View进行调用。
调用View#requestlayout会将当前View标记为PFLAG_FORCE_LAYOUT,然后在下一个Vsync进行刷新ViewTree时,ViewRootImpl从最顶层的View逐步向下分发,具有PFLAG_FORCE_LAYOUT属性就会回调measure、layout和draw。
3.2 scheduleTraversals发送同步屏障及mChoreographer.postCallback一个类型为CALLBACK_TRAVERSAL回调
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//向ViewRootImpl的线程(通常为UIThread)发送一条同步屏障,阻塞后续的同步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//然后mChoreographer.postCallback一个类型为CALLBACK_TRAVERSAL,回调为mTraversalRunnable的消息
//mChoreographer.postCallback的内部实现是关键
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
public ViewRootImpl(Context context, Display display) {
//构造ViewRootImpl时,调用Choreographer.getInstance获取单例对象
mChoreographer = Choreographer.getInstance();
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
//Vsync信号回调Choreographer.CALLBACK_TRAVERSAL消息时,调用doTraversal
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//首先移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
//然后开始进行绘制,内部调用performMeasure,performLayout和performDraw
performTraversals();
}
}
3.3Choreographer初始化和postCallback实现
Choreographer定义了五种帧同步信号回调类型(主要为:用户触屏input类型;动画animation类型;遍历绘制traversal类型)
Choreographer通过ThreadLocal线程独立的方式,并使用ViewRootImpl的Looper进行初始化,保证Choreographer的mHandler发送消息也是到ViewRootImpl的线程
DisplayEventReceiver是一个关键内部类,用于向SurfaceFlinger注册ScheduleVsync信号,并进行回调触发CallbackQueue
public static final int CALLBACK_INPUT = 0;
public static final int CALLBACK_ANIMATION = 1;
public static final int CALLBACK_INSETS_ANIMATION = 2;
public static final int CALLBACK_TRAVERSAL = 3;
public static final int CALLBACK_COMMIT = 4;
private static final int CALLBACK_LAST = CALLBACK_COMMIT;
private final CallbackQueue[] mCallbackQueues;
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
return new Choreographer(looper, VSYNC_SOURCE_APP);
}
};
public static Choreographer getInstance() {
return sThreadInstance.get();
}
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
//使用ViewRootImpl的Looper进行初始化,Choreographer的mHandler发送消息也是发送到ViewRootImpl的线程
mHandler = new FrameHandler(looper);
//DisplayEventReceiver是一个关键内部类,用于向SurfaceFlinger注册ScheduleVsync信号,并进行回调监听
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//初始化CallbackQueues,每个CallbackQueue都是一个链表,初始化5中类型的CallbackQueue
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
CallbackQueue是一个链表结构,根据 支持两种类型的回调:常规的事件回调(上面那5种 input animator traversal等);另外一类是FRAME_CALLBACK_TOKEN,提供给App注册监听Vsync信号到来时机,做一些帧率监控等
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) { #由Choreographer.postFrameCallback(mRenderProfiler)注册监听
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run(); #由Choreographer.postCallback(int callbackType, Runnable action, Object token)注册监听
}
}
}
private final class CallbackQueue {
private CallbackRecord mHead;
...
}
对android4.1黄油计划引入Vsync之后的版本,通过发起下一个Vsync信号监听的方式进行调度。
由于ViewRootImpl#scheduleTraversals() 时用屏障消息阻塞了同步消息,这里通过和ViewRootImpl在一个线程的mHandler发送插入到messagequeue头部的异步消息MSG_DO_SCHEDULE_VSYNC,最终调用到mDisplayEventReceiver.scheduleVsync()发起对下一个Vsync信号的监听。
对于android4.1之前的不支持Vsync的版本,根据当前时间,上一帧时间以及帧间隔16.67ms,判断是否触发do_frame回调。
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) { #由Choreographer.postFrameCallback(mRenderProfiler)注册监听
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run(); #由Choreographer.postCallback(int callbackType, Runnable action, Object token)注册监听
}
}
}
private final class CallbackQueue {
private CallbackRecord mHead;
...
}
3.4 FrameDisplayEventReceiver 发起对下一个Vsync监听以及回调处理
FrameDisplayEventReceiver继承自DisplayEventReceiver,通过 DisplayEventReceiver#scheduleVsync调用Native方法最终向SurfaceFlinger发起下一个Vsync信号监听请求;当监听到下一个Vsync信号时,从Native回调上来 DisplayEventReceiver#dispatchVsync,进一步调用FrameDisplayEventReceiver#onVsync。
此时由于ViewRootImpl#scheduleTraversals() 时用同步屏障阻塞了同步消息,仍处于阻塞状态,把该消息设置为异步消息,不受同步屏障限制,进行doFrame回调
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
//当收到来自SurfaceFlinger下一帧Vsync信号回调时,就会调用onVsync这个方法。发送一个回调是FrameDisplayEventReceiver#run的消息
Message msg = Message.obtain(mHandler, this);
//由于ViewRootImpl#scheduleTraversals() 时用同步屏障阻塞了同步消息,此时同步消息仍处于阻塞状态,把该消息设置为异步消息,不受同步屏障限制
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
}
public abstract class DisplayEventReceiver {
public DisplayEventReceiver(Looper looper, int vsyncSource) {
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
vsyncSource);
}
//通过scheduleVsync调用Native方法,最终向SurfaceFlinger发起下一个Vsync信号监听请求
public void scheduleVsync() {
nativeScheduleVsync(mReceiverPtr);
}
//当监听到下一个Vsync信号时,从Native回调上来
// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
}
3.5 doFrame回调Choreographer不同类型的事件
依次处理用户交互触屏input,界面动画animation,界面遍历绘制traversal等类型回调
void doFrame(long frameTimeNanos, int frame) {
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
void doCallbacks(int callbackType, long frameTimeNanos) {
synchronized (mLock) {
//对当前 callbackType 类型的 CallbackQueue 循环遍历,找出 dueTime 大于当前时间的第一个 CallbackRecord,
//因为 CallbackQueue 是按照 dueTime「从小到大」排序的,最早的 CallbackRecord 在 mHead 位置
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
}
for (CallbackRecord c = callbacks; c != null; c = c.next) {
//逐个回调,根据Choreographer.postFrameCallback还是Choreographer.postCallback做不同类型的回调
c.run(frameTimeNanos);
}
}
3.6 ViewRootImpl#doTraversal绘制
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//首先移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
//然后开始进行绘制,内部调用performMeasure,performLayout和performDraw
performTraversals();
}
}
至此Java层流程分析完毕,我们整体回顾下
View#invalidate/requestlayout
---》ViewRootImpl#scheduleTraversals
--》Choreographer.postCallback类型为CALLBACK_TRAVERSA注册监听回调
--》Choreographer由五种类型帧同步信号回调(input,animation,traversal等)同时内部的mhandler和ViewRootImpl在用一个线程,DisplayEventReceiver#scheduleVsync向SurfaceFlinger注册Vsync回调监听
--》SurfaceFlinger收到HAL层发送过来的Vsync信号,回调给DisplayEventReceive#onVsync
--》onVsync回调ViewRootImpl种postCallback的runnable,比如上面的.CALLBACK_INSETS_ANIMATION类型
--》ViewRootImpl#doTraversal内部调用performMeasure,performLayout和performDraw
下面我们看下 Choreographer和SurfaceFlinger之间的交互过程
3.7 DisplayEventDispatcher::scheduleVsync 请求下一个Vsync信号的监听
DisplayEventReceiver#nativeScheduleVsync通过JNI调用到NativeDisplayEventReceiver#scheduleVsync(是在其父类DisplayEventDispatcher种定义实现)
#frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp
static void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {
sp<NativeDisplayEventReceiver> receiver =
reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);
status_t status = receiver->scheduleVsync();
}
#frameworks/native/libs/gui/DisplayEventDispatcher.cpp
status_t DisplayEventDispatcher::scheduleVsync() {
if (!mWaitingForVsync) {
// Drain all pending events.
nsecs_t vsyncTimestamp;
PhysicalDisplayId vsyncDisplayId;
uint32_t vsyncCount;
VsyncEventData vsyncEventData;
//读取 vsyncTimestamp vsyncDisplayId vsyncCount和vsyncEventData数据,这里是通过封装为gui::BitTube的Socket进行通信的
if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount, &vsyncEventData)) {
ALOGE("dispatcher %p ~ last event processed while scheduling was for %" PRId64 "", this,
ns2ms(static_cast<nsecs_t>(vsyncTimestamp)));
}
//这里是关键,请求下一个Vsync信号。mReceiver对应的是DisplayEventReceiver
status_t status = mReceiver.requestNextVsync();
...
}
return OK;
}
DisplayEventDispatcher::processPendingEvents通过Socket获取vsync信息( vsyncTimestamp vsyncDisplayId vsyncCount和vsyncEventData数据)
bool DisplayEventDispatcher::processPendingEvents(nsecs_t* outTimestamp,
PhysicalDisplayId* outDisplayId,
uint32_t* outCount,
VsyncEventData* outVsyncEventData) {
bool gotVsync = false;
// 创建一个大小为 100 的事件数据
DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
ssize_t n;
// 通过DisplayEventReceiver 从 Socket 读端获取数据
while ((n = mReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
...
for (ssize_t i = 0; i < n; i++) {
const DisplayEventReceiver::Event& ev = buf[i];
switch (ev.header.type) {
// 读取到了一个 VSYNC 信号,并把数据赋值给参数
case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
gotVsync = true;
*outTimestamp = ev.header.timestamp;
*outDisplayId = ev.header.displayId;
*outCount = ev.vsync.count;
*outVsyncEventData = ev.vsync.vsyncData;
break;
case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
if (ev.hotplug.connectionError == 0) {
dispatchHotplug(ev.header.timestamp, ev.header.displayId,
ev.hotplug.connected);
} else {
dispatchHotplugConnectionError(ev.header.timestamp,
ev.hotplug.connectionError);
}
break;
case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE:
dispatchModeChanged(ev.header.timestamp, ev.header.displayId,
ev.modeChange.modeId, ev.modeChange.vsyncPeriod);
break;
case DisplayEventReceiver::DISPLAY_EVENT_NULL:
dispatchNullEvent(ev.header.timestamp, ev.header.displayId);
break;
case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE:
mFrameRateOverrides.emplace_back(ev.frameRateOverride);
break;
...
}
}
}
return gotVsync;
}
为什么说是是通过Socket获取的Vsync信息,我们继续来看下DisplayEventReceiver的构造函数
#frameworks/native/libs/gui/DisplayEventReceiver.cpp
DisplayEventReceiver::DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vsyncSource,
EventRegistrationFlags eventRegistration,
const sp<IBinder>& layerHandle) {
sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
if (sf != nullptr) {
mEventConnection = nullptr;
#createDisplayEventConnection是SurfaceFlinger的函数,这里通过Binder进行跨进程通信
binder::Status status =
sf->createDisplayEventConnection(vsyncSource,
static_cast<
gui::ISurfaceComposer::EventRegistration>(
eventRegistration.get()),
layerHandle, &mEventConnection);
if (status.isOk() && mEventConnection != nullptr) {
#这里的DataChannel数据通道是gui::BitTube类型,它是封装了Socket的API
mDataChannel = std::make_unique<gui::BitTube>();
#把Socket数据通道设置给mEventConnection
status = mEventConnection->stealReceiveChannel(mDataChannel.get());
...
}
}
}
BitTube的定义如下,可以看到它就是Socket的封装接口
frameworks/native/libs/gui/BitTube.cpp
// Socket buffer size. The default is typically about 128KB, which is much larger than we really
// need. So we make it smaller.
static const size_t DEFAULT_SOCKET_BUFFER_SIZE = 4 * 1024;
BitTube::BitTube(size_t bufsize) {
init(bufsize, bufsize);
}
BitTube::BitTube(DefaultSizeType) : BitTube(DEFAULT_SOCKET_BUFFER_SIZE) {}
BitTube::BitTube(const Parcel& data) {
readFromParcel(&data);
}
void BitTube::init(size_t rcvbuf, size_t sndbuf) {
int sockets[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets) == 0) {
size_t size = DEFAULT_SOCKET_BUFFER_SIZE;
setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
// since we don't use the "return channel", we keep it small...
setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
fcntl(sockets[0], F_SETFL, O_NONBLOCK);
fcntl(sockets[1], F_SETFL, O_NONBLOCK);
mReceiveFd.reset(sockets[0]);
mSendFd.reset(sockets[1]);
}
...
}
综上Android的Vsync正在进程间通信时,使用了Binder和Socket
通过Binder(sf->createDisplayEventConnection)进行初始连接和事件注册
通过Socket(gui::BitTube)进行数据传输,它提供非阻塞、高性能的进程间数据传输,适用于高频率、低延迟的Vsync数据传输
获取到下一个Vsync信息后,我们就可以进行下一个Vsync信号的监听请求,DisplayEventReceiver::requestNextVsync
#frameworks/native/libs/gui/DisplayEventReceiver.cpp
status_t DisplayEventReceiver::requestNextVsync() {
if (mEventConnection != nullptr) {
// 通过mEventConnection请求下一个Vsync,那么mEventConnection封装了socket数据通道,通过socket方式进行数据传输
mEventConnection->requestNextVsync();
return NO_ERROR;
}
return mInitError.has_value() ? mInitError.value() : NO_INIT;
}
3.8 DisplayEventDispatcher::dispatchVsync VSYNC 的分发处理
#frameworks/native/libs/gui/DisplayEventDispatcher.cpp
int DisplayEventDispatcher::handleEvent(int, int events, void*) {
// Drain all pending events, keep the last vsync.
nsecs_t vsyncTimestamp;
PhysicalDisplayId vsyncDisplayId;
uint32_t vsyncCount;
VsyncEventData vsyncEventData;
//从Socket端口读取 vsyncTimestamp vsyncDisplayId vsyncCount和vsyncEventData数据
if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount, &vsyncEventData)) {
mWaitingForVsync = false;
mLastVsyncCount = vsyncCount;
//分发这个Vsync信号,调用到NativeDisplayEventReceiver::dispatchVsync,最终回调到java层的DisplayEventReceiver#dispatchVsync
dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount, vsyncEventData);
}
...
return 1; // keep the callback
}
NativeDisplayEventReceiver::dispatchVsync 通过JNI派发 Vsync信号给到java层的 DisplayEventReceiver#dispatchVsync
void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId,
uint32_t count, VsyncEventData vsyncEventData) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
//获取到了 Java 层的 DisplayEventReceiver 对象
ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal ));
ScopedLocalRef<jobject> vsyncEventDataObj(env, GetReferent(env, mVsyncEventDataWeakGlobal));
if (receiverObj.get() && vsyncEventDataObj.get()) {
env->SetIntField(vsyncEventDataObj.get(),
gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo
.preferredFrameTimelineIndex,
vsyncEventData.preferredFrameTimelineIndex);
...
//通过 JNIEnv 调用 dispatchVsync 方法
env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync,
timestamp, displayId.value, count);
}
mMessageQueue->raiseAndClearException(env, "dispatchVsync");
}
到此,我们对native层Choreograhper和SufaceFlinger间进行发送Vsync信号监听以及进行派发处理回调到java层的整体流程也分析完毕,其中App和SurfaceFlinger进程的Vsync信号传递是Binder建立连接,Socket进行数据通信。
-->DisplayEventDispatcher::scheduleVsync, 请求下一个Vsync信号的监听
-->DisplayEventDispatcher::processPendingEvents通过Socket获取vsync信息( vsyncTimestamp vsyncDisplayId vsyncCount和vsyncEventData数据)
-->DisplayEventReceiver::requestNextVsync 进行下一个Vsync信号的监听请求
-->DisplayEventDispatcher::dispatchVsync VSYNC 的分发处理
-->NativeDisplayEventReceiver::dispatchVsync 通过JNI派发 Vsync信号给到java层的 DisplayEventReceiver#dispatchVsync
四、Perfetto视角串联流程
Perfetto先分别filter VSync、RenderThreaed以及SurfaceFlinger然后pin,和UIThread一起进行观察渲染流程,可以看到整个流程非常清晰
-
屏幕根据自身刷新频率(60帧/s、90帧/s、120帧/s),发送Vsync信号。Android系统存在两个Vsync:Vsync-app和Vsync-sf,其中Vsync-app用于通知APP进程开始自己的窗口渲染新的一帧;Vsync-sf用于控制SurfaceFlinger合成画面并送给显示屏驱动的节奏。
-
Choreographer的内部类FrameDisplayEventRecevier#onVsync收到Native层的回调
-
触发APP进程的Choreographer#doFrame 依次进行input、animator、traversal等处理,其中开启硬件加速的情况,UIThread只是构建Displaylist,分离逻辑和执行,减少UI耗时
-
视图树的Displaylist构建完成后,UIThred把其同步给RenderThread,这里用到了CanvasProperty替换基本类型,进行动态映射,延迟计算和渲染
-
在RenderThread线程使用OpenGLES/Vulkan在GPU上进行绘制渲染
-
渲染完成后调用eglSwapBuffer把渲染的backbuffer和frontbuffer进行交换,然后作为生产者将frontbuffer通过共享内存的方式给到BufferQueue
-
SurfaceFlinger进程收到Vsync-sf
-
SurfaceFlinger作为消费者从BuffferQueue中获取GraphicBuffer构建layer,然后询问HWComper是否可以自己合成,如果可以直接给到HWComper,如果不行则自己先调用OpenGLES/Vulkan机型合成后再给到HWComper
-
HWComper完成最终的合并后,进行上屏显示
五、问题解答
我们来回顾下文章开头提到的那些问题,绝大部分问题上面已经给出答案,下面对几个问题我们在这里再展开说明下
问题2:为什么View刚被加载时想获取其的宽高,需view.post(Runnalbe)中获取?
当View被添加到ViewGroup,UI发生变化,调用View#requestlayout,进一步调用到ViewRootImpl#scheduleTraversals发送先同步屏障消息,Choreographer收到下一帧的Vsync回调时,Choreographer的内部类方法FrameDisplayEventReceiver#onVsync调用异步消息进行ViewRootImpl#doTraversal,该方法会移除同步屏障并调用performTraversals完成measure layout和draw. 这时view已经绘制完毕可以获取到真实的宽高信息,View.Post的Runnale作为同步消息顺序执行即可获取正确的宽高
#View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
//如果view已经attach到window了,通过AttachInfo获取ViewRootImpl的Handler向ViewRootImpl发送一条同步消息,通过action回调中获取view的宽高
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
//如果view还没有attach到widow,先把该runnable加入到View持有的一个待执行队列,等到ViewRootImpl分发dispatchAttachedToWindow时,再一次性把该待执行队列都Post一个message
getRunQueue().post(action);
return true;
}
问题4:Vsync信号是谁发出的?需要App主动请求吗?界面没有变化是否会有Vsync信号?App是否响应Vsync信号取决于什么?
Vsync和三缓冲是android4.1黄油计划引入的提升Android系统操作流畅度减少丢帧卡顿的一个重要的机制,
Vsync是一般是由硬件发出,经过HAL(硬件抽象层)的HWComposer转为Vsync信号,Vsync分为Vsync-app和Vsync-sf,其中Vsync-app用于app进程的渲染, Vsync-sf用于SurfaceFlinger将多层layer的给到HWComposer,最终合并上屏,
当屏幕需要刷新的时候通过View#invaildate或者View#requestlayout,执行到ViewRootImpl#scheduleTraversals,然后向Choerogragher postCallback一个Choreographer.CALLBACK_TRAVERSAL类型的回调,接着调用到Native层的DisplayEventDispatcher::scheduleVsync请求下一个Vsync信号的监听,当收到Vysnc信号后通过DisplayEventDispatcher::dispatchVsync回掉给Java层,FrameDisplayEventReceiver#onVsync收到底层过来的Vsnc信号后进行绘制渲染等相关处理.
所以Vsync信号的发出和页面是否变化没有关系,有屏幕控制,App做的是当需要页面刷新时,请求下一个Vsync信号的监听回调.
问题7:SurfaceView/TextureView渲染视频时频繁的刷新界面,为什么UI界面不卡顿?
先说SurfaceView, android中一个Window对应一个Surface,但是SurfaceView比较特殊,它拥有独立的Surface,通过该surface进行渲染,详细来说:SurfaceView上的内容并非View#Canvas直接绘制,而是底层的Surface(作为生产者从BufferQueue中获取的GraphicBuffer)独立渲染到屏幕上,这样做的好处是SurfaceView上内容的更新不需要经过主线程频繁的调用Veiw#invalidate或者View#requestlayout来重新绘制整个View树,视频解码厚的图像帧直接传递了个Surface,SurfaceFlinger收到Vsync-sf信号后对所有的layer(App层对应的就是Surface)进行合成,进而上屏显示.
再说texureView,TextureView本质上是一个普通的View,但它内部将输入的图像数据(例如视频帧)作为一个OpenGL纹理处理,纹理的更新通过SurfaceTexure的异步机制实现, 然后在下一个Vsync-app进行ViewTree绘制时只需要从最新的纹理中取出数据进行合成渲染,不需要等视频解码或者纹理上传完成
踪上,无论是SurfaceView还是Texturveiw,视频频繁刷新UI不卡顿的关键在于,视频帧的更新和UIThread的绘制基本是异步的,不需要为每帧都触发ViewTree的invalidate或requestlayout.
问题9:动画可以在RenderThread上执行吗?
先简单看android 5.0引入RenderThread支持硬件加速后UIThread和RenderThread是如何分工和传递数据的?
开启硬件加速后,Canvas由DisplayListCanvas,该类重载了部分绘制方法,对原有的基础类型被进行CanvasProperty对象包装.
代码如下所示
//开启硬件加速情况下,Canvas由DisplayListCanvas重装,其中部分方法的参数类型CanvasProperty对象进行封装
public abstract class DisplayListCanvas extends BaseRecordingCanvas {
/** @hide */
protected DisplayListCanvas(long nativeCanvas) {
super(nativeCanvas);
}
/**
* TODO: Public API alternative
* @hide
*/
@UnsupportedAppUsage
public abstract void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top,
CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx,
CanvasProperty<Float> ry, CanvasProperty<Paint> paint);
...
}
使用CanvasProperty对象对原始基本类型进行包装(例如: Float被包装为CanvasProperty).
public final class CanvasProperty<T> {
@UnsupportedAppUsage
public static CanvasProperty<Float> createFloat(float initialValue) {
return new CanvasProperty<Float>(nCreateFloat(initialValue));
}
...
private static native long nCreateFloat(float initialValue);
}
那么为什么使用CanvasProperty替换基本类型?
CanvasProperty本质上提供了一个延迟计算的能力,它不直接存储值,而是一个计算函数,在UIThread提供逻辑,真正的渲染在RenderThread线程调用到该函数时才计算,这样做有两个好处
1. 解耦绘制逻辑和运算执行,延迟不必要得计算,只进行Displaylist的构建将耗时的渲染计算和操作给到RenderThread线程,减少UIThread的负载
2. RenderThread线程绘制时会进行归类重排序,对DisplaylistOps进行优化,生成更高效的渲染指令
这样是不是我们动态的修改CanvasProperty的计算函数,就可以在RenderThread线程进行平滑的动画处理?
https://medium.com/@workingkills/understanding-the-renderthread-4dc17bcaf979这篇文章给出了说明和相关的实现代码
问题13:整体上介绍下Android绘制渲染流程,App开发者熟悉渲染流程后对于日常开发有什么帮助?
整体上介绍渲染流程上面已经提到过不止一次,不清楚可以再回看下.
熟悉渲染流程对于App开发者有什么帮助, 可以从三个方面思考:
1.输入层: 减少不必要的View#invalidate和View#requestLayout直接或间接的调用, 减少页面层级和使用更高效的控件
2. 处理层: 解耦数据,逻辑和渲染,数据上采用异步获取组装或拆装,预加载或者懒加载; 渲染上充分利用RenderThread和GPU,熟悉不同系统回调和复用机制; 逻辑上有更多的时间和精力专注于业务逻辑和创新.
3.输出层:流畅度 秒开率等技术指标和用户体验
本质上都是围绕着,减少CPU上UIThread的处理任务和耗时, 充分利用RenderThread和GPU,提升性能和用户体验, 另外熟练使用性能分析工具和监控工具并了解其运行机制,对我们也会有帮助.
最后引用张绍文 高手课中的一句话结束全文和自我激励.
虽然很多大厂有专门的性能优化团队,但我觉得鼓励和培养团队里的每一个人都去关注性能问题更加重要。我们在使用性能工具的同时,要学会思考,应该知道它们的原理和局限性。更进一步来说,你还可以尝试去为这些工具做一些优化,从而实现更加完善的方案。
Android 底层基于 Linux 内核,像 systrace、Simpleperf 也是利用 Linux 提供的机制实现,因此学习一些 Linux 的基础知识,对于理解这些工具的工作原理以及排查性能问题,都有很大帮助。
切记不要浮躁,多了解和学习一些底层的技术,对我们的成长会有很大帮助。日常开发中我们也不能只满足于完成需求就可以了,在实现上应该学会多去思考内存、卡顿这些影响性能的点,我们比别人多想一些、多做一些,自己的进步自然也会更快一些。
六、资料
-
android官网graphics:https://source.android.google.cn/docs/core/graphics
-
Android 渲染机制——原理篇(显示原理全过程解析)https://budaye.blog.youkuaiyun.com/article/details/110921652
-
史上最全Android渲染机制讲解:https://juejin.cn/post/6844904084139425806
-
Android 基于 Choreographer 的渲染机制详解:https://androidperformance.com/2019/10/22/Android-Choreographer/
-
Android渲染机制:https://luisliu.cn/post/android/android-render/
-
Android图形系统(三)系统篇:渲染/合成的底层原理浅析 https://juejin.cn/post/7132777622487957517
-
Understanding the RenderThread:https://medium.com/@workingkills/understanding-the-renderthread-4dc17bcaf979
-
Android Performance系列文章:https://www.androidperformance.com/
感谢你的阅读
欢迎关注公众号“音视频开发之旅”,一起学习成长。
欢迎交流