博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
GraphicsStatsService之2 UI绘制的时间信息来源
阅读量:7298 次
发布时间:2019-06-30

本文共 14232 字,大约阅读时间需要 47 分钟。

文中所有源码基于Android8.0

用到的类:

GraphicsStatsService.java

ThreadedRenderer.java
android_view_ThreadedRenderer.cpp
RenderProxy.cpp
RenderTask.h/cpp
RenderThread.h/cpp
ProfileDataContainer.h/cpp
ConvasContext.h/cpp
JankTracker.h/cpp

1.共享内存何时创建

在 中提到,dump数据都是从ActiveBuffer这个类中的到的,其中包含了一个MemoryFile,下面是它的创建:

private ActiveBuffer fetchActiveBuffersLocked(IGraphicsStatsCallback token, int uid, int pid,            String packageName, int versionCode) throws RemoteException {    int size = mActive.size();    //1. 根据时间来判断,今天有没有创建buffer,如果有直接返回buffer    long today = normalizeDate(System.currentTimeMillis()).getTimeInMillis();    for (int i = 0; i < size; i++) {        ActiveBuffer buffer = mActive.get(i);        if (buffer.mPid == pid                && buffer.mUid == uid) {            // If the buffer is too old we remove it and return a new one            if (buffer.mInfo.startTime < today) {                buffer.binderDied();                break;            } else {                return buffer;            }        }    }    // 2.没找到buffer,创建一个    try {        ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName, versionCode);        mActive.add(buffers);        return buffers;    } catch (IOException ex) {        throw new RemoteException("Failed to allocate space");    }}复制代码

1.1 如果今天有合适的buffer,则不创建直接使用。超过了一天,则走它的进程死亡后的处理逻辑,然后走第2步,创建buffer。 从它的定时器也可以看出:

private Calendar normalizeDate(long timestamp) {    Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));    calendar.setTimeInMillis(timestamp);    //每天的0点    calendar.set(Calendar.HOUR_OF_DAY, 0);    calendar.set(Calendar.MINUTE, 0);    calendar.set(Calendar.SECOND, 0);    calendar.set(Calendar.MILLISECOND, 0);    return calendar;}private void scheduleRotateLocked() {    mRotateIsScheduled = true;    Calendar calendar = normalizeDate(System.currentTimeMillis());     //又添加了每个月的第一天    calendar.add(Calendar.DATE, 1);    mAlarmManager.setExact(AlarmManager.RTC, calendar.getTimeInMillis(), TAG, this::onAlarm,            mWriteOutHandler);}复制代码

每天的0点触发,证明有效期只有一天。

1.2 创建buffer,内部创建MemoryFile。

2. 如何与进程关联

共享内存创建后,要靠它的fd来读写数据,那么这个fd是怎么传出去的呢?

private ParcelFileDescriptor requestBufferForProcessLocked(IGraphicsStatsCallback token,            int uid, int pid, String packageName, int versionCode) throws RemoteException {        ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName, versionCode);        scheduleRotateLocked();        return getPfd(buffer.mProcessBuffer);    }private ParcelFileDescriptor getPfd(MemoryFile file) {        try {            ...            return new ParcelFileDescriptor(file.getFileDescriptor());        } catch (IOException ex) {            ...        }    }复制代码

包装成了一个ParcelFileDescriptor,由此可以看出,要进行Binder传输了,那么调用requestBufferForProcessLocked方法的地方,就是要创建它的地方。requestBufferForProcessLocked这个方法是IGraphicsStats.aidl生成的,说明要跨进程了,从它的名字上也可以看出端倪,为进程创建buffer。 追踪代码可以发现,在ThreadedRenderer.java中,其内部类 ProcessInitializer的init方法中进行调用了,而ThreadedRenderer是在ViewRootImpl.java中创建的,所以在创建Window时就会创建这个Buffer,如果这个进程已经有这个buffer了,则直接返回此buffer。

