androidUI卡顿原理分析及Vsync信号机制
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了androidUI卡顿原理分析及Vsync信号机制相关的知识,希望对你有一定的参考价值。
参考技术A 一、UI卡顿定义1、用户角度:app操作界面刷新缓慢,响应不及时;界面滑动不够流畅;
2、系统角度:屏幕刷新帧率不稳定,掉帧严重,无法保证每秒60帧,导致屏幕画面撕裂;
二、UI卡顿常见原因分析以及处理方案
1、过度绘制:
原因:界面布局设计不合理或者过于复杂导致系统无法在16毫秒内完成渲染,view过度绘制导致CPU或者GPU负载过重,View频繁触发measure、layout操作,导致measure、layout累计耗时严重以及整个View错误的频繁重新渲染;
方案:优化界面布局,使界面布局视图扁平化,去除不必要的背景颜色,减少透明色的使用;
方案依据原理:尽量减少View在系统中measure、layout、draw的累计时间;
2、UI线程的复杂运算
原因:UI主线程运算耗时
方案:减少UI线程中数据运算,使用子线程处理耗时任务
3、频繁GC
原因:(1)、内存抖动;(2)、瞬间产生大量对象,消耗内存;
方案:尽量避免在循环逻辑或者onDraw方法中频繁创建新对象和使用局部变量;
三、android Vsync机制
1、什么是Vsync ?
Vsync 是Vertical Synchronization(垂直同步)的缩写,是一种在PC上很早就广泛使用的技术,可以简单的把它认为是一种定时中断。而在Android 4.1(JB)中已经开始引入VSync机制,用来同步渲染,让AppUI和SurfaceFlinger可以按硬件产生的VSync节奏进行工作。
2、Android屏幕刷新过程
Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,屏幕的刷新过程是每一行从左到右(行刷新,水平刷新,Horizontal Scanning),从上到下(屏幕刷新,垂直刷新,Vertical Scanning)。当整个屏幕刷新完毕,即一个垂直刷新周期完成,会有短暂的空白期,此时发出 VSync 信号。所以,VSync 中的 V 指的是垂直刷新中的垂直-Vertical。
3、没有使用Vsync的情况
可见vsync信号没有提醒CPU/GPU工作的情况下,在第一个16ms之内,一切正常。然而在第二个16ms之内,几乎是在时间段的最后CPU才计算出了数据,交给了Graphics Driver,导致GPU也是在第二段的末尾时间才进行了绘制,整个动作延后到了第三段内。从而影响了下一个画面的绘制。这时会出现Jank(闪烁,可以理解为卡顿或者停顿)。这时候CPU和GPU可能被其他操作占用了,这就是卡顿出现的原因;
4、使用Vsync同步
CPU/GPU接收vsync信号,Vsync每16ms一次,那么在每次发出Vsync命令时,CPU都会进行刷新的操作。也就是在每个16ms的第一时间,CPU就会响应Vsync的命令,来进行数据刷新的动作。CPU和GPU的刷新时间,和Display的FPS是一致的。因为只有到发出Vsync命令的时候,CPU和GPU才会进行刷新或显示的动作。CPU/GPU接收vsync信号提前准备下一帧要显示的内容,所以能够及时准备好每一帧的数据,保证画面的流畅;
5、多级缓冲
Android除了使用Vsync机制,还使用了多级缓冲的策略来优化屏幕显示,如双重缓冲(A + B),当Display buffer A 数据时,CPU/GPU就已经在buffer B 中处理下一帧要显示的数据了。
可是,当系统资源紧张性能降低时,导致GPU在处理某帧数据时太耗时,在Vsync信号到来时,buffer B的数据还没准备好,此时不得不显示buffer A的数据,这样导致后面CPU/GPU没有新的buffer准备数据,空白时间无事可做,后面Jank频出
因此采用三级缓冲来解决系统对性能不稳定导致的卡顿
当出现上面所述情况后,新增一个buffer C 可以减少CPU和GPU在Vsync同步间的空白间隙,此时CPU/GPU能够利用buffer C 继续工作,后面buffer A 和 buffer B 依次处理下一帧数据。这样仅是产生了一个Jank,可以忽略不计,以后的流程就顺畅了。
注:在多数正常情况下还是使用二级缓冲机制,三级缓冲只是在需要的时候才使用;
vsync 信号分发源码分析
上周发了一篇 ,光知道生成怎么行呢?vsync 信号分发源码深度分析奉上,由底层硬件到上层 Choreographer,让我们一起来揭开 vsync 的神秘面纱吧
-
SurfaceFlinger>>DispSync -
DispSync>>DispSyncSource -
DispSyncSource>>EventThread -
Connection>>SurfaceFlinger -
Connection>>app
1. SurfaceFlinger>>DispSync
vsync 信号由 HWComposer 类生成后,会回调 SurfaceFlinger.cpp 的 onVSyncReceived 方法:
void SurfaceFlinger::onVSyncReceived(int32_t type, nsecs_t timestamp) {
mPrimaryDispSync.addResyncSample(timestamp);
}
mPrimaryDispSync 为 DispSync 类型,DispSync 在构造时开启了一个 DispSyncThread 工作线程,专门负责 vsync 信号的分发:
DispSync::DispSync(const char* name) :
mName(name),
mRefreshSkipCount(0),
mThread(new DispSyncThread(name)) {
mThread->run("DispSync", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE);
...
}
接着看 DispSync 的 addResyncSample 方法是如何处理 vsync 信号的:
bool DispSync::addResyncSample(nsecs_t timestamp) {
mResyncSamples[idx] = timestamp; //vsync 信号时间戳
updateModelLocked();
}
void DispSync::updateModelLocked() {
mThread->updateModel(mPeriod, mPhase, mReferenceTime);
}
DispSync 内部记录下 vsync 信号时间戳后,通过 updateModel 方法通知内部工作线程 DispSyncThread:
void updateModel(nsecs_t period, nsecs_t phase, nsecs_t referenceTime) {
Mutex::Autolock lock(mMutex); //锁
mPeriod = period;
mCond.signal(); //唤醒
}
注意当前还处于 vsync 信号生成线程,若是软件生成则还处于 HWComposer 的 VSyncThread 线程,为了不影响 vsync 信号的生成工作,这里切换到另外的线程去分发。
具体而言,就是通过 mCond.signal() 方法通知 DispSyncThread 线程去进一步分发,DispSyncThread 的分发工作在其 threadLoop 方法中:
virtual bool threadLoop() {
while (true) {
{
Mutex::Autolock lock(mMutex); //锁
if (mPeriod == 0) {
mCond.wait(mMutex); //没 vsync 信号就阻塞
continue;
}
now = systemTime(SYSTEM_TIME_MONOTONIC);
// 收集注册了需要接受 vsync 信号的回调
callbackInvocations = gatherCallbackInvocationsLocked(now);
}
if (callbackInvocations.size() > 0) {
//分发回调
fireCallbackInvocations(callbackInvocations);
}
}
}
至此 DispSync 中对 vsync 信号的分发工作执行完毕。
2. DispSync>>DispSyncSource
在 DispSync 内部工作线程 DispSyncThread 中,对注册需要接受 vsync 信号的回调进行调用,完成了 vsync 信号分发工作。
那具体是谁注册了监听?是谁需要进一步接受 vsync 信号呢?还是要从 DispSyncThread 的 threadLoop 方法入手,它通过 gatherCallbackInvocationsLocked 方法获取到当前已注册的监听,代码如下:
Vector<CallbackInvocation> gatherCallbackInvocationsLocked(nsecs_t now) {
Vector<CallbackInvocation> callbackInvocations;
for (size_t i = 0; i < mEventListeners.size(); i++) {
CallbackInvocation ci;
ci.mCallback = mEventListeners[i].mCallback;
callbackInvocations.push(ci);
}
return callbackInvocations;
}
然后通过 fireCallbackInvocations 方法回调了监听:
void fireCallbackInvocations(const Vector<CallbackInvocation>& callbacks) {
for (size_t i = 0; i < callbacks.size(); i++) {
callbacks[i].mCallback->onDispSyncEvent(callbacks[i].mEventTime);
}
}
DispSync 内部对监听进行了封装,我们只关注外部传来的 mCallback,其为 DispSync::Callback 类型。通过以上代码可以知道 mCallback 被记录在 mEventListeners 中,那只要再看下哪里将监听添加到 mEventListeners 中,就能知道到底是谁注册的监听了。
在 SurfaceFlinger 初始化时 DispSyncSource 与 DispSync(mPrimaryDispSync) 产生关联:
void SurfaceFlinger::init() {
sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
vsyncPhaseOffsetNs, true, "app");
mEventThread = new EventThread(vsyncSrc, *this);
sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
sfVsyncPhaseOffsetNs, true, "sf");
mSFEventThread = new EventThread(sfVsyncSrc, *this);
...
}
也就是说 DispSyncSource 内部持有 DispSync,在 DispSyncSource 内部将监听注册到 DispSync 中:
virtual void setVSyncEnabled(bool enable) {
Mutex::Autolock lock(mVsyncMutex);
if (enable) {
status_t err = mDispSync->addEventListener(mName, mPhaseOffset,
static_cast<DispSync::Callback*>(this));
} else {
status_t err = mDispSync->removeEventListener(
static_cast<DispSync::Callback*>(this));
}
mEnabled = enable;
}
DispSyncSource 本身继承了 DispSync::Callback 接口,即直接将自身作为监听注册到 DispSync 中。
也就是说,在 DispSync 内部工作线程中通过 fireCallbackInvocations 分发 vsync 信号,实际上调用了 DispSyncSource 的 onDispSyncEvent 方法。
需要注意的是,SurfaceFlinger init 时创建了两个 DispSyncSource,分别是上层 app 和 surfaceFlinger 自身,为什么要分这两种呢?仅仅是代码上的逻辑分离吗?
仔细观察 SurfaceFlinger init 中对这两个 DispSyncSource 的构造入参,分别传入了 vsyncPhaseOffsetNs 和 sfVsyncPhaseOffsetNs,这是要实现 vsync 信号的错时分发,即不同时分发 vsync 信号给上层 app 和 surfaceFlinger。
3. DispSyncSource>>EventThread
接着来看 DispSyncSource 的 onDispSyncEvent 方法是如何处理 vsync 信号的:
virtual void onDispSyncEvent(nsecs_t when) {
sp<VSyncSource::Callback> callback;
{
Mutex::Autolock lock(mCallbackMutex);
callback = mCallback;
}
if (callback != NULL) {
callback->onVSyncEvent(when);
}
}
又是一个 callBack 回调了出去,这个 callBack 又是谁呢?可以再看一下上面的 SurfaceFlinger init 方法,类似于 DispSyncSource 向 DispSync 注册监听,EventThread 构造时传入了 DispSyncSource,EventThread 向 DispSyncSource 注册了监听。
callBack 类型为 VSyncSource::Callback,而 EventThread 自身也实现了这个接口:
class EventThread : public Thread, private VSyncSource::Callback
接着来看 EventThread 内部实现的 onVSyncEvent 方法:
void EventThread::onVSyncEvent(nsecs_t timestamp) {
Mutex::Autolock _l(mLock);
mVSyncEvent[0].header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
mVSyncEvent[0].header.id = 0;
mVSyncEvent[0].header.timestamp = timestamp; //vsync 时间戳
mVSyncEvent[0].vsync.count++;
mCondition.broadcast();
}
注意当前还处于 DispSync 的工作线程,这里将 vsync 信号记录到 DisplayEventReceiver::Event 类型的 mVSyncEvent 数组中后,通过 mCondition.broadcast() 通知 EventThread 工作线程去处理,即从 DispSync 工作线程切换到了 EventThread 工作线程。
下面来看 EventThread 的工作线程中是如何处理的:
bool EventThread::threadLoop() {
DisplayEventReceiver::Event event;
Vector< sp<EventThread::Connection> > signalConnections;
signalConnections = waitForEvent(&event);
const size_t count = signalConnections.size();
for (size_t i=0 ; i<count ; i++) {
const sp<Connection>& conn(signalConnections[i]);
status_t err = conn->postEvent(event);
}
return true;
}
同样是通过返回 true 来实现线程无限循环调用 threadLoop 方法,其中比较关键的是 waitForEvent 方法,关键逻辑如下:
Vector< sp<EventThread::Connection> > EventThread::waitForEvent(
DisplayEventReceiver::Event* event){
Vector< sp<EventThread::Connection> > signalConnections;
do {
检测 mVSyncEvent 数组中是否有 vsync 信号
如果有传递给 event 参数,并开始遍历所有注册的 connection
如果 connection.count >= 0,就加到 signalConnections 中
if(waitForVSync){
mCondition.waitRelative(mLock, timeout); //休眠等待
}
} while (signalConnections.isEmpty());
return signalConnections;
}
梳理一下 EventThread 工作线程中的逻辑:通过 waitForEvent 方法进入休眠等待 vsync 信号,如果有 vsync 信号会唤醒,并将 vsync 信号传递给 event,返回 Connection,随后将 vsync 信号分发给了 Connection。
现在我们的关注点变为 EventThread::Connection,它是谁注册的?其 postEvent 方法又是如何进一步分发 vsync 信号的呢?
4. Connection>>SurfaceFlinger
依然聚焦于 vsync 的传递为脉络,接着来看 Connection 的 postEvent 方法:
status_t EventThread::Connection::postEvent(
const DisplayEventReceiver::Event& event) {
DisplayEventReceiver::sendEvents(mChannel, &event, 1);
}
ssize_t DisplayEventReceiver::sendEvents(const sp<BitTube>& dataChannel,
Event const* events, size_t count){
return BitTube::sendObjects(dataChannel, events, count);
}
ssize_t BitTube::sendObjects(const sp<BitTube>& tube,
void const* events, size_t count, size_t objSize){
const char* vaddr = reinterpret_cast<const char*>(events);
ssize_t size = tube->write(vaddr, count*objSize);
}
可以看到 Connection 内部持有一个 BitTube,BitTube 是用来处理进程间通讯的机制,和管道类似,基于 SocketPair 封装实现。SocketPair 用来创建一对未命名、互相连接的套接字,套接字的一端可以进行读和写的操作,用来实现全双工的通讯。
在 Connection 中将 vsync 信号数据写入了 BitTube,那在哪里监听读呢?这就要分 SurfaceFlinger 与 app 两路了,首先来看 SurfaceFlinger 是如何接收的。再回到 SurfaceFlinger 的 init 方法:
void SurfaceFlinger::init() {
sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
vsyncPhaseOffsetNs, true, "app");
mEventThread = new EventThread(vsyncSrc, *this);
sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
sfVsyncPhaseOffsetNs, true, "sf");
mSFEventThread = new EventThread(sfVsyncSrc, *this);
mEventQueue.setEventThread(mSFEventThread);
...
}
在构造完 SurfaceFlinger 的 EventThread 后,通过 setEventThread 方法将 EventThread 设置给 mEventQueue,mEventQueue 为 MessageQueue 类型,方法如下:
void MessageQueue::setEventThread(const sp<EventThread>& eventThread){
mEventThread = eventThread;
mEvents = eventThread->createEventConnection();
mEventTube = mEvents->getDataChannel();
mLooper->addFd(mEventTube->getFd(), 0, Looper::EVENT_INPUT,
MessageQueue::cb_eventReceiver, this);
}
这下就全走通了,SurfaceFlinger 的消息队列监听了 Connection 中的 BitTube,当 vsync 信号在 EventThread 中分发给 Connection 写入 BitTube 后,SurfaceFlinger 的消息队列就能收到通知了。
接着来看 SurfaceFlinger 的 MessageQueue 如何进一步处理 vsync 消息,在 MessageQueue 的 setEventThread 方法中设置接受到 vsync 后回调 cb_eventReceiver 方法:
int MessageQueue::cb_eventReceiver(int fd, int events, void* data) {
MessageQueue* queue = reinterpret_cast<MessageQueue *>(data);
return queue->eventReceiver(fd, events);
}
cb_eventReceiver 进一步调用 eventReceiver 方法:
int MessageQueue::eventReceiver(int /*fd*/, int /*events*/) {
ssize_t n;
DisplayEventReceiver::Event buffer[8];
while ((n = DisplayEventReceiver::getEvents(mEventTube, buffer, 8)) > 0) {
for (int i=0 ; i<n ; i++) {
if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
#if INVALIDATE_ON_VSYNC
mHandler->dispatchInvalidate();
#else
mHandler->dispatchRefresh();
#endif
break;
}
}
}
return 1;
}
可以看到读取出 vsync 信号数据后,只用到了 type 信息,然后通过 Handler 回调出去处理。
至此 vsync -> SurfaceFlinger 的分发流程分析完毕。
5. Connection>>app
尽管 vsync 传递给 SurfaceFlinger 流程发生在同个进程,却使用了支持跨进程的 BitTube。为什么要使用 BitTube 呢?可以想到的一个优点是,可以与 vsync 信号传递给上层 app 进程的方式统一。
我们知道上层 app 会统一将 UI 刷新请求发给 Choreographer,Choreographer 会在下次 vsync 信号到来时真正执行 UI 绘制,下面通过源码来搞懂底层的 vsync 是如何传递至 Choreographer 的。
Choreographer 中实际与 vsync 信号相关的逻辑在 DisplayEventReceiver 中,初始化 Choreographer 时会构造 DisplayEventReceiver:
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
mHandler = new FrameHandler(looper);
mDisplayEventReceiver = new FrameDisplayEventReceiver(looper, vsyncSource);
...
}
DisplayEventReceiver 构造函数中会调用到 DisplayEventReceiver.java 的 nativeInit 方法:
private static native long nativeInit(
WeakReference<DisplayEventReceiver> receiver,
MessageQueue messageQueue, int vsyncSource);
底层实现位于 android_view_DisplayEventReceiver.cpp 中:
static jlong nativeInit(JNIEnv* env,
jclass clazz,
jobject receiverWeak, jobject messageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
sp<NativeDisplayEventReceiver> receiver = new NativeDisplayEventReceiver(env,
receiverWeak, messageQueue);
status_t status = receiver->initialize();
...
return reinterpret_cast<jlong>(receiver.get());
}
构造了一个 NativeDisplayEventReceiver,其继承自 DisplayEventReceiver,在后者的构造函数中,创建了关键的 IDisplayEventConnection binder 接口,建立与 SurfaceFlinger 通信的连接:
DisplayEventReceiver::DisplayEventReceiver() {
// ISurfaceComposer binder 接口
sp<ISurfaceComposer> sf(ComposerService::getComposerService());
if (sf != NULL) {
// IDisplayEventConnection binder 接口
mEventConnection = sf->createDisplayEventConnection();
if (mEventConnection != NULL) {
mDataChannel = mEventConnection->getDataChannel();
}
}
}
为什么要将 MessageQueue 传递下来呢?因为要借助 Android Handler 已有的功能,即基于内部 epoll 机制添加对某文件描述符事件的监听,上文传递给 SurfaceFlinger 的原理也是如此。
nativeInit 中构造 NativeDisplayEventReceiver 后调用了其 initialize 方法,内部便通过 Looper 添加了对文件描述符的监听:
status_t DisplayEventDispatcher::initialize() {
int rc = mLooper->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT, this, NULL);
return OK;
}
然后再回到上层的 Choreographer ,其通过 scheduleVsync 请求下一次的 vsync 信号,最终会调用到 DisplayEventReceiver 的 native 方法:
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
private static native void nativeScheduleVsync(long receiverPtr);
底层同样是在 android_view_DisplayEventReceiver.cpp 中:
static void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {
sp<NativeDisplayEventReceiver> receiver =
reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);
receiver->scheduleVsync();
}
最终调用到 DisplayEventReceiver 的 requestNextVsync 方法:
status_t DisplayEventReceiver::requestNextVsync() {
mEventConnection->requestNextVsync();
}
上面提到 mEventConnection 是一个 binder 接口,跨进程调用到 BnDisplayEventConnection,而后者的实现类就是 SurfaceFlinger 中的 EventThread:
class EventThread : public Thread, private VSyncSource::Callback {
class Connection : public BnDisplayEventConnection {...}
所以会进一步调用到 EventThread 的 requestNextVsync 方法:
void EventThread::requestNextVsync(
const sp<EventThread::Connection>& connection) {
Mutex::Autolock _l(mLock);
mFlinger.resyncWithRateLimit();
if (connection->count < 0) {
connection->count = 0;
mCondition.broadcast();
}
}
上文对 EventThread 做过详细的分析,这里再贴一遍其工作线程的关键逻辑代码:
Vector< sp<EventThread::Connection> > EventThread::waitForEvent(
DisplayEventReceiver::Event* event){
Vector< sp<EventThread::Connection> > signalConnections;
do {
检测 mVSyncEvent 数组中是否有 vsync 信号
如果有传递给 event 参数,并开始遍历所有注册的 connection
如果 connection.count >= 0,就加到 signalConnections 中
if(waitForVSync){
mCondition.waitRelative(mLock, timeout); //休眠等待
}
} while (signalConnections.isEmpty());
return signalConnections;
}
如上,在 requestNextVsync 中将 connection->count 置为 0 然后唤醒 EventThread,EventThread 被唤醒后会将 connection 加入要通知 vsync 的 signalConnections 中,随后与上文中分析过的逻辑一致,通过 BitTube+looper 进一步分发 vsync 信号。
值得注意的是,在 waitForEvent 中判断如果 connection->count 为 0,会将 count 置为 -1,这意味着 requestNextVsync 方法名副其实,只是一次性请求接受一次 vsync 信号而已,要再调用 requestNextVsync 才能再次接受 vsync 信号。
最后再来看 vsync 信号传递到 java 层的流程,vsync 消息到来通过 dispatchVsync 方法分发:
int DisplayEventDispatcher::handleEvent(int, int events, void*) {
...
if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
mWaitingForVsync = false;
dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);
}
return 1;
}
然后又回到了与 java 层最接近的 android_view_DisplayEventReceiver.cpp 中:
void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));
if (receiverObj.get()) {
env->CallVoidMethod(receiverObj.get(),
gDisplayEventReceiverClassInfo.dispatchVsync, timestamp, id, count);
}
}
jni 调用了上层 DisplayEventReceiver.java 的 dispatchVsync 方法:
// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
随后进一步回调到 Choreographer 的 doFrame,执行 ViewRootImpl 传过来的 TraversalRunnable、调用 performTraversals 方法,由顶而下的执行界面绘制逻辑。
最后总结一下:vsync 信号分为 SurfaceFlinger、app 两路分发,这两路分别对应于一个 EventThread。当 vsync 信号到来时,会唤醒 EventThread 线程,然后 EventThread 通过注册到内部的 Connection 分发出去。Connection 进一步通过 BitTube 并使用消息机制 looper 监听 BitTube 的 fd,从而实现 vsync 的分发。
关注我
助你升职加薪
Android 面试官
以上是关于androidUI卡顿原理分析及Vsync信号机制的主要内容,如果未能解决你的问题,请参考以下文章