Android 5.1 SurfaceFlinger VSYNC详解

原文网址(转载请注明出处):
http://blog.csdn.net/newchenxf/article/details/49131167

VSYNC是自android 4.1版本以后加入的,目的是为了改善android的流畅程度。
其实网上已经有不少朋友对android SurfaceFlinger的VSYNC做分析,比如下面的这些文章,本人也是阅读了很多别人家的文章后,深读代码,加log调试研究,才写的,感谢他们。
1. Android 4.4(KitKat)中的设计模式-Graphics子系统
http://blog.csdn.net/jinzhuojun/article/details/17427491
2. Android 4.4(KitKat)中VSync信号的虚拟化
http://blog.csdn.net/jinzhuojun/article/details/17293325
3. android4.1及后续版本的UI平滑技术
http://blog.csdn.net/sfrysh/article/details/8312766
4. Android应用程序UI硬件加速渲染环境初始化过程分析
http://blog.csdn.net/luoshengyang/article/details/45769759

现在Android已经到了6.0,不过我还没拉到6.0的代码,所以就先用5.1版本做分析。好了,现在我们开始。
我们将分3部分来讲解。一是什么是VSYNC,一是VSYNC如何从hardware传到surfaceflinger,一是VSYNC如何从surfaceflinger传到application。

一、什么是VSYNC

VSYNC是啥?他就是一个信号源。假设你的屏幕是60FPS的话,那意味着,每隔1s,那么屏幕就有60次中断信号产生,即,每隔16.666ms,就会有一次中断信号产生。这个能干嘛用呢?对于屏幕来说,它无非就是每次来一个中断信号,就刷新一下屏幕。
在Android 4.1以前,VSYNC和android没有半毛钱关系。但到了4.4及以后,android就引入了VSYNC,干嘛用呢?为的就是利用这个中断信号,来刷新UI,使得更新UI变得更加有序,流畅。

加入VSYNC以后,UI是如何画图和更新的呢?
首先,我们必须明白,android graphics的主干就是2件事:
一个是画图,一个是合成显示。
画图 需要android app调用Canvas API去画。至于最后是纯软件画,还是用OpenGL ES,也就是硬件加速去画,那就看具体情况了。这里不详说。
合成显示 需要surfaceflinger拿到app画好的几张图(因为当前可见的UI不只一个app,某个app也不一定只有一张图,所以说是,几张图,其实对于framework来说,图也可以认为是一个Surface,某一个最最普通的activity,它会有个layout对吧,那其实会对应一个Surface,当然了,如果你这layout里还有SurfaceView控件,那这个activity就有2个Surface了),合成为一张,然后送出去。如果你是机顶盒平台,那就是通过HDMI的驱动,输出到电视。如果你是某个android手机,那应该是送到linux的framebuffer driver,该驱动会把数据render到屏幕上。

那么,VSYNC作用就来了。
Drawingwith VSYNC

如图所示,Display代表合成显示,CPU/GPU代表画图。
在第1个VSYNC时间,CPU/GPU画图,Display还没拿到图,不显示。
第2个VSYNC时间,图1画好了,Display拿到,就显示它,同时,CPU/GPU也开始画第2个图。
第3个VSYNC时间,图2画好了,Display拿到,就显示它,同时,CPU/GPU也开始画第3个图。
如此往复。
当然了,上图是一个示意图,实际使用时,会用tripple buffer的机制,具体详见【android4.1及后续版本的UI平滑技术】。
另外,实际工作也会加上一定的偏移。
这里写图片描述
虽然有偏移,但大家工作既保持一定的节拍,又可以相互错开,一前一后保持着咚次哒次,也挺好的,还可以让CPU能错开工作高峰。其中两个Phase offset参数(即VSYNC_EVENT_PHASE_OFFSET_NS和SF_VSYNC_EVENT_PHASE_OFFSET_NS)是可调的。
具体详见【Android 4.4(KitKat)中VSync信号的虚拟化

总而言之,有2个地方需要用VSYNC,一个是surfaceflinger,每来一个VSYNC,就合成显示一次。一个是application,每来一个VSYNC,就画一张图。