private void initGraphicsStats() {    try {        // 1. request buffer        IBinder binder = ServiceManager.getService("graphicsstats");        mGraphicsStatsService = IGraphicsStats.Stub.asInterface(binder);        requestBuffer();    } catch (Throwable t) {    }}private void requestBuffer() {    try {        final String pkg = mAppContext.getApplicationInfo().packageName;        // 2. 调用service的requestBufferForProcess方法        ParcelFileDescriptor pfd = mGraphicsStatsService                .requestBufferForProcess(pkg, mGraphicsStatsCallback);        nSetProcessStatsBuffer(pfd.getFd());        pfd.close();    } catch (Throwable t) {        Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t);    }}复制代码

2.1 得到service

2.2 调用请求buffer的方法,然后通过一个native方法将fd设置到了底层,最后将fd关闭了。为什么给关了呢,不是要往里写数据吗?接着往下看:

# android_view_ThreadedRenderer.cppstatic void android_view_ThreadedRenderer_setProcessStatsBuffer(JNIEnv* env, jobject clazz,        jint fd) {    RenderProxy::setProcessStatsBuffer(fd);}复制代码

直接调用了RenderProxy的方法,setProcessStatsBuffer。

CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) {    args->thread->globalProfileData().switchStorageToAshmem(args->fd);    close(args->fd);    return nullptr;}void RenderProxy::setProcessStatsBuffer(int fd) {    SETUP_TASK(setProcessStatsBuffer);    auto& rt = RenderThread::getInstance();    args->thread = &rt;    args->fd = dup(fd);    rt.queue(task);}复制代码

哦,执行了SETUP_TASK(setProcessStatsBuffer)这样一句,接着queue到RenderThread里面了。还有个CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd),这个是什么呢?

首先看CREATE_BRIDGE2,这是一个宏,如下:

// 将method和Args连接在一起#define ARGS(method) method ## Args#define CREATE_BRIDGE0(name) CREATE_BRIDGE(name,,,,,,,,)#define CREATE_BRIDGE1(name, a1) CREATE_BRIDGE(name, a1,,,,,,,)#define CREATE_BRIDGE2(name, a1, a2) CREATE_BRIDGE(name, a1,a2,,,,,,)...其他参数个数的宏#define CREATE_BRIDGE(name, a1, a2, a3, a4, a5, a6, a7, a8) \    typedef struct { \        a1; a2; a3; a4; a5; a6; a7; a8; \    } ARGS(name); \    static_assert(std::is_trivially_destructible
::value, \ "Error, ARGS must be trivially destructible!"); \ static void* Bridge_ ## name(ARGS(name)* args)复制代码

那么,我们的参数是:

name : setProcessStatsBuffer
a1 : RenderThread* thread
a2 : int fd
将我们的代入看下是什么样子呢?

typedef struct {
RenderThread* thread; int fd;} setProcessStatsBufferArgs;//省略assert这句static void* Bridge_setProcessStatsBuffer(setProcessStatsBufferArgs* args) { args->thread->globalProfileData().switchStorageToAshmem(args->fd); close(args->fd); return nullptr;}复制代码

原来是声明了一个函数,那么SETUP_TASK想必就是调用它了。

#define SETUP_TASK(method) \    ...省略判断    MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \    ARGS(method) *args = (ARGS(method) *) task->payload()复制代码

将我们的方法替换后得到下面的语句:

//定义函数指针typedef void* (*RunnableMethod)(void* data);void RenderProxy::setProcessStatsBuffer(int fd) {    MethodInvokeRenderTask* task =     new MethodInvokeRenderTask((RunnableMethod)Bridge_setProcessStatsBuffer);	setProcessStatsBufferArgs*args = (setProcessStatsBufferArgs*) task->payload()        auto& rt = RenderThread::getInstance();    args->thread = &rt;    args->fd = dup(fd);    rt.queue(task);}复制代码

将Bridge_setProcessStatsBuffer传给了MethodInvokeRenderTask,现在看下它的实现:

// Renderask.h#define METHOD_INVOKE_PAYLOAD_SIZE (8 * sizeof(void*))class MethodInvokeRenderTask : public RenderTask {public:    explicit MethodInvokeRenderTask(RunnableMethod method)        : mMethod(method), mReturnPtr(nullptr) {}	//1.返回了mData变量    void* payload() { return mData; }    void setReturnPtr(void** retptr) { mReturnPtr = retptr; }	    //2.执行了传进来的方法    virtual void run() override {        void* retval = mMethod(mData);        if (mReturnPtr) {            *mReturnPtr = retval;        }        // Commit suicide        delete this;    }private:    RunnableMethod mMethod;    char mData[METHOD_INVOKE_PAYLOAD_SIZE];    void** mReturnPtr;};复制代码

这里有两点

第一:payload()方法将mData返回给外面,并且在我们这个方法中强转成了setProcessStatsBufferArgs*,为什么就转换了呢? 我们看mData的size是METHOD_INVOKE_PAYLOAD_SIZE,也就是8个sizeof(void*)的大小,可以理解为8个sizeof(int*)的大小,64位的机子上就是8*8 = 64。
为什么是8呢?因为CREATE_BRIDGE这个宏最多支持8个参数。
第二:将参数传给Bridge_setProcessStatsBuffer,然后执行。

OK,现在回到我们的void RenderProxy::setProcessStatsBuffer(int fd)方法,将setProcessStatsBufferArgs*args填充成如下:

