慕课网 剖析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    //也就是说在不同的线程调用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置flasevoid 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 sf(ComposerService::getComposerService());    if(sf != NULL){        //得到mEventConnection,这是另外一个binder句柄,        //这种套路很常见,拿到系统服务binder句柄后,要么openSeesion,要么CreateConnection,总之要单独弄一个通道。        mEventConnection = sf->createDisplayEventConnection();        //Channel是connection创建时new的一个BitTube对象        mDataChannel = mEventConnection->getDataChannel();    }}sp 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 SurfaceFlinger::createDisplayEventConnection(){    //eventThread是一个线程:等待、处理event    return mEventThread->createEventConnection();}sp EventThread::createEventConnection() const{    //new Connection and return    return new Connection(const_cast(this));}EventThread::Connection::Connection(const sp&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 & connection){    //把conection add到DisplayEventConnection列表    mDisplayEventConnections.add(connection);    //发广播,类似java的notifyAll,对应的有wait才对,    //wait是在eventThread里面。    mCondition.broadcast();  //notifyAll}

 

看看EventThread是在哪里创建的

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

 

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

Vector> EventThread::waitForEvent(..){    Vector>signalConnections;    do{        //看是否已经有vysnc信号来了,(不用关心vysnc怎么来,只关心怎么分发到应用层))        //有,就准备connection列表返回        //没有,等待vysnc    }while(signalConnections.isEmpty());    return signalConnctions;}//注意void EventThread::requestNextVsync(const sp::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&dataChannel, Event const* events, size_t count){    return BitTube::sendObjects(dataChannel, events, count);}ssize_t BitTube::snedObjects(const sp& tube,...){    const char* vaddr = reinterpret_cast(events);    ssize_t size = tube->wiret(vaddr, count *objSize);    return size < 0 ? size:size/static_cast(objSize);}

 

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

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

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

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

 

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

DisplayEventRecevier::DisplayEventRecevier(){    sp sf(ComposerService::getComposerService());    mEventConnection = sf->createDisplayEventConnection();    //得到mDataChannel,它就是BitTube    mDataChannel = mEventConnection->getDataCannel();}//看看BitTube如何跨进程传递//应用端拿到Connection的proxy端,getDataCannel把请求transact出去,virtual sp 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 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(this),...);}static jlong nativeInit(JNIEnv* env, jclass clazz, jobject recevierWeak,...){    ....    //native层,创建一个NativeDisplayEventReceiver对象    sp recevier = new  NativeDisplayEventRecevier(...);    //看看initialize    status_t status = recevier->initialize();    return reinterpret_castgetLooper()->addFd(mRecevier.getFd(), 0, Looper::EVENT_INPUT, this, NULL);     return OK; }  DisplayEventReceiver::DisplayEventReceiver(){    //拿到SF的binder句柄:ISurfaceComposer    sp 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的通信过程。

 

 

 

更多相关文章

  1. 史上最全Android开发资料:资源、UI、函数库、测试、构建全套教程
  2. Android Handler消息队列的实现原理
  3. 【android】手写一套Java的Handler程序,深入理解Android消息机制
  4. android异步线程利用Handler将消息发送至UI线程
  5. Android消息机制原理,仿写Handler Looper源码跨线程通信原理--之
  6. Android日志消息的生成详细步骤
  7. Android应用程序键盘(Keyboard)消息处理机制分析(16)
  8. android 测试读取LEB数据的函数

随机推荐

  1. Android火焰效果程序
  2. Android(安卓)Log史上最强大的.最易用的
  3. Android(安卓)application 中使用 provid
  4. Android(安卓)手撸一个简易路由Router
  5. Android墨迹3.0特性介绍效果实现——做一
  6. Android(安卓)五步修改状态栏颜色
  7. android主流屏幕适配方案总结
  8. Android(安卓)九宫格解锁Demo--Android(
  9. Android之通用MVP模式框架
  10. Android面试的一些总结