文中所有源碼基于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,如果有直接返回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.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 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.cpp
void ProfileDataContainer::switchStorageToAshmem(int ashmemfd) {
int regionSize = ashmem_get_size_region(ashmemfd);
if (regionSize < static_cast<int>(sizeof(ProfileData))) {
reutrn;
}
// 1.創建ProfileData
ProfileData* 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. mergedata
newData->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 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這樣的方法,然而它<b>重寫了操作符-></b>所以真正的實現還是在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呈現模式分析 到底是什么,顯示在屏幕上那些柱狀圖又是何時將什么數據繪制上的?