Android framework : UI刷新机制:Vsync and Choreographer

慕课网 剖析framework 笔记

6-1 说说android的UI刷新机制

 

这个和界面优化有关系,卡顿会影响用户体验,

理解UI刷新机制对解决问题有帮助的

 

问题:

1,丢帧是什么原因引起的?

2,Android的刷新频率是60帧/s,是每隔16ms就调用onDraw绘制一次?

3,onDraw之后屏幕会马上刷新吗?

4,如果界面没有重绘,还会每隔16ms刷新屏幕嘛》?

5,如果屏幕块刷新时才去onDraw绘制,会丢帧吗?

 

AP从系统拿到buffer,画完后返回给系统,

系统服务把buffer写到缓冲区,屏幕以一定频率刷新,每次刷新读缓冲区并显示,

如果缓冲区没有新数据,屏幕就一直使用老的数据。

 

 

屏幕的图像缓存不只有一个,如果只有一个,一边读一边写,会导致tearing,一般第一帧,一半第二帧,

所以一般有2个或者以上,一直swap就行了

 

下个问题,AP端什么时候开始绘制

屏幕每次收到vsync就从buffer拿数据显示,

绘制 是AP端发起的,随时可能发起,

所以1st脉冲显示0帧图像,2nd脉冲显示1st帧图像,

第三个时钟周期还是显示1st帧,因为2nd帧还没有画完,没画完不是因为它画的时间长,是因为它画的太晚了,vsync快来才画。

如果这种现象发生的非常频繁,用户就能感觉到,界面非常卡顿。

 

 

优化:

如果绘制也和显示一个节奏就行了,

每帧图像的绘制都控制在16ms以内,就行了,

但是有一个问题,AP层的View的重绘调RequestLayout,

这个函数什么时刻都可以调用。,怎么控制它真正绘制的时机?? : Choreographer

 

 

Android怎么处理的、?关键是一个类:Choreographer,舞蹈指导,

你往里发一个消息,它最快也要下一个vsync来的时候触发,

如,绘制随时发起,封装一个Runnable,丢个Choreographer,下个vsync来,处理这个消息,开始界面重绘。

相当于绘制节奏完全由Choreographer控制,

 

看看Choreographer的原理:

从requestLayout说起,它我们比较熟,是用来发起UI重绘的,

public void requestLayout(){
    ...
    scheduleTraversals();
}


