文中所有源码基于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.共享内存何时创建

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


private ActiveBuffer fetchActiveBuffersLocked(IGraphicsStatsCallback token, int uid, int pid,String packageName, int versionCode) throws RemoteException {int size = mActive.size();//1. 根据时间来判断,今天有没有创建buffer,如果有直接返回bufferlong 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 oneif (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 bufferIBinder 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.cpp
static 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<ARGS(name)>::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 suicidedelete 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::getInstanceargs->thread = &rt;2 复制了一个fdargs->fd = dup(fd);rt.queue(task);
}
复制代码

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

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

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

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

// ProfileDataContainer.cpp
void ProfileDataContainer::switchStorageToAshmem(int ashmemfd) {int regionSize = ashmem_get_size_region(ashmemfd);if (regionSize < static_cast<int>(sizeof(ProfileData))) {reutrn;}// 1.创建ProfileDataProfileData* newData = reinterpret_cast<ProfileData*>(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. mergedatanewData->mergeWith(*mData);freeData();mData = newData;mIsMapped = true;
}复制代码

关键有两点
第一 : map一块内存,然后创建ProfileData结构。在GraphicsStatsService之1-dump数据一文中提到,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<IRenderPipeline> 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<nsecs_t>(1_s / displayInfo.fps);setFrameInterval(frameIntervalNanos);
}复制代码

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

void JankTracker::finishFrame(const FrameInfo& frame) {// Fast-path for jank-free framesint64_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.cpp
void 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<uint32_t>(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.cpp
static 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<const ProfileData*>(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呈现模式分析 到底是什么,显示在屏幕上那些柱状图又是何时将什么数据绘制上的?

GraphicsStatsService之2 UI绘制的时间信息来源相关推荐

  1. Android源码解析:UI绘制流程之控件绘制

    带着问题看源码 再接再厉,我们来分析UI绘制流程最后一步绘制流程 入口ViewRootImpl.performDraw()方法 private void performDraw() {//...try ...

  2. 【Android 应用开发】UI绘制流程 ( 生命周期机制 | 布局加载机制 | UI 绘制流程 | 布局测量 | 布局摆放 | 组件绘制 | 瀑布流布局案例 )

    文章目录 一. 博客相关资料 及 下载地址 1. 代码查看方法 ( ① 直接获取代码 | ② JAR 包替换 ) 2. 本博客涉及到的源码查看说明 二. Activity 生命周期回调机制 1. An ...

  3. Win10 UWP开发中的重复性静态UI绘制小技巧 1

    Win10 UWP开发中的重复性静态UI绘制小技巧 1 原文:Win10 UWP开发中的重复性静态UI绘制小技巧 1 介绍 在Windows 10 UWP界面实现的过程中,有时会遇到一些重复性的.静态 ...

  4. Android UI绘制流程分析(三)measure

    源码版本Android 6.0 请参阅:http://androidxref.com/6.0.1_r10 本文目的是分析从Activity启动到走完绘制流程并显示在界面上的过程,在源码展示阶段为了使跟 ...

  5. Framework学习之路(一)—— UI绘制深入源码分析

    Framework学习之路(一)-- UI绘制深入源码分析 本篇为笔者对Android SDK 33版本的UI绘制入口进行追踪的过程,主要作笔记作用.由于笔者经验尚浅,水平也有限,所以会存在很多不足的 ...

  6. 文件时间信息在测试中的应用

    1 简介 文件时间信息在测试中也有妙用- 通过记录模块运行前后的文件时间信息来识别运行前后发生变化的文件,从而识别模块运行前后的新增文件.删除的文件和内容发生变化的文件. 利用识别出来的发生变化的文件 ...

  7. R语言ggplot2可视化:使用ggplot2绘制按时间顺序排列的时间线图(chronological timeline plot)

    R语言ggplot2可视化:使用ggplot2绘制按时间顺序排列的时间线图(chronological timeline plot) 目录 R语言ggplot

  8. Pandas把dataframe中的整数数值(integer)转化为时间(日期、时间)信息实战

    Pandas把dataframe中的整数数值(integer)转化为时间(日期.时间)信息实战 目录 Pandas把dataframe中的整数数值转化为时间(日期.时间)信息实战

  9. java信息格式化,Java如何格式化包含时间信息的消息?

    在这里,我们演示如何使用java.text.MessageFormat该类来格式化包含时间信息的消息.package org.nhooo.example.text; import java.util. ...

最新文章

  1. JavaScript Collection
  2. “全宇宙首个”用中文编写的操作系统!作者还自创了甲、乙、丙编程语言?...
  3. 网页快照是什么?对SEO优化有什么作用?
  4. Matplotlib实例教程(十二)箱形图
  5. python的concat用法_python的concat等多种用法详解
  6. 电脑模拟器哪个好_电脑系统杀毒软件哪个好测评
  7. linux无效内存访问,x86_64 Linux 3.0:无效的内存地址
  8. java 僵尸进程_Linux 僵尸进程
  9. jetson tx2上运行mobilenet-ssd的坑:interrupted by signal 9: SIGKILL
  10. 反序列化对象列表发生异常_面试官:你知道Java对象的序列化与反序列化背后的原理吗?...
  11. c++拷贝构造函数(深拷贝和浅拷贝)
  12. 论文审稿回复LaTeX模板
  13. masm5.0与masm32
  14. UEFI shell控制台向.efi文件传入参数--通过protocol实现
  15. Qgis 3.18 的安装步骤
  16. 三宝小精灵机器人_“三宝”机器人
  17. 关于芯片最高工作频率的计算
  18. 栈溢出 __stack_chk_fail
  19. OpenMMLab全景图
  20. 欢迎来到魔法的未来~

热门文章

  1. BZOJ2388: 旅行规划
  2. Js 对象添加属性
  3. Linux ISATAP配置
  4. Windows Phone 7 文件下载进度和速度显示
  5. JMETER 主界面工具栏介绍
  6. python将字符串s和换行符写入文件fp_Python 文件操作
  7. 2020年Web前端技术的三大趋势(干货)
  8. mipi协议_Cadence发布业界首款面向多协议PHY的验证IP产品
  9. 计算机应用技术专业标志,计算机应用技术论文
  10. linux重启python服务_如何将python脚本作为linux服务启动