GraphicsStatsService是Android M(6.0)之後Google加入的用於收集彙總Android系統的渲染剖面數據(profile data),主要途徑是經過容許渲染線程請求匿名共享存儲緩衝(ashmem buffer)來存放它們的統計信息來實現的。這篇文章旨在分析GraphicsStatsService的工做流程和這些統計信息的前因後果。css
首先來看下GraphicsStatsService都收集了哪些信息。經過adb shell dumpsys graphicsstats 能夠輸出GraphicsStatsService收集的信息,如下是在小米手機上執行該命令時輸出的信息:java
Package: com.android.systemui
Stats since: 23494814317ns
Total frames rendered: 132008
Janky frames: 8913 (6.75%)
90th percentile: 12ms
95th percentile: 19ms
99th percentile: 38ms
Number Missed Vsync: 1954
Number High input latency: 279
Number Slow UI thread: 2704
Number Slow bitmap uploads: 454
Number Slow issue draw commands: 5408android
Package: com.miui.systemAdSolution
Stats since: 234903483403ns
Total frames rendered: 44
Janky frames: 19 (43.18%)
90th percentile: 53ms
95th percentile: 57ms
99th percentile: 113ms
Number Missed Vsync: 3
Number High input latency: 2
Number Slow UI thread: 6
Number Slow bitmap uploads: 11
Number Slow issue draw commands: 10shell
Package: android
Stats since: 272814918805ns
Total frames rendered: 369
Janky frames: 16 (4.34%)
90th percentile: 13ms
95th percentile: 15ms
99th percentile: 31ms
Number Missed Vsync: 2
Number High input latency: 0
Number Slow UI thread: 9
Number Slow bitmap uploads: 2
Number Slow issue draw commands: 11數組
Package: com.miui.personalassistant
Stats since: 295433832807ns
Total frames rendered: 8
Janky frames: 5 (62.50%)
90th percentile: 85ms
95th percentile: 85ms
99th percentile: 85ms
Number Missed Vsync: 3
Number High input latency: 1
Number Slow UI thread: 5
Number Slow bitmap uploads: 0
Number Slow issue draw commands: 1數據結構
...........app
能夠看到輸出不少應用的渲染信息,以包名做爲區分。其中Stats since表示該應用的統計信息是從系統開機多長時間(納秒)後開始統計的,Total frames表示一共繪製了多少幀,Janky frames表示有多少幀是卡頓的,90th percentile、95th percentile、99th percentile分別表示90%、95%、99%的幀是在多少毫秒內完成的;最後的五個指標表示卡頓的具體緣由及卡頓的幀數(一個幀可能有多個卡頓的緣由),具體的解釋能夠看第5章的第4小結。這些信息能夠幫助應用開發者分析其應用的卡頓狀況,也能夠幫助系統開發瞭解整個系統的性能狀況。ide
那麼這GraphicsStatsService服務運做機制是如何的呢?這些統計數據都是怎麼收集的?下面讓咱們一步步來探索,首先看一下GraphicsStatsService都長啥樣。函數
GraphicsStatsService 類文件位於frameworks/base/services/core/java/com/android/server /GraphicsStatsService.java,系統不少核心的服務都位於該目錄下。post
該類實現了IGraphicsStats接口,本質上是一個binder,IGraphicsStats接口經過AIDL實現,相應的文件是frameworks/base/core/java/android/view/IGraphicsStats.aidl。裏面只定義了一個方法:
interface IGraphicsStats { ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token); }
所以,requestBufferForProcess方法也就是GraphicsStatsService的核心方法之一,顧名思義,該方法是給由packageName指定的進程分配buffer,並返回指向該buffer的文件描述符,具體代碼很少,列舉在下面:
@Override public ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token) throws RemoteException { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); ParcelFileDescriptor pfd = null; long callingIdentity = Binder.clearCallingIdentity(); try { if (!isValid(uid, packageName)) { throw new RemoteException("Invalid package name"); } synchronized (mLock) { pfd = requestBufferForProcessLocked(token, uid, pid, packageName); } } finally { Binder.restoreCallingIdentity(callingIdentity); } return pfd; }
在這段代碼中,首先檢驗包名的合法性,這個主要是經過比較token中的getCallingUid和packageName對應的uid是否相等來實現的,因此咱們要傳兩個相關聯的packageName和token進來。
隨後,若是合法性檢驗經過,則調用requestBufferForProcessLocked分配buffer,這個方法又調用了fetchActiveBuffersLocked
private ActiveBuffer fetchActiveBuffersLocked(IBinder token, int uid, int pid, String packageName) throws RemoteException { int size = mActive.size(); for (int i = 0; i < size; i++) { ActiveBuffer buffers = mActive.get(i); if (buffers.mPid == pid && buffers.mUid == uid) { return buffers; } } // Didn't find one, need to create it try { ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName); mActive.add(buffers); return buffers; } catch (IOException ex) { throw new RemoteException("Failed to allocate space"); } }
這段代碼中,首先根據uid和pid嘗試從mActive取buffer,若是取到則直接返回,不然新建立一個ActiveBuffer加入mActive並返回引用。能夠看到系統所有分配的buffer是經過mActive來統一管理的,它是一個ArrayList<ActiveBuffer>,而ActiveBuffer則是核心類,它是GraphicsStatsService的一個final內部類,代碼很少,具體以下:
private final class ActiveBuffer implements DeathRecipient { final int mUid; final int mPid; final String mPackageName; final IBinder mToken; MemoryFile mProcessBuffer; HistoricalData mPreviousData; ActiveBuffer(IBinder token, int uid, int pid, String packageName) throws RemoteException, IOException { mUid = uid; mPid = pid; mPackageName = packageName; mToken = token; mToken.linkToDeath(this, 0); mProcessBuffer = new MemoryFile("GFXStats-" + uid, ASHMEM_SIZE); mPreviousData = removeHistoricalDataLocked(mUid, mPackageName); if (mPreviousData != null) { mProcessBuffer.writeBytes(mPreviousData.mBuffer, 0, 0, ASHMEM_SIZE); } } @Override public void binderDied() { mToken.unlinkToDeath(this, 0); processDied(this); } void closeAllBuffers() { if (mProcessBuffer != null) { mProcessBuffer.close(); mProcessBuffer = null; } } }
在構造方法中,咱們能夠清晰地看到,buffer最終是經過匿名共享內存的一個形式MemoryFile來實現的,而底層是經過JNI來進行讀寫的。另外注意到,該類實現了DeathRecipient接口,意思是死亡收件人,裏面只有一個方法就是binderDied()。在構造方法中,調用了mToken.linkToDeath(this, 0)將本身註冊成爲死亡收件人,當持有該binder(mToken)的進程死亡的時候,就會回調binderDied(),而後在processDied()中作清理工做,自此爲進程請求buffer的Java層過程已經完畢。
那麼GraphicsStatsService是在哪裏輸出統計信息的呢,那就得看在GraphicsStatsService中的另一個關鍵方法,就是dump(FileDescriptor fd, PrintWriter fout, String[] args),該方法重寫了Binder的同名方法,專門用於輸出渲染的統計信息,以下:
@Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); synchronized (mLock) { for (int i = 0; i < mActive.size(); i++) { final ActiveBuffer buffer = mActive.get(i); fout.print("Package: "); fout.print(buffer.mPackageName); fout.flush(); try { buffer.mProcessBuffer.readBytes(mTempBuffer, 0, 0, ASHMEM_SIZE); ThreadedRenderer.dumpProfileData(mTempBuffer, fd); } catch (IOException e) { fout.println("Failed to dump"); } fout.println(); } for (HistoricalData buffer : mHistoricalLog) { if (buffer == null) continue; fout.print("Package: "); fout.print(buffer.mPackageName); fout.flush(); ThreadedRenderer.dumpProfileData(buffer.mBuffer, fd); fout.println(); } } }
這個方法首先檢查相應的權限(dump數據也是要權限的啊),而後是兩個for循環。
第一個循環遍歷mActive,輸出包名,而後調用buffer.mProcessBuffer.readBytes(mTempBuffer, 0, 0, ASHMEM_SIZE),讀取ASHMEM_SIZE到mTempBuffer,再而後就交給ThreadedRenderer的dumpProfileData(mTempBuffer, fd)去輸出了,GraphicsStatsService本身並無幹什麼事情,這其實也是正常的,畢竟裏面的mTempBuffer的數據格式GraphicsStatsService是不知道的。咱們首先想想,渲染統計信息究竟是誰放進去的?用腳趾頭想想就能夠知道,那確定是負責渲染的那個傢伙啊,它本身的事情它本身最清楚。ThreadedRenderer的這名字看起來就是渲染線程。因此具體的統計信息的輸出仍是得由它老人家來負責(並且咱們猜想也是它放進去的,具體咱們後面再看)。
後面還有一個for,用於輸出HistoricalData(歷史數據嘛,這名字仍是很容易懂得),過程其實和上面差很少。
而後這個dump是如何被調用的呢??回想一下咱們的命令adb shell dumpsys graphicsstats,咱們能夠發現跟dumpsys有關,其執行文件位於系統目錄下的/system/bin/dumpsys,源文件位於frameworks/native/cmds/dumpsys/dumpsys.cpp,關鍵代碼以下
int main(int argc, char* const argv[]) { signal(SIGPIPE, SIG_IGN); sp<IServiceManager> sm = defaultServiceManager(); fflush(stdout); if (sm == NULL) { ALOGE("Unable to get default service manager!"); aerr << "dumpsys: Unable to get default service manager!" << endl; return 20; } Vector<String16> services; Vector<String16> args; bool showListOnly = false; ... services.add(String16(argv[1])); for (int i=2; i<argc; i++) { args.add(String16(argv[i])); } const size_t N = services.size(); ... for (size_t i=0; i<N; i++) { sp<IBinder> service = sm->checkService(services[i]); if (service != NULL) { ... int err = service->dump(STDOUT_FILENO, args); if (err != 0) { aerr << "Error dumping service info: (" << strerror(err) << ") " << services[i] << endl; } } else { aerr << "Can't find service: " << services[i] << endl; } } return 0; }
首先經過defaultServiceManager取得ServiceManager,經過它能夠取得全部的系統服務,而後咱們輸入的graphicsstats會被輸入到services裏面,sm->checkService(services[i])經過名字取得對應service的引用,最後由service->dump(STDOUT_FILENO, args)完成信息的輸出。這個掉用就是調用Binder裏面的 dump(FileDescriptor fd, String[] args) ,最終調用上面所說的dump(FileDescriptor fd, PrintWriter fout, String[] args)。
如此,GraphicsStatsService類中的代碼已經分析完了,這只是整個流程的開始,下面咱們繼續分析。
首先,GraphicsStatsService做爲系統服務,確定是在實在SystemServer中被啓動的。具體代碼是在/homeframeworks/base/services/java/com/android/server/SystemServer.java的startOtherServices()中,以下:
if (!disableNonCoreServices) { ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE, new GraphicsStatsService(context)); }
在這裏咱們能夠看到,GraphicsStatsService並非核心服務,若是disableNonCoreServices爲true,那麼它將不被啓動。這樣,服務在開機的時候已經起動了,那麼requestBufferForProcess何時調用呢?
在ThreadedRenderer的內部靜態類ProcessInitializer中,有個initGraphicsStats(Context context, long renderProxy) 方法:
private static void initGraphicsStats(Context context, long renderProxy) { try { IBinder binder = ServiceManager.getService("graphicsstats"); if (binder == null) return; IGraphicsStats graphicsStatsService = IGraphicsStats.Stub .asInterface(binder); sProcToken = new Binder(); final String pkg = context.getApplicationInfo().packageName; ParcelFileDescriptor pfd = graphicsStatsService. requestBufferForProcess(pkg, sProcToken); nSetProcessStatsBuffer(renderProxy, pfd.getFd()); pfd.close(); } catch (Throwable t) { Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t); } }
咱們能夠看到,這裏一樣是經過ServiceManager取得了graphicsStatsService,而後調用requestBufferForProcess爲進程分配buffer並返回文件描述符pfd,而後經過本地方法nSetProcessStatsBuffer(renderProxy, pfd.getFd()) 將渲染的代理類與文件描述符關聯。好,到此爲止,graphicsStatsService的流程已經完了,下面咱們重點關注Native層渲染統計信息的收集和輸出。先來看簡單的,輸出方面,瞭解其數據結構,再看收集。
前面咱們分析java層面的統計信息輸出,分析到了ThreadedRenderer的dumpProfileData(mTempBuffer, fd),下面咱們繼續分析。該方法直接調用了native方法nDumpProfileData,jni層對應的文件是/frameworks/base/core/jni/android_view_ThreadedRenderer.cpp,對應的方法以下:
static void android_view_ThreadedRenderer_dumpProfileData(JNIEnv* env, jobject clazz, jbyteArray jdata, jobject javaFileDescriptor) { int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor); ScopedByteArrayRO buffer(env, jdata); if (buffer.get()) { JankTracker::dumpBuffer(buffer.get(), buffer.size(), fd); } }
該方法作了一些轉換,就調用了 JankTracker的dumpBuffer(buffer.get(), buffer.size(), fd),對應的文件是/frameworks/base/libs/hwui/JankTracker.cpp。hwui意思是hardware ui,跟圖像渲染的硬件加速相關,而jank tracker的意思是卡頓追蹤,一看名字就知道咱們找對了。相關的方法以下:
void JankTracker::dumpBuffer(const void* buffer, size_t bufsize, int fd) { if (bufsize < sizeof(ProfileData)) { return; } const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer); dumpData(data, fd); } void JankTracker::dumpData(const ProfileData* data, int fd) { dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount); dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount, (float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f); dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90)); dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95)); dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99)); for (int i = 0; i < NUM_BUCKETS; i++) { dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]); } dprintf(fd, "\n"); }
能夠看到,dumpData最終完成的最後的輸出,相關的結構體有JANK_TYPE_NAMES和ProfileData。JANK_TYPE_NAMES其實就是一個字符常量數組,裏面存了卡頓的類型,定義在文件的前頭:
static const char* JANK_TYPE_NAMES[] = { "Missed Vsync", "High input latency", "Slow UI thread", "Slow bitmap uploads", "Slow issue draw commands", };
ProfileData則是一個定義在JankTracer.h中的結構體:
struct ProfileData { uint32_t jankTypeCounts [NUM_BUCKETS]; uint32_t frameCounts [57] ; uint32_t totalFrameCount; uint32_t jankFrameCount; };
看起來也沒啥特別的,如今的關鍵就是data中的數據是何時誰填進去的。
看到如今,是否是有點暈了,哈哈,還記得data是從哪裏來的嗎?首先,data來自於buffer,它是在dumpBuffer中由buffer指針強制轉換而來,而buffer則是層層傳下來的,最終的源頭是requestBufferForProcess方法分配的buffer!
如此一來,內存分配的前因後果已經解決了。鍋已經造好了,那麼是誰往裏面放東西的呢?那就取決於誰調用了GraphicsStatsService的requestBufferForProcess。注意到requestBufferForProcess是一個由AIDL定義的接口,調用者確定使用了跨進程的方法調用了它,那麼去哪裏找這些調用呢。別忘了,在第3小節中,咱們分析到ThreadedRenderer的initGraphicsStats調用了requestBufferForProcess,而後經過本地方法nSetProcessStatsBuffer(renderProxy, pfd.getFd()) 將渲染的代理類與文件描述符關聯。根據對代理模式的最基本的瞭解,真正進行渲染工做的應該是這個代理類RenderProxy,因此咱們將追蹤的目標轉移到它身上。
先讓咱們看看nSetProcessStatsBuffer(),其定義在frameworks/base/libs/hwui/renderthread/RenderProxy.cpp中,
CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) { args->thread->jankTracker().switchStorageToAshmem(args->fd); close(args->fd); return nullptr; } void RenderProxy::setProcessStatsBuffer(int fd) { SETUP_TASK(setProcessStatsBuffer); args->thread = &mRenderThread; args->fd = dup(fd); post(task); }
CREATE_BRIDGE2和SETUP_TASK都是宏定義,挺複雜的,這裏就不貼這兩個宏定義的代碼了,這裏直接給出展開後的結構,有興趣的朋友能夠去本身研究研究。
typedef struct { RenderThread* thread, int fd } setProcessStatsBufferArgs; static void* Bridge_setProcessStatsBuffer(setProcessStatsBufferArgs* args){ args->thread->jankTracker().switchStorageToAshmem(args->fd); close(args->fd); return nullptr; } void RenderProxy::setProcessStatsBuffer(int fd) { MethodInvokeRenderTask* task = new MethodInvokeRenderTask( (RunnableMethod) Bridge_setProcessStatsBuffer); setProcessStatsBufferArgs *args = (setProcessStatsBufferArgs *) task->payload(); args->thread = &mRenderThread; args->fd = dup(fd); post(task); }
能夠看到,第一個宏定義了一個結構setProcessStatsBufferArgs和一個方法Bridge_setProcessStatsBuffer,第二個宏定義了nSetProcessStatsBuffer()函數自己,當nSetProcessStatsBuffer()被調用時,就會新建一個MethodInvokeRenderTask,並把Bridge_setProcessStatsBuffer做爲回調函數,而後爲thread賦值&mRenderThread,爲fd複製一個fd,最後提交新建的任務。咱們來看看MethodInvokeRenderTask的定義,frameworks/base/libs/hwui/renderthread/RenderTask.h中:
typedef void* (*RunnableMethod)(void* data); class MethodInvokeRenderTask : public RenderTask { public: MethodInvokeRenderTask(RunnableMethod method) : mMethod(method), mReturnPtr(nullptr) {} void* payload() { return mData; } void setReturnPtr(void** retptr) { mReturnPtr = retptr; } 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; };
MethodInvokeRenderTask繼承於RenderTask,在構造函數中,用method初始化mMethod,用nullptr初始化mReturnPtr。函數payload()直接返回mData,實際上是一個字符數組mData[METHOD_INVOKE_PAYLOAD_SIZE]。任務被執行的時候,就是執行虛函數run(),注意到有一句mMethod(mData),這裏調用傳進來的方法mMethod,其實就是Bridge_setProcessStatsBuffer,實參是mData,而mData被轉換成了(setProcessStatsBufferArgs *),因而成功調用了Bridge_setProcessStatsBuffer(setProcessStatsBufferArgs *),最後的功能由args->thread->jankTracker(). switchStorageToAshmem(args->fd)完成。
首先咱們看下switchStorageToAshmem這個函數,位於frameworks/base/libs/hwui/JankTracker.cpp:
void JankTracker::switchStorageToAshmem(int ashmemfd) { ... 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; } ... if (newData->totalFrameCount > (1 << 24)) { divider = 4; } for (size_t i = 0; i <(sizeof(mData->jankTypeCounts) / sizeof(mData->jankTypeCounts[0])); i++) { newData->jankTypeCounts[i] >>= divider; newData->jankTypeCounts[i] += mData->jankTypeCounts[i]; } for (size_t i = 0; i <(sizeof(mData->frameCounts) / sizeof(mData->frameCounts[0])); i++) { newData->frameCounts[i] >>= divider; newData->frameCounts[i] += mData->frameCounts[i]; } newData->jankFrameCount >>= divider; newData->jankFrameCount += mData->jankFrameCount; newData->totalFrameCount >>= divider; newData->totalFrameCount += mData->totalFrameCount; if (newData->statStartTime > mData->statStartTime || newData->statStartTime == 0){ newData->statStartTime = mData->statStartTime; } freeData(); mData = newData; mIsMapped = true; }
這函數主要是完成線程私有內存在共享內存的映射。首先使用 mmap(NULL, sizeof(ProfileData), PROT_READ | PROT_WRITE,MAP_SHARED, ashmemfd, 0)申請了一個共享newData,而後把mData的數據映射進去,最後把mData指向這塊內存完成映射,並mIsMapped置true。至此,爲進程新建儲存渲染統計信息的buffer的流程就完全走完啦。
而args->thread的值是mRenderThread,其實就是被代理的RenderThread,調用jankTracker得到保存其保存的對jankTracker的引用。那麼這個jankTraker是在哪裏被初始化的呢?答案就在/frameworks/base/libs/hwui/renderthread/RenderThread.cpp中:
void RenderThread::initThreadLocals() { sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay( ISurfaceComposer::eDisplayIdMain)); status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &mDisplayInfo); LOG_ALWAYS_FATAL_IF(status, "Failed to get display info\n"); nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1000000000 / mDisplayInfo.fps); mTimeLord.setFrameInterval(frameIntervalNanos); initializeDisplayEventReceiver(); mEglManager = new EglManager(*this); mRenderState = new RenderState(*this); mJankTracker = new JankTracker(frameIntervalNanos); }
這個函數首先取得屏幕的顯示參數mDisplayInfo,而後利用mDisplayInfo.fps去初始化JankTracker。
JankTracker的代碼位於frameworks/base/libs/hwui/JankTracker.cpp:
JankTracker::JankTracker(nsecs_t frameIntervalNanos) { // By default this will use malloc memory. It may be moved later to ashmem // if there is shared space for it and a request comes in to do that. mData = new ProfileData; reset(); setFrameInterval(frameIntervalNanos); } ... void JankTracker::setFrameInterval(nsecs_t frameInterval) { mFrameInterval = frameInterval; mThresholds[kMissedVsync] = 1; /* * Due to interpolation and sample rate differences between the touch * panel and the display (example, 85hz touch panel driving a 60hz display) * we call high latency 1.5 * frameinterval * * NOTE: Be careful when tuning this! A theoretical 1,000hz touch panel * on a 60hz display will show kOldestInputEvent - kIntendedVsync of being 15ms * Thus this must always be larger than frameInterval, or it will fail */ mThresholds[kHighInputLatency] = static_cast<int64_t>(1.5 * frameInterval); // Note that these do not add up to 1. This is intentional. It's to deal // with variance in values, and should be sort of an upper-bound on what // is reasonable to expect. mThresholds[kSlowUI] = static_cast<int64_t>(.5 * frameInterval); mThresholds[kSlowSync] = static_cast<int64_t>(.2 * frameInterval); mThresholds[kSlowRT] = static_cast<int64_t>(.75 * frameInterval); }
構造函數首先new 一個ProfileData,而後將其重置,最後把參數傳遞給setFrameInterval並調用。
而在setFrameInterval中咱們彷佛看到了一些熟悉的東西,五個閾值:kMissedVsync,kHighInputLatency,kSlowUI,kSlowSync和kSlowRT,kMissedVsync固定爲1,而其餘幾個分別是frameInterval的1.5倍,0.5倍,0.2倍,0.75倍,因爲傳進來的frameIntervalNanos的值爲1000000000 / mDisplayInfo.fps,單位是納秒,因此mDisplayInfo.fps的值通常是60,因此frameInterval的值通常爲16.6ms,後面的幾個閾值分別是kHighInputLatency = 25ms,kSlowUI = 8.3ms,kSlowSync = 3.3ms和kSlowRT = 12.5ms。這幾個值應該是用來衡量一個幀不一樣階段的渲染時間性能,具體有什麼意義咱們後面再看。
從前面分析中,咱們能夠知道,mData就是存放渲染統計信息的數據結構,下面咱們看看JankTracker中對mData的操做。操做主要有三個:addFrame、reset和freeData,其中reset和freeData分別用於重置和清除數據,比較重要的是addFrame,用於添加一幀的渲染信息:
void JankTracker::addFrame(const FrameInfo& frame) { mData->totalFrameCount++; using namespace FrameInfoIndex; // Fast-path for jank-free frames int64_t totalDuration = frame[kFrameCompleted] - frame[kIntendedVsync]; uint32_t framebucket = frameCountIndexForFrameTime( totalDuration, (sizeof(mData->frameCounts) / sizeof(mData->frameCounts[0])) ); //keep the fast path as fast as possible if (CC_LIKELY(totalDuration < mFrameInterval)) { mData->frameCounts[framebucket]++; return; } //exempt this frame, so drop it if (frame[kFlags] & EXEMPT_FRAMES_FLAGS) { return; } mData->frameCounts[framebucket]++; mData->jankFrameCount++; for (int i = 0; i < NUM_BUCKETS; i++) { int64_t delta = frame[COMPARISONS[i].end] - frame[COMPARISONS[i].start]; if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) { mData->jankTypeCounts[i]++; } } }
首先將總幀數totalFrameCount加1,而後計算這一幀的渲染時間totalDuration,值等於結束時間kFrameCompleted-發送Vsync信號的時間kIntendedVsync。而後若是totalDuration的時間小於mFrameInterval(16.6ms),那麼這一幀就是不卡頓的,直接返回。
第二個if,判斷這個幀是不是「豁免」的,若是是也直接返回。那麼什麼幀是「豁免」的呢?先看看這個常量EXEMPT_FRAMES_FLAGS,
static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::kWindowLayoutChanged | FrameInfoFlags::kSurfaceCanvas;
能夠看到這個EXEMPT_FRAMES_FLAGS等於kWindowLayoutChanged|kSurfaceCanvas,也就說若是一個幀是跟kWindowLayoutChanged有關的或者是用一個專門的SurfaceCanvas來繪製的,那麼這個幀不在統計的範圍以內。在這個定義以前有一大段註釋,大概意思是:有一些幀是不作卡頓統計的,好比那些第一次繪製的,用戶以爲慢也是正常的,能夠被動畫或者其餘手段掩蓋的幀,還有那些繪製在surface上面的幀。
若是前面的兩個if都沒有返回,那麼代表這個幀是卡頓的,開始作卡頓統計。在最下面的那個for循環裏面,開始遍歷每一個bucket,就是上面所說的那五種卡頓類型,而後就算每種類型的所消耗的時間delta=COMPARISONS[i].end - COMPARISONS[i].start,若是這個時間大於上面所說的閾值mThresholds,那麼將對應的卡頓類型數+1。
COMPARISONS是一個數組,定義以下:
static const char* JANK_TYPE_NAMES[] = { "Missed Vsync", "High input latency", "Slow UI thread", "Slow bitmap uploads", "Slow draw", }; struct Comparison { FrameInfoIndexEnum start; FrameInfoIndexEnum end; }; static const Comparison COMPARISONS[] = { {FrameInfoIndex::kIntendedVsync, FrameInfoIndex::kVsync}, {FrameInfoIndex::kOldestInputEvent, FrameInfoIndex::kVsync}, {FrameInfoIndex::kVsync, FrameInfoIndex::kSyncStart}, {FrameInfoIndex::kSyncStart, FrameInfoIndex::kIssueDrawCommandsStart}, {FrameInfoIndex::kIssueDrawCommandsStart, FrameInfoIndex::kFrameCompleted}, };
從中,咱們能夠看到,Missed Vsync和High input latency時間區間是有重合的,他們開始的時間不同,可是結束的時間都是kVsync,後面三個bukect的時間都是首尾相連的,到這裏這個五個bukect的時間區間已經很明確了:
Vsync階段:KIntendedVsync 到 KVsync
input階段:KOldestInputEvent 到 KVsync
UI thread階段:KVsync 到 KSyncStart
bitmap uploads階段:KSyncStart 到 KIssueDrawCommandsStart
draw階段:KIssueDrawCommandsStart 到 KFrameCompleted
那麼到如今,具體填進去的操做已經看完了,那麼系統在哪裏調用了這個addFrame呢?而後這個mData有是怎麼交給dump環節來輸出的呢?
然咱們先來回答一下第二個問題。其實總結一下前面的就能夠知道了,綜述一下:Java層ThreadedRenderer在其初始化的過程當中調用了GraphicsStatsService.requestBufferForProcess爲渲染進程分配了放置存儲統計數據的匿名共享內存buffer,返回一個fd,並將其放在mActive統一管理,供java層之後調用;而native層是用RenderProxy來進行任務的代理,RenderProxy又調用了JankTracker.switchStorageToAshmem()來真正完成這一任務,生成了一塊ProfileData類型的內存,由mData持有引用,由JankTracker.addFrame()負責往其中填寫數據。當咱們須要dump的時候,GraphicsStatsService就會取出全部mActive全部保存好的buffer,在經過JNI交由JankTracker自己的dumpData來輸出,兜了一大圈,又轉回來了。能夠發如今在整個過程當中,JankTracker纔是真正核心的類。
那麼整個過程就剩下一個疑問點啦,那就是addFrame是在什麼地方被調用的。這個涉及到view的渲染過程,我會在下一篇博客中解剖。
最後說在後面的,這以上內容都是我我的的理解,我也是第一次看這方面的內容,不少地方也不理解,有不少都是猜的,因此這篇博客更多的是個人代碼閱讀筆記,做爲一個實習生,個人水平頗有限,錯漏的地方確定有不少,歡迎批評指出。