void scheduleTraversals(){
    //它没直接绘制,做了2件事情,
    //1,往线程消息队列插了一个syncBarrier
    mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
    //2,往Choreographer消息队列插了一个callback
    mChoreographer.postCallback(Choregrapher.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    .....
}

 

1,syncBarrier就是一个屏障,把屏障插入消息队列,屏障后面的普通消息就要等着,屏障撤出才可以处理,

但是屏障对异步消息没有作用,这么做的原因:

因为有些类型的消息非常紧急,要马上处理。如果消息队列里面普通消息过多,耽误事,所以插了屏障,优先处理异步消息。

这里往Choreographer丢的Runnnable就是一个异步消息,

下个 Vsync来的时候,异步消息是要紧急处理的。

 

2,Choreographer,是和ViewRootImpl一起创建的,

创建:

//有getInstance,是单例吗?不是
public static Choreographer getInstance(){
    //他是一个ThreadLocal,ThreadLocal<Choreographer>
    //也就是说在不同的线程调用getInstance返回的是不同的Choreographer对象
    return SThreadInstance.get();
}

 

 

假如有人一口气调用了10次requestLayout,那么下次vsync到来前,会引发10次UI重绘吗?

不会

 

void scheduleTraversals(){
    //每次判bool变量,是false才进,什么时候置的false?在mTraversalRunnable => doTraversal
    if(!mTraversalScheduled){
        mTraversalScheduled = true;
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ....
    }
}
//下次vsync来的时候执行doTraversal,里面会给bool置flase
void doTraservsal(){
    if(mTraversalScheduled){
        mTraversalScheduled = false;
        performTraversals();
    }
}

 

看看这个callback是怎么加入到Choreographer的

private void postCallbackDelayedInternal(int callback Type, ...){
    ....
    //Choreographer里面有一个数组叫mCallBackQueues,数组里每个元素都是一个callback单链表
    //一方面要根据callback类型插入对应的单链表
    //另一方面要根据callback要执行的时间顺序排序,dueTime,越是马上发生的callback越放到链表的前面
    mCallbackQueue[callbackType].addCallbackLocked(dueTime, ...);
    ....
    scheduleFrameLocked(now);
}

private void scheduleFramelocked(long now){
    //如果当前线程就是Choreographer的工作线程,直接调shceduleVsyncLocked
    if(isRunningOnLooperThreadLocked()){
        scheduleVsyncLocked();
    }else{
        //否则要发消息到Choreographer的工作线程里面去,
        Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
        //消息是异步消息,不受到屏障的影响,
        msg.setAsynchronous(true);
        //消息要插入到消息队列的头,非常紧急,
        //因为要告诉SF,下一个vsync来时,第一时间通知我们,因为如果错过这个vysnc,就又要等一个周期
        mHandler.sendMessageAtFrontOfQueue(msg);
    }
}

 

scheduleVsyncLocked后会发生什么?

是告诉SF,我们要关注下一个vsync信号了,

当下个vsync信号发生,SF通知我们,然后回调到FrameDisplayEventReciver里面的onVsync()

class FrameDisplayEventReceiver extends DisplayEventReciver implements Runnable{
    @Override
    //timestamNanos是vsync信号的时间戳
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame){
        ....
        mTimestampNanos = timestampNanos;
        mFrame = frame;
        //这个消息this其实是一个runnable,就是后面的run()
        Message msg = Message.obtain(mHandler.this);
        mgs.setAsynchronous(true);
        //发了一个消息到Choreographer的工作线程里面去了,
        //封装一个消息丢出去干嘛?它不是切换工作线程,因为onVsync本身就是调到Choreographer里面的,
        //这个mHandler也是,这里发消息丢出去,它带了一个时间戳,表示消息要触发的时间,这样就可以按照时间戳顺序处理消息。
        mHandler.sendMessageAtTime(msg, timestampNanos/TimeUils.NANOS_PER_MS);
    }
    
    @Overide
    public void run(){
        //到时间了,消息被处理,就是执行doFrame
        doFrame(mTimestampNanos, mFrame);
    }
}

 

doFrame分两个阶段

 

//阶段1
//frameTimeNanos是这帧的时间戳
void doFrame(long frameTimeNanos, int frame){
    long intendedFrameTimeNanos = frameTimeNanos;
    long startNanos = System.nanoTime();
    //当前时间和时间戳间隔越大,这帧处理的延迟越大
    final long jitterNanos = startNanos - frameTimeNanos;
    //延迟大到超过一个周期,
    if(jitterNanos >= mFrameIntervalNanos){
        //算算延迟了几个周期
        final long skippedFrames = jitterNanos/mFrameIntervalNanos;
        //如果跳过的帧数大于这个常量,就会打印这个很熟悉的日志
        //主线程做的事情太多了,绘制都做不了,跳帧太多
        if(skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT){
            Log.i(TAG, "Skipped" + skippedFrames + "frames! " + "the appliction may be doing too much work on main thread");
        }
        .....
    }
}


//阶段2,处理callback,
//calback有4中类型,每种对应一个callback链表
void doFrame(long frameTimeNanos, int frame){
    ....
    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
    doCallbacks(CHoreographer.CALBACK_ANIMATION, frameTImeNanos);
    doCAllbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
    doCallBacks(Choreographer.CALLLBACK_COMMIT, frameTimeNanos);'
}

//执行对应的doCallbacks,
//callback有时间戳,到了时间才执行回调
void doCallbacks(int callbackType, long frameTimeNanos){
    CallbackRecord callbacks;
    //extractDueCallback作用就是从callbackQueue取出到了时间的callbacks,
    callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(...);
    //for循环执行链表中的callback的run函数
    for(CallbackRecode c = callbacks; c != null, c = c.next){
        c.run(frameTimeNanos)
    }
}

 

看看scheduleTraversals的时候传的callback是什么样的

//准备绘制,
void scheduleTraversals(){
    if(!mTraversalScheduled){
        mTraversalScheduled = true;
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ....
    }
}

//mTraversalRunnable,最后调的就是doTraversal() => performTraversals()
final class TraversalRunnbale implements Runnable{
    @Override
    public void run(){
        doTraversal(); // => performTraversals,真正绘制的地方
    }
}

 