void RenderProxy::setProcessStatsBuffer(int fd) {	....       auto& rt = RenderThread::getInstance();    1 线程填充为 RenderThread::getInstance    args->thread = &rt;    2 复制了一个fd    args->fd = dup(fd);    rt.queue(task);}复制代码

原来是将这个task放到了RenderThread中去执行了,fd用dup系统调用复制了一个,这就理解了java层为何直接close掉了。

在RenderThread类中,将这个task执行,也就是我们的函数执行:

static void* Bridge_setProcessStatsBuffer(setProcessStatsBufferArgs* args) {	// 1 switch    args->thread->globalProfileData().switchStorageToAshmem(args->fd);    close(args->fd);    return nullptr;}复制代码

原来是RenderThread里面拿到globalProfileData(),是ProfileDataContainer的变量,然后执行switchStorageToAshmem(args->fd)。 这个函数的意思可以理解一下,switch to , 也就是说ProfileDataContainer这个变量可能一直有数据,现在将它的存储调整到了java曾创建的那个共享内存中。现在看下这个方法的实现:

// ProfileDataContainer.cppvoid ProfileDataContainer::switchStorageToAshmem(int ashmemfd) {    int regionSize = ashmem_get_size_region(ashmemfd);    if (regionSize < static_cast
(sizeof(ProfileData))) { reutrn; } // 1.创建ProfileData ProfileData* newData = reinterpret_cast
( mmap(NULL, sizeof(ProfileData), PROT_READ | PROT_WRITE, MAP_SHARED, ashmemfd, 0)); if (newData == MAP_FAILED) { int err = errno; ALOGW("Failed to move profile data to ashmem fd %d, error = %d", ashmemfd, err); return; } // 2. mergedata newData->mergeWith(*mData); freeData(); mData = newData; mIsMapped = true;}复制代码

关键有两点

第一 : map一块内存,然后创建ProfileData结构。在一文中提到,dump的数据是sizeof(ProfileData)的大小,这里就是答案了。
第二 : 之前猜测,为什么是数据switch to Ashmen,这个merge应该可以解释,之前的确是存在数据的。

至此我们在java层创建的fd就跟底层的ProfileData绑定在一起了,数据是何时存储到里面的呢? 首先看在RenderThread类里创建的 ProfileDataContainer 这个变量,谁拿走去填充数据了呢? 追踪源码,可定位到:

CanvasContext::CanvasContext(RenderThread& thread, bool translucent,        RenderNode* rootRenderNode, IContextFactory* contextFactory,        std::unique_ptr
renderPipeline) : mRenderThread(thread) , mOpaque(!translucent) , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord())) , mJankTracker(&thread.globalProfileData(), thread.mainDisplayInfo()) , mProfiler(mJankTracker.frames()) , mContentDrawBounds(0, 0, 0, 0) , mRenderPipeline(std::move(renderPipeline)) {...}复制代码

可以看到,原来是JankTracker创建时,拿走了它的引用。那么接着看JankTracker这个类的构造函数:

JankTracker::JankTracker(ProfileDataContainer* globalData, const DisplayInfo& displayInfo) {    mGlobalData = globalData;    nsecs_t frameIntervalNanos = static_cast
(1_s / displayInfo.fps); setFrameInterval(frameIntervalNanos);}复制代码

哦,原来是付给自己的成员变量,那么它是什么时侯将数据写入的呢?

找到下面的函数:

void JankTracker::finishFrame(const FrameInfo& frame) {    // Fast-path for jank-free frames    int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted);    ...先省略	    //1 记录绘制时间    mData->reportFrame(totalDuration);    (*mGlobalData)->reportFrame(totalDuration);	    //2 这一帧绘制时间正常    // Keep the fast path as fast as possible.    if (CC_LIKELY(totalDuration < mFrameInterval)) {        return;    }	    //3 有跳帧    mData->reportJank();    (*mGlobalData)->reportJank();	        for (int i = 0; i < NUM_BUCKETS; i++) {        int64_t delta = frame.duration(COMPARISONS[i].start, COMPARISONS[i].end);        if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) {            mData->reportJankType((JankType) i);            (*mGlobalData)->reportJankType((JankType) i);        }    }}复制代码

我们关注 mGlobalData 这个变量,刚才是这个变量接收的,这个函数主要有三点

第一 : 记录绘制时间,然后存储在mGlobalData中,即ProfileDataContainer这个结构里。我们看下这个结构:

class ProfileDataContainer {
...public: ... void switchStorageToAshmem(int ashmemfd); ProfileData* get() { return mData; } ProfileData* operator->() { return mData; }private: void freeData(); ProfileData* mData = new ProfileData; bool mIsMapped = false;};复制代码

发现它并无reportFrame这样的方法,然而它重写了操作符->所以真正的实现还是在ProfileData这个结构里:

// ProfileData.cppvoid ProfileData::reportFrame(int64_t duration) {    mTotalFrameCount++;    uint32_t framebucket = frameCountIndexForFrameTime(duration);    if (framebucket <= mFrameCounts.size()) {        mFrameCounts[framebucket]++;    } else {        framebucket = (ns2ms(duration) - kSlowFrameBucketStartMs) / kSlowFrameBucketIntervalMs;        framebucket = std::min(framebucket, static_cast
(mSlowFrameCounts.size() - 1)); mSlowFrameCounts[framebucket]++; }}复制代码

终于,原来数据的记录在这,这个方法记录着总帧数和哪个柱状图的数据。

第二 : 当绘制一帧的时间小于mFrameInterval,就直接返回了,mFrameInterval的值一般是 1/60 ms,也就是平时说的16ms。

第三 : 当一帧的时间大于正常值,就属于Jank了,那么就按jank记录下来。同第一步的分析。

到此,我们看到了数据是怎么存储的,那么是什么时侯调用这个存储方法呢?

继续追踪源码,发现CanvasContext的draw()方法,每绘制一帧,就调用一下:

void CanvasContext::draw() {	...省略    bool didSwap = mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo,            &requireSwap);    mIsDirty = false;	...省略    mJankTracker.finishFrame(*mCurrentFrameInfo);}复制代码

最后,可以看出每绘制一帧ui,都要记录一下,这一帧用了多久,是否jank了,原因是什么等数据。数据的写入流程分析完成。

3.数据存哪里了

前面说道,java层有定时器并且还有binder死亡的监听,然后保存数据到本地. 从进程死亡入手,毕竟杀app是很正常的事: 在GraphicsStatsService.java中,ActiveBuffer中有一个IGraphicsStatsCallback参数,是app进程的里binder对象, 然后监听了它的死亡,当它死亡时,会走binderDied回调方法,进一步处理后,调用

private static native void nSaveBuffer(String path, String packageName, int versionCode,            long startTime, long endTime, byte[] data);复制代码

参数path是什么呢?拼接字符串可以看出是 /data/system/graphicsstats/时间/包名/版本号/total这样一个路径,比如 在手机上看到: /data/system/graphicsstats/1531440000000/com.sdu.didi.psnger,然后继续往下看:

// com_android_server_GraphicsStatsService.cppstatic void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,        jint versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {    ScopedByteArrayRO buffer(env, jdata);	...省略    const ProfileData* data = reinterpret_cast
(buffer.get()); GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);}复制代码

可以看数据转换成ProfileData后,直接调用了GraphicsStatsService.cpp的方法:

void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,        int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {    service::GraphicsStatsProto statsProto;    //1 节写之前存在的,并与要写入的合并    if (!parseFromFile(path, &statsProto)) {        statsProto.Clear();    }    if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {        return;    }  	//2 按protobuf写入    int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660);    int wrote = write(outFd, &sCurrentFileVersion, sHeaderSize);     {        FileOutputStreamLite output(outFd);        bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush();     close(outFd);}复制代码

主要有两点,从文件里按protobuf解析出来,然后在合并,然后将新数据写回去。

注:protobuf 是google的数据序列化格式,主要优点是轻便,高效。用protobuf的语言描述后,可以用工具直接转换成c++,java,python等的接口,很方便使用

下一节讨论 设置中的 GPU呈现模式分析 到底是什么,显示在屏幕上那些柱状图又是何时将什么数据绘制上的?

转载地址:http://hmfnm.baihongyu.com/

你可能感兴趣的文章
代码片收集
查看>>
网站备案与备案注销
查看>>
书单丨打开投资理财之路,从这25本书开始
查看>>
Less 创建css3动画@keyframes函数
查看>>
.NET Framework 4 与 .NET Framework 4 Client Profile的区别与联系
查看>>
Que pensez-vous de air jordan pas cher
查看>>
SQL Server 2008创建定期自动备份任务(转)
查看>>
SimpleDateFormat
查看>>
epoll_wait会被系统中断唤醒
查看>>
Java设计模式-代理模式
查看>>
Android--sharepreference总结
查看>>
在博客园已经一年多时间了,今天开通博客了!
查看>>
给定矩阵行数和矩阵列数,顺时针打印矩阵(从0开始)
查看>>
个人阅读作业week7
查看>>
Java数据类型(2)------自动封装拆箱
查看>>
java基本语法
查看>>
oracle多表关联多字段update
查看>>
欧拉函数
查看>>
AngularJS源码解析4:Parse解析器的详解
查看>>
HTTP错误 404.17 - Not Found" IIS 7.5 请求的内容似乎是脚本,因而将无法由静态文件处理程序来处理...
查看>>