二、VSYNC如何从hardware传到surfaceflinger

理论上,VSYNC是硬件产生的,是一个中断信号。比如,我当前做的机顶盒项目,这中断信号是HDMI产生的,我的电视是60FPS,因此每16.666..ms就有一个中断。一般ODM厂商已有驱动程序,会接收该中断,其他进程可以通过ioctl来间接接收该中断。假设该驱动是disp_driver。
我们知道,Android Graphics有一个 HWComposer HAL库,这是ODM实现的。其实,这个库也要完成VSYNC的工作。它通过与disp_driver通信,去监听硬件产生的VSYNC中断,收到以后,立马通过callback函数,返回给android framework 的HWComposer。当然啦,framework的HWComposer也得向它的HAL注册callback,不然HAL就没法回调了。
注册函数是:
hwc_composer_device_1 -> registerProcs
VSYNC的回调函数是:
HWComposer -> hook_vsync

对android来说,Graphics的核心进程时surfaceflinger,它主要用于合成各种surface并显示。HWComposer HAL是它加载的。显然HAL的VSYNC callback也是给他的。

surfaceflinger有4个线程与VSYNC有关。
1个EventControlThread,用于开始/关闭 HWComposer HAL的VSYNC。
1个是DispSyncThread,用于接收HWComposer HAL的VSYNC信号,并分发给EventThread。
2个EventThread。之所以是2个,是因为surfaceflinger创建了2个EventThread对象,一个给application用,一个给自己用。如上文所说,application用它来画图,surfaceflinger用它来显示。EventThread类主要是接收DispSyncThread发来的VSYNC,并分发给真正使用的人。
这些线程是在surfaceflinger初始化的时候创建的。
代码如下:

void SurfaceFlinger::init() {
......
    // start the EventThread
    //mPrimaryDispSync is DispSync
    sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
            vsyncPhaseOffsetNs, true, "app");
    mEventThread = new EventThread(vsyncSrc);
    sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
            sfVsyncPhaseOffsetNs, true, "sf");
    mSFEventThread = new EventThread(sfVsyncSrc);
    mEventQueue.setEventThread(mSFEventThread);

    mEventControlThread = new EventControlThread(this);
    mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY);
}

DispSync::DispSync() :
        mRefreshSkipCount(0),
        mThread(new DispSyncThread()) {
    //start DispSyncThread
    mThread->run("DispSync", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE);

从代码可知,2个EventThread对象分别是mEventThread,给app用,mSFEventThread,给surfaceflinger自己用。

下面给出这4个Thread关系图。
VSYNC相关的thread
从这个图,我们可以清晰的看出VSYNC的传递过程。

HWComposer HAL通过callback函数,把VSYNC信号传给DispSyncThread,DispSyncThread传给EventThread。这就是本节要讨论的,VSYNC如何从hardware传到android framework。
postEvent是干嘛呢?它用于把VSYNC传递给app,是下节要讨论的内容。这里先简单描述一下:
如果是app用的话,那么,app会向EventThread注册一个connection(每个app启动时,都会注册)。所以该EventThread会向所有的connection发送VSYNC信号(通过postEvent 函数)。
而如果是surfaceflinger自己用的话,就简单了,就自己一个connection。
上面代码的mEventQueue.setEventThread(mSFEventThread)就是surfaceflinger自己注册connection的。
且看该函数的实现

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);
}

通过createEventConnection向EventThread注册了connection。
surfaceflinger如何根据vsync的节奏来工作,比较简单,和app如何根据vsync的节奏来工作差不多,所以本文只讨论app如何使用vsync,也就是第三节的内容。

前面的模块图毕竟比较简约,我这里给出详细的,VSYNC如何从HWComposer HAL发到EventThread。
这里写图片描述
从图中可知,HWComposer HAL层获得硬件中断,然后通过callback函数,回调到SurfaceFlinger的onVsyncReceived。
SurfaceFlinger调用DispSync的updateModel函数,唤醒DispSyncThread。
DispSyncThread通过fireCallbackInvocations函数,告诉DispSyncSource有VSYNC了。

    void fireCallbackInvocations(const Vector<CallbackInvocation>& callbacks) {
        for (size_t i = 0; i < callbacks.size(); i++) {
            callbacks[i].mCallback->onDispSyncEvent(callbacks[i].mEventTime);
        }
    }