总结:

1,AP层的View调用requestLayout要求重绘,

2,其中是new了一个Runnable丢到Choreographer的消息队列,

3,Choreographer没马上处理消息,它是通过requestNextVsync向SurfaceFlinger请求下一个vSync信号,

4,SF会在下个vsync来的时候,通过postSyncEvent向choreographer发送一个通知,

5,choreographer收到通知之后就会处理消息队列里面的消息

6,之前的requestLayhout对应的Runnable里面执行的就是performTraversal,真正的执行绘制

 

 

 

下面看看scheduleVsync的实现

private void scheduleVsyncLocked(){
    //会调到native的DisplayEventReceiver的shceduleVsync函数
    mDisplayEventReceiver.scheduleVsync();
}

status_t NativeDisplayEventReceiver::scheuleVsync(){
    status_t status = mReceiver.requestNextVsync();
}

status_t DisplayEventReceiver::requestNextVsync(){
    //requestNextVsync,请求下一个vsync,
    //mEventConnection对象怎么创建的?在DisplayEventRecevier的构造函数创建的
    mEventConnection->requestNextVsync();
}

DisplayEventReceiver::DisplayEventReceiver(){
    //拿到SF的binder句柄:ISurfaceComposer
    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
    if(sf != NULL){
        //得到mEventConnection,这是另外一个binder句柄,
        //这种套路很常见,拿到系统服务binder句柄后,要么openSeesion,要么CreateConnection,总之要单独弄一个通道。
        mEventConnection = sf->createDisplayEventConnection();
        //Channel是connection创建时new的一个BitTube对象
        mDataChannel = mEventConnection->getDataChannel();
    }
}

sp<BitTube> EventThread::Connection::getDataChannel() const{
    return mChannel;
}

//这种套路很常见,拿到系统服务binder句柄后,要么openSeesion,要么CreateConnection,总之要单独弄一个通道。

//Channel是connection创建时new的一个BitTube对象

 

BitTube其实就是两个描述符,通过socketpair创建

mSendFd

mReceiverFd

机制:

和管道有点像,如果有个人拿到了读Fd-ReceiverFd,阻塞在这里,另一个人拿到SendFd 写Fd时,ReceiverFd就会被唤醒。

socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)

 

 

看看connection如何创建的

//它的实现在SF进程里面
sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection(){
    //eventThread是一个线程:等待、处理event
    return mEventThread->createEventConnection();
}

sp<EventThread::Connection> EventThread::createEventConnection() const{
    //new Connection and return
    return new Connection(const_cast<EventThread*>(this));
}

EventThread::Connection::Connection(const sp<EventThread>&eventThread)
    //new BitTube
    :count(-1), mEventThread(eventThread),mChannel(new BitTube()){}
    
void EventThread::Connection::onFirstRef(){
    //向EventThread注册自己,这样EventThread有event的时候,就可以分发connection,
    //connection又调到我们应用进程里面
    mEventThread->registerDisplayEventConnection(this);
}    

//看看connection是怎么注册到eventThread的
status_t EventThread::registerDisplayEventConnection(const sp<EventThread::Connection> & connection){
    //把conection add到DisplayEventConnection列表
    mDisplayEventConnections.add(connection);
    //发广播,类似java的notifyAll,对应的有wait才对,
    //wait是在eventThread里面。
    mCondition.broadcast();  //notifyAll
}

 

看看EventThread是在哪里创建的

void SurfaceFlinger::init(){
    //vysnc信号源,传给了EventThread
    sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,..);
    //创建EventThread
    mEventThread = new EventThread(vsyncSrc);
    ...
}
//thread启动后不停的执行threadLoop
bool EventThread::threadLoop(){
    DisplayEventReceiver::Event event;
    Vector< sp<EventThread::Connection >signalConnections;
    //等待事件,返回Connection列表,里面都是event
    signalConnections = waitForEvent(&event);
    
    const size_t count = signalConnections.size();
    //遍历connection
    for(size_t i=0; i<count; i++){
        const sp<Connection> & conn(signalConnections[i]);
        //通过postEvent分发事件,典型的如vsync事件
        conn->postEvent(event);
    }
}

 

看看waitForEvent函数,这个函数很长,细节很多

Vector<sp<EvenThread::Connection>> EventThread::waitForEvent(..){
    Vector<sp<EventThead::Connection>>signalConnections;
    do{
        //看是否已经有vysnc信号来了,(不用关心vysnc怎么来,只关心怎么分发到应用层))
        //有,就准备connection列表返回
        //没有,等待vysnc
    }while(signalConnections.isEmpty());
    return signalConnctions;
}

//注意
void EventThread::requestNextVsync(const sp<EventThead>::Connection>&connection){
    //connection里面有一个字段count,当count>=0时,才表示connection关注vsyncEvent,
    //所以应用端调用requestNextVsync就是把count从-1变成0,
    if(connection->count < 0){
        connection->count = 0;
        mCondition.broadcast();
    }
}

 

当我们把connection加会signalConnections列表时,它有把count设置为-1.

这样你想接受下个vsync,就又要调一次requestNextVsync,把它重新置0

 

再看看事件如何分发出去

status_t EventThread::Connection::postEvent(const DispplayEeventReceiver::Event& event){
    ssize_t size = DIslayEventReceiver::sendEvents(mChannel, &event, 1);
    return size < 0 ? statuc_t(size):status_t(NO_ERROR);
}

ssize_t DisplayEventReceiver::sendEvnets(const sp<BitTube>&dataChannel, Event const* events, size_t count){
    return BitTube::sendObjects(dataChannel, events, count);
}

ssize_t BitTube::snedObjects(const sp<BitTube>& tube,...){
    const char* vaddr = reinterpret_cast<const char*>(events);
    ssize_t size = tube->wiret(vaddr, count *objSize);
    return size < 0 ? size:size/static_cast<ssize_t>(objSize);
}

 

BitTube就是socket管道,有两个fb,w/r,write就是写,那读的一端就会收到通知。

BitTube的描述符是在SF端创建的,写的一端是在SF进程,

1,那读的一端是如何传给应用进程的?

2,应用进程如何监听读FD的?

 

解答他们要看DislayEventRecevier的构造函数

DisplayEventRecevier::DisplayEventRecevier(){
    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
    mEventConnection = sf->createDisplayEventConnection();
    //得到mDataChannel,它就是BitTube
    mDataChannel = mEventConnection->getDataCannel();
}

//看看BitTube如何跨进程传递
//应用端拿到Connection的proxy端,getDataCannel把请求transact出去,
virtual sp<BitTube> getDataChannel() const {
    Parcel data, reply;
    data.writeInterfaceToken(IDisplayEventConnection::getInterfaceDescriptor());
    remote()->trascat(GET_DATA_CHANNEL, data, &reply);
    //BitTube根据reply把描述符还原出来,
    return new BitTube(reply);
}

//SF收到getDataChannel请求,会返回mChannel,Channel就是bitTube,
//Channel放到parcel,跨进程传递给应用进程,放到reply里面
sp<BitTube> EventThread::Connection::getDataChannel() const{
    return mChannel;
}

 

问题2,AP进程如何监听BitTube读事件?

要从Choreographer的构造函数说起,

private Choreographer(Looper looper){
    ...
    //如果用vsync信号,就会创建DisplayEventReceiver,FrameDisplayEventReceiver继承了DisplayEventReceiver,
    mDisplayEventReceiver = new FrameDisplayEventReceiver(looper);
}

public DisplayEventReceiver(Looper looper){
    ....
    //构造函数调到了nativeInit函数,它是native的函数,
    mRecevierPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this),...);
}

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject recevierWeak,...){
    ....
    //native层,创建一个NativeDisplayEventReceiver对象
    sp<NativeDisplayEventReceiver> recevier = new  NativeDisplayEventRecevier(...);
    //看看initialize
    status_t status = recevier->initialize();
    return reinterpret_cast<jlong.(receiver.get());
 }
 
 status_t NativeDisplayEventReceiver::initialize(){
     //往Looper里添加了一个fd,是要Looper监听fd的事情, 
     //EVENT_INPUT表示读事件,
     //参数this,是回调,表示如果FD有可读事件,就触发回调,后面讲,
     //fd是mRecevier的getFd,就是返回了DataChannel的getFd
     mMessageQueue->getLooper()->addFd(mRecevier.getFd(), 0, Looper::EVENT_INPUT, this, NULL);
     return OK;
 }
 
 DisplayEventReceiver::DisplayEventReceiver(){
    //拿到SF的binder句柄:ISurfaceComposer
    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
    if(sf != NULL){
        //得到mEventConnection,这是另外一个binder句柄,
        //这种套路很常见,拿到系统服务binder句柄后,要么openSeesion,要么CreateConnection,总之要单独弄一个通道。
        mEventConnection = sf->createDisplayEventConnection();
        //Channel是connection创建时new的一个BitTube对象
        mDataChannel = mEventConnection->getDataChannel();
    }
}

int DisplayEventRecevier::getFd() const{
    //前面讨论过,DataChannel就是BitTube
    return mDataChannel->getFd();
}

//BitTube有两个描述符,发送和接收,这里返回的是接收的,
//发送的Fd在SurfaceFlinger,
int BitTube::getFd() const{
    return mReceiveFd;
}

 

看看getlooer()->addFd,Fd如何添加到looper的,

int Looper::addFd(int fd, int ident, int events, ...){
    Request request;
    request.fd = fd;
    ....
    //new了epoll_event,加入epoll
    struct epoll_event eventItem;
    request.initEventItem(&eventItem);
    
    int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem);
    //把request加到mRequest列表
    mRequests.add(fd, request);
}

 

看看Looper如何检测FD的读事件

int Looper::pollnner(int timeoutMillis){
    ...
    //epoll_wait返回后在for里处理事件
    int eventCount = epoll_wait(mEpollFd, eventItems,...);
    
    //有两种事件,
    for(int i = 0; i< eventCount; i++){
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        //1,消息队列的事件
        if(fb == mWakeEventFd){
            ...
        }else{//2,其他fd事件,
            if(epollEvents & EPOLLIN) events |= EVENT_INPUT;
            //放到response列表,for结束后,统一处理responses
            pushResponse(events, mRequestl.valueAt(requestIndex));
        }
    }
    //统一处理Responses
    for(size_t i = 0 ; i < mResponses.size(); i++){
        Response& response = mResponsese.editItemAt(i);
        int fd = response.request.fd;
        intevents = response.evdnts;
        void *data = responose.request.data;
        //调用callback的handleEvent,返回值很重要,
        int callbackResult = response.request.callback->handleEvent(fd, events, data);
        if(callbackResult == 0){
            //返回0则删除这个fd
            removeFd(fd, response.request.seq);
        }
        response.request.callback.clear();
        result = POLL_CALLBACK;
    }
    
    return result;
}

我们添加的BitTube的Fd的回调是什么?

int NativeDisplayEventRecevier::handleEvent(int receiveFd, int events,...){
    //把SF发的event读进来,
    if(processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, ...)){
        mWaitingForVsync = false;
        //分发vysnc,
        dispatchVsync(vsyncTimestamp, vsyncDisplayId, vysncCount);
    }
    //返回1,Fd会一直被Looper监听,不会删除掉。
    return 1;
}

void NatvieDisplayEventRecevier::dispatchVsync(nsecs_t timestamp,...){
    //jni调用,调到java层的dispatchVysnc
    env->CallVoidMethod(recevierObj.get()),gDisplayEventReceiverClassInfo.dispatchVsync, timestamp,...);
}

void displatchVsync(long timestampNanos, int builtInDisplayId, int frame){
    //前面讲过,是处理Choreographer的callback的
    onVysnc(timestampNanos, builtInDisplayId, frame);
}

 

回到问题:

1,丢帧原因? mainthread有耗时操作,耽误了View的绘制

2,android的刷新频率是60帧/s,那每隔10ms调onDraw绘制一次?

60是vsync的频率,但不是每个vsync都发起绘制,需要AP端主动发起重绘,才会向SF请求接收Vsync信号,才会再下个vsync来的时候绘制。

3.onDraw后屏幕立刻刷新? 不是,下一个vsync来的时候花心

4,如果界面没有重绘,还会每隔17ms刷新屏幕? 会的,只是数据一直用的旧的

5,如果快刷新时才绘制会丢帧吗?

View重绘不会被马上执行,都是下个vsync来才开始的,所以何时发起重绘没什么关系

 

总结,述说Android的UI刷新机制,如何回答:

1,vsync的原理

2,Choreographer的原理

3,UI刷新的大致流程,应用和SurfaceFlinger的通信过程。

 

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值