mCallback就是DispSyncSource,因为他继承了DispSync::Callback。
所以,就调用到了DispSyncSource的onDispSyncEvent函数。

    virtual void onDispSyncEvent(nsecs_t when) {
        sp<VSyncSource::Callback> callback;
        {
            Mutex::Autolock lock(mMutex);
            callback = mCallback;
            ......
        }
        if (callback != NULL) {
            callback->onVSyncEvent(when);
        }
    }

对DispSyncSource来说,它的mCallback成员变量,是EventThread(EventThread继承了VSyncSource::Callback)。
所以,又调用了EventThread的onVSyncEvent函数。
这么说来,其实我们可以把DispSyncSource理解为DispSyncThread与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;
    mVSyncEvent[0].vsync.count++;
    mCondition.broadcast();
}

他也比较简单,存下vsync,然后唤醒EventThread。
EventThread的threadLoop唤醒后,通过postEvent,向所有注册过的connection发送VSYNC消息。

VSYNC从surfaceflinger到app

上一节讲了VSYNC从HAL到framework(其实就是surfaceflinger),这一节就讲讲VSYNC从surfaceflinger到app。
其实就是那个给app用的EventThread如何把VSYNC送到app。

首先,每个app创建时,都会new一个RenderThread(有且只有一个)。看名字就知道,它用于管理app UI 。
它初始化的时候,会new DisplayEventReceiver

void RenderThread::initializeDisplayEventReceiver() {
    ......
    mDisplayEventReceiver = new DisplayEventReceiver();
    ......

    // Register the FD
    mLooper->addFd(mDisplayEventReceiver->getFd(), 0,
            Looper::EVENT_INPUT, RenderThread::displayEventReceiverCallback, this);
}

DisplayEventReceiver是啥?
看名字就知道,它用于接收VSYNC。
首先,对于EventThread,其connection是这么定义的。
它是一个binder调用。
API定义在IDisplayEventConnection,总共就3个函数。

class IDisplayEventConnection : public IInterface
{
public:
    /*
     * getDataChannel() returns a BitTube where to receive the events from
     */
    virtual sp<BitTube> getDataChannel() const = 0;

    virtual void setVsyncRate(uint32_t count) = 0;

    virtual void requestNextVsync() = 0;
};

服务端的具体实现是Connection类,它继承了BnDisplayEventConnection ,并具体实现了3个函数。
定义在frameworks/native/services/surfaceflinger/EventThread.h

class Connection : public BnDisplayEventConnection {
public:
    Connection(const sp<EventThread>& eventThread);
    status_t postEvent(const DisplayEventReceiver::Event& event);
        ....
private:
    virtual ~Connection();
    virtual void onFirstRef();
    virtual sp<BitTube> getDataChannel() const;
    virtual void setVsyncRate(uint32_t count);
    virtual void requestNextVsync();    // asynchronous
    sp<EventThread> const mEventThread;
    sp<BitTube> const mChannel;
};

这个类最重要的成员变量,就是mChannel,它是BitTube类。BitTube是啥?
字面意思就是字节管道。如果看它的构造函数,就能发现,其实它就是包了socket,用于进程间通信。

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));
        // sine 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 = sockets[0];
        mSendFd = sockets[1];
    } else {
        mReceiveFd = -errno;
        ALOGE("BitTube: pipe creation failed (%s)", strerror(-mReceiveFd));
    }
}

DisplayEventReceiver构造函数,就首先会调用surfaceflinger去注册一个connection,connection又会创建BitTube。客户端便可以通过getDataChannel()获得这个BitTube。
那么进程通信就可以开始了。
surfaceflinger进程的EventThread把VSYNC不断的往BitTube写(想象成往socket写),而app进程将会创建DisplayEventReceiver,该app进程又可以不断的读BitTube(想象成读socket)。这样,VSYNC就可以传输了。

那么,谁会创建DisplayEventReceiver呢?是传说中的RenderThread。
如上文所说,每个app创建,都会new一个RenderThread,用于管理UI的画图。如同上面讲的4个线程, 这个RenderThread也是继承Thread类,当我们调用它的成员函数run的时候,就会创建一个新的线程。这个新的线程的入口点函数为RenderThread类的成员函数threadLoop。

RenderThread类有一个成员变量mQueue,描述的是一个Task Queue。这个Queue用于保存所有需要执行的Task,每一个Task都是用一个RenderTask对象来描述。同时,RenderTask类有一个成员变量mRunAt,用来表明Task的执行时间。这样,保存在Task Queue的Task就可以按照执行时间从先到后的顺序排序,这个mRunAt最终会存VSYNC。于是,RenderThread类的成员函数nextTask通过判断排在队列头的Task的执行时间是否小于等于当前时间,就可以知道当前是否有Task需要执行。如果有Task需要执行的话,就将它返回给调用者。我们可以把Task当做是画图任务。

这里给出RenderThread创建DisplayEventReceiver的代码。

void RenderThread::initializeDisplayEventReceiver() {
    LOG_ALWAYS_FATAL_IF(mDisplayEventReceiver, "Initializing a second DisplayEventReceiver?");
    mDisplayEventReceiver = new DisplayEventReceiver();
    status_t status = mDisplayEventReceiver->initCheck();
    LOG_ALWAYS_FATAL_IF(status != NO_ERROR, "Initialization of DisplayEventReceiver "
            "failed with status: %d", status);

    // Register the FD
    mLooper->addFd(mDisplayEventReceiver->getFd(), 0,
            Looper::EVENT_INPUT, RenderThread::displayEventReceiverCallback, this);
}

这个函数的一个语句很重要:
mLooper->addFd(mDisplayEventReceiver->getFd(), 0,
Looper::EVENT_INPUT, RenderThread::displayEventReceiverCallback, this);
它表示,监听一个文件描述符,如果有变化,就执行回调函数displayEventReceiverCallback。
那么,mDisplayEventReceiver->getFd()获得的文件描述符是啥?
且看定义

int DisplayEventReceiver::getFd() const {
    //mDataChanel is BitTube
    if (mDataChannel == NULL)        
        return NO_INIT;
    return mDataChannel->getFd();
}

再看BitTube的定义

int BitTube::getFd() const
{
    return mReceiveFd;
}

啊,原来,这个mReveivedRd就是sockets[0]。

而回调函数displayEventReceiverCallback干嘛呢?查看函数的调用顺序,就可以看到,其实它就是去读sockets[0]。
所以一切就明白了,surfaceflinger进程与某个app进程传递VSYNC,就是surfaceflinger的EventThread线程与app的RenderThread通过socket通信来传递。
下面给出流程图。
这里写图片描述
那么,RenderThread通过displayEventReceiverCallback回调,获得VSYNC后,会干嘛呢?当然就是画图啦!!
文章【Android应用程序UI硬件加速渲染环境初始化过程分析】有比较详细的介绍,我这里忽略一些条件判断等各种细节,大概描述一下主体流程:
displayEventReceiverCallback会调用drainDisplayEventQueue,请看这个函数。

void RenderThread::drainDisplayEventQueue() {
    ATRACE_CALL();
    nsecs_t vsyncEvent = latestVsyncEvent(mDisplayEventReceiver);
    if (vsyncEvent > 0) {
        mVsyncRequested = false;
        if (mTimeLord.vsyncReceived(vsyncEvent) && !mFrameCallbackTaskPending) {
            ATRACE_NAME("queue mFrameCallbackTask");
            mFrameCallbackTaskPending = true;
            nsecs_t runAt = (vsyncEvent + DISPATCH_FRAME_CALLBACKS_DELAY);
            queueAt(mFrameCallbackTask, runAt);
        }
    }
}

首先通过latestVsyncEvent获得最新发出的VSYNC时间,然后,通过queueAt,把mFrameCallbackTask及对应的VSYNC时间插入Task Queue,并调用“mLooper->wake();”唤醒线程。
来看线程函数:

bool RenderThread::threadLoop() {
    ......
    initThreadLocals();

    int timeoutMillis = -1;
    for (;;) {
        int result = mLooper->pollOnce(timeoutMillis);
        LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR,
                "RenderThread Looper POLL_ERROR!");

        nsecs_t nextWakeup;
        // Process our queue, if we have anything
        while (RenderTask* task = nextTask(&nextWakeup)) {
            task->run();
            // task may have deleted itself, do not reference it again
        }
        ......
    }
}

线程唤醒后,执行task->run()。他会干嘛呢?
查看其定义知,就是调用
mRenderThread->dispatchFrameCallbacks()。
而这个函数就是真正去调用画图啦!
代码片段如下,doFrame()是真正干活的函数。

void RenderThread::dispatchFrameCallbacks() {
    ......
    for (std::set<IFrameCallback*>::iterator it = callbacks.begin(); it != callbacks.end(); it++) {
        (*it)->doFrame();
    }
}

总结一下就是。
1. App进程创建了RenderThread对象,RenderThread又创建了DisplayEventReceiver对象。
2. DisplayEventReceiver获得surfaceflinger的client handle,然后通过函数createDisplayEventConnection,向EventThread注册一个IDisplayEventConnection。这其实是binder调用。
3. DisplayEventReceiver通过IDisplayEventConnection->getDataChannel()获得BitTube对象,该对象包了socket管道的文件句柄,用于后面两个进程传递VSYNC。
4. 这些都准备好以后,一切都开始咚次哒次的跑起来。SurfaceFlinger的EventThread线程通过socket与App的RenderThread传递VSYNC时间。RenderThread获得VSYNC后,根据VSYNC的节拍,通过调用doFrame去最终画图。

结语

android graphics非常复杂庞大,VSYNC只是冰山一角。未来还有更多的知识值得学习。。。

  • 9
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
Android 中,View 是用户界面的基本组件,用于显示和处理用户界面上的各种元素。Android 13 中的 SurfaceFlinger 通过与 View 系统进行协作来实现定时刷新 View 的功能。 具体来说,Android 中的 View 系统提供了一个名为 Choreographer 的系统服务,它可以帮助应用程序实现定时刷新 View 的功能。Choreographer 可以协调应用程序中各种动画和 UI 事件的执行顺序,并在每一帧结束时向 SurfaceFlinger 发送 VSync 信号,从而触发 SurfaceFlinger 开始下一帧的渲染。 当应用程序中的某个 View 需要进行定时刷新时,可以通过以下步骤来实现: 1. 在 View 中创建一个 Choreographer.FrameCallback 对象,并重写其 doFrame() 方法来实现 View 的刷新逻辑; 2. 在 View 中注册 FrameCallback 对象,以便在每一帧结束时得到回调通知; 3. 在 View 的生命周期中,通过 Choreographer.getInstance().postFrameCallback(FrameCallback) 方法来注册和注销 FrameCallback 对象。 以下是一个示例代码: ```java public class MyView extends View implements Choreographer.FrameCallback { private Choreographer mChoreographer; public MyView(Context context) { super(context); mChoreographer = Choreographer.getInstance(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mChoreographer.postFrameCallback(this); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mChoreographer.removeFrameCallback(this); } @Override public void doFrame(long frameTimeNanos) { // 在这里实现 View 的刷新逻辑 // ... // 重新注册 FrameCallback 对象 mChoreographer.postFrameCallback(this); } } ``` 在上述代码中,我们首先创建了一个 MyView 类,它继承自 View 类,并实现了 Choreographer.FrameCallback 接口。在 MyView 中,我们在 onAttachedToWindow() 方法中注册了 FrameCallback 对象,并在 onDetachedFromWindow() 方法中注销了 FrameCallback 对象。在 doFrame() 方法中,我们实现了 View 的刷新逻辑,并在最后重新注册了 FrameCallback 对象以实现定时刷新的功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

newchenxf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值