編輯:關於Android編程
相對其他 Android 性能指標(如內存、CPU、功耗等)而言,顯示性能(包括但不僅限於我們常說的“流暢度”)的概念本來就相對復雜。讓我們更蛋疼的是,業界對顯示測試評估方式也是豐富多樣,這無疑更加重了我們對其理解的復雜程度。
筆者簡單搜集了一些業界中提及的顯示性能指標,大家可以來品評一下:
指標名稱:FPS
相關資料:Android性能測試之fps獲取指標名稱:Aggregate frame stats(N 多個指標)
相關資料:?Testing Display Performance指標名稱:Jankiness count、Max accumulated frames、Frame rate
相關資料:JankTestBase.java指標名稱:SM、Skipped frames
相關資料:Android應用性能評測調優
面對如此之多的顯示性能指標,想必大家也會跟筆者一樣,心中難免疑惑叢生。其實,我們只需要依次弄清楚以下三個哲學問題,所有的問題也許就會迎刃而解:
你是誰——這些指標具體反映了什麼問題 你從哪兒來——這些指標數值是怎麼得到的 你要到哪兒去——這些指標如何落地來指導優化因此,本文將嘗試依次從上訴三個問題來逐步分析和探討各個顯示性能指標。
總所周知,脫離了具體的應用背景,所有的指標都是沒有意義的。所以,為了徹底弄清楚各個顯示性能指標的具體身份,我們勢必得從 Android 的圖像渲染流程說起。
具體展開之前,首先需要說明的是,為了降低復雜程度和本章篇幅,在這個環節之中,我們只討論圖像渲染流程中的各個具體環節所對應的指標有哪些。而指標的具體定義,由第二章《你從哪兒來——這些指標數值是怎麼得到的》進行討論。
下圖是筆者結合各類資料(主要是是源碼及官方文檔),在根據自己的理解梳理出的幾種常見場景下的圖像渲染流程:
PS 1:筆者個人技術水平有限,若存在理解有誤的地方還望指正。
PS 2:本文主要討論的 Android 源碼為 Android 6.0
備注:基於 OpenGL 的應用可以使用 ChZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcmVvZ3JhcGhlciDW0LXEIFZTWU5DINDFusXAtL340NDNvM/x5NbIvrmk1/e1xLCyxcWhozwvcD4KPC9ibG9ja3F1b3RlPgoKPHA+yc/D5tXit/nNvMnmvLC1xLjFxO69z7bgo6zSqs3qyKuz1M24ucC8xrXDt9Gyu8nZyrG85KGjsru5/brD1NrO0sPH1rvKx8/rxarD97DXz9TKvtDUxNy499bW1rix6rXEuqzS5aOsy/nS1M7Sw8fWu9Do0qrA7cflz8LD5sG9tPO52M+1vLS/yaO6PC9wPgoKCjxwPjxzdHJvbmc+U3VyZmFjZUZsaW5nZXKhokhXQ29tcG9zZXLT61N1cmZhY2W1xLnYz7U8L3N0cm9uZz48L3A+Cgo8c3Ryb25nPlN1cmZhY2U8L3N0cm9uZz6jur/J0tTA7b3izqpBbmRyb2lkz7XNs9bQtcTSu7j2u/mxvs/Uyr61pdSqoaPWu9KqyrnTw0FuZHJvaWTIztLi0rvW1kFQSbvmzbyjrLvm1sa1xL3hufu2vL2rt7TTs9TaU3VyZmFjZcnPoaMKPHN0cm9uZz5TdXJmYWNlRmxpbmdlcjwvc3Ryb25nPqO6t/7O8dTL0NDU2lN5c3Rlbb34s8zW0KOs08PAtM2z0ru53MDtz7XNs7XE1qG7urPlx/jJ6LG4o6zG5Nb30qrX99PDyse9q8+1zbPW0LXEtPOyv7fWU3VyZmFjZb340NC6z7PJoaNTdXJmYWNlRmxpbmdlctb30qrKudPDR1BVvfjQ0FN1cmZhY2W1xLrPs8mjrLrPs8m1xL3hufu9q9DOs8nSu7j2RnJhbWVCdWZmZXKhowo8c3Ryb25nPkhXQ29tcG9zZXI8L3N0cm9uZz6jury0SGFyZHdhcmUgQ29tcG9zZXIgSEFMo6zG5Nf308PKx72rU3VyZmFjZUZsaW5nZXLNqLn9R1BVus+zybXEveG5+9PrxuTL+1N1cmZhY2XSu8bw1+7W1dDOs8lCdWZmZXJRdWV1ZdbQtcTSu7j2QnVmZmVyoaO0y83io6xIV0NvbXBvc2Vyv8nS1NCt1vpTdXJmYWNlRmxpbmdlcr340NBTdXJmYWNltcS6z7PJo6y1q8rHt/G9+NDQ0K3W+srH08lIV0NvbXBvc2Vyvva2qLXEoaMK1rW1w9ei0uK1xMrHo6zT0LXEU3VyZmFjZbK708lXaW5kb3dNYW5hZ2VyudzA7aOsvavWsb3T1/fOqkhXQ29tcG9zZXK1xMrkyOvWrtK70+tTdXJmYWNlRmxpbmdlcrXEyuSz9tf21+6687XEus+zyaGjCjxwPjxzdHJvbmc+Q2hvcmVvZ3JhcGhlcqGiU3VyZmFjZUZsaW5nZXKhokhXQ29tcG9zZXLT61ZTWU5DtcS52M+1PC9zdHJvbmc+PC9wPgoKPHN0cm9uZz5WU1lOQzwvc3Ryb25nPqO6VmVydGljYWwgU3luY2hyb25pemF0aW9utcTL9dC0o6zL/LXE1/fTw8rHyrlHUFW1xOTWyL7GtcLK0+vP1Mq+xve1xMui0MLGtcLKo6jSu7Djzqq5zLao1rWjqc2ssr2007b4sdzD4rP2z9a7rcPmy7rB0bXEz9bP86GjCjxzdHJvbmc+SFdDb21wb3Nlcjwvc3Ryb25nPqO6VlNZTkPQxbrF1vfSqtPJSFdDb21wb3Nlcs2ouf3Tsrz+tKW3oqGjCjxzdHJvbmc+Q2hvcmVvZ3JhcGhlcjwvc3Ryb25nPqO6tbHK1bW9VlNZTkPQxbrFyrGjrENob3Jlb2dyYXBoZXK9q7C008XPyLy2uN+1zdLAtM7IpbX308PKudPD1d/NqLn9cG9zdENhbGxiYWNrzOHHsMno1sO1xLvYtfe6r8r9o6zL/MPHt9ax8MrHo7rTxc/IvLbX7rjftcRDQUxMQkFDS19JTlBVVKGi08XPyLy2tM6437XEQ0FMTEJBQ0tfQU5JTUFUSU9O0tS8sNPFz8i8ttfutc21xENBTExCQUNLX1RSQVZFUlNBTKGjCjxzdHJvbmc+U3VyZmFjZUZsaW5nZXI8L3N0cm9uZz6julN1cmZhY2W1xLrPs8my2df30rLKsbv509pWU1lOQ9DFusW9+NDQtcShowoKCjxwPrzytaXAtMu1o6xBbmRyb2lkIM28z/Hk1si+wfezzNb30qrTydLUz8LM2NX3o7o8L3A+CgoKztLDx7/J0tS88rWlsNEgQW5kcm9pZCDNvM/x5NbIvrzcubm31s6q06bTw6OoU3VyZmFjZaOpoaLPtc2zo6hTdXJmYWNlRmxpbmdlcqOpoaLTsrz+o6hTY3JlZW6jqcj9uPay47y2o6zG5NbQu+bWxtTa06bTw7Ljo6y6z7PJvLDM4b27yc/GwdTaz7XNs7Ljo6zP1Mq+1NrTsrz+suOjuwrO3sLb06bTw6OoU3VyZmFjZaOpoaLPtc2zo6hTdXJmYWNlRmxpbmdlcqOpoaLTsrz+o6hTY3JlZW6jqba8yse1scfSvfa1sbvm1sbE2sjdt6LJ+rjEseSjrLLFu+G21Lvm1sbE2sjdvfjQ0LSmwO2juwrPtc2z1tC1xCBTdXJmYWNlRmxpbmdlciDS1Lywvvi087K/t9YgU3VyZmFjZSC2vMrHsLTV1SBWU1lOQyDQxbrFtcS92tfgwLSwssXF19S8urXEyM7O8aO7CsS/x7CjrL74tPOyv7fWIFN1cmZhY2UgtrzK9NPaIEhhcmR3YXJlIFJlbmRlcmluZ6GjCgoKCgo8aDIgaWQ9"各個指標在-android-圖像渲染流程所代表的意義">各個指標在 Android 圖像渲染流程所代表的意義
大致梳理了 Android 的圖像渲染流程之後,我們需要做的一件事情,就是看看上面提到的指標,都對應了渲染流程的哪些階段,這樣對於我們了解各個指標所反映的具體物理意義及其優勢劣勢都有極大幫助。再次強調,在這個環節之中,我們的討論僅限於只討論指標所對應的渲染流程的具體階段,各指標的具體定義由第二章具體展開。
系統層級(SurfaceFlinger)的顯示性能指標
基礎數據:SurfaceFlinger 合成次數 指標意義:
系統合成幀率:FPS 特別說明:
SurfaceFlinger 僅在顯示區域內的 Surface 有提交內容更新時才會進行合成(上屏),因此,系統合成幀率低並不一定意味著圖像顯示性能差,有可能是因為當前並沒有任何的內容更新所導致。 若顯示區域內的某個待測 Surface 持續進行更新時, SurfaceFlinger的合成(上屏)的頻率可以在某種程度上反映該 Surface 的顯示性能,但從理論上分析該指標並不一定准確。這是因為,若顯示區域內尚存在其他 Surface,它們也會影響 SurfaceFlinger 的合成(上屏)的行為,從而干擾結果。 若某個 Surface 的合成不在 SurfaceFlinger 中進行(如 Camera Preview),則該 Surface 的顯示性能無法用這類指標進行衡量。應用層級(Surface)的顯示性能指標
基礎數據:繪制過程中每一幀的關鍵時間點(如開始繪制時間、結束繪制時間等) 指標意義:
應用繪制幀率:Frame rate 應用繪制輪詢頻率:SM 應用繪制超時(跳幀)的次數:Aggregate frame stats、Jankiness count、Skipped frames 應用繪制超時(跳幀)的幅度:Aggregate frame stats、Max accumulated frames、Skipped frames 特別說明:
與 SurfaceFlinger 類似, Surface也僅在有內容更新時才會進行繪制,因此,繪制頻率低並不一定意味著圖像顯示性能差,有可能是因為當前並沒有任何的內容更新所導致。 如 SM、Skipped frames 這類指標,由於其基礎數據取自 Choreographer,若 某些 Surface 的繪制不依賴於 Choreographer ,則這些指標無法衡量該 Surface 的顯示性能。 如 Aggregate frame stats、Jankiness count、Max accumulated frames、Frame rate 這類指標, 由於其基礎數據僅在硬件繪制(Hardware Rendering)過程中進行統計,屬於 HWUI 的功能,所以非硬件繪制的 Surface 自然無法使用這類指標進行衡量。小結
評價顯示性能的各個指標,可以按其在圖像渲染流程中的作用,分為以下兩類:
系統層級的指標僅有 FPS 一根獨苗,它的限制是 Surface 的和合成需要在 SurfaceFlinger中進行; 應用層級的指標較多,它們之中又可以分為兩類:
1) SM、Skipped frames 需要 Surface 依賴 Choreographer進行繪制,才能正常工作;
2) Aggregate frame stats、Jankiness count、Max accumulated frames、Frame rate 屬於 HWUI 的功能, 需要 Surface 的繪制由 HWUI 進行才能進行分析。Step 2:你從哪兒來——這些指標數值是怎麼得到的
第一章的內容僅僅是站在整個圖像繪制流程的高度來簡單分析各個指標的,本章將進一步分析各個指標的基礎數據來源以及具體計算方式。
基礎數據:系統層級(SurfaceFlinger)的合成(上屏)的次數
前面說到,在 Android 系統中,SurfaceFlinger 扮演了系統中所有 Surface 的管理者的角色,當應用程序所對應的 Surface 更新之後,絕大多數的 Surface 都將在 SurfaceFlinger 之中完成了合並的工作之後,最終才會在 Screen 上顯示出來。
當然, SurfaceFlinger 的執行也是由 VSYNC 信號驅動的,這也決定了每秒鐘合成次數的上限就是 60 次。當 SurfaceFlinger 接收到 Surface 更新通知的時候,將會由 SurfaceFlinger::handleMessageRefresh 函數進行處理,其中包含重建可見區域、初始化、合成等步驟。這裡,我們主要關注 SurfaceFlinger::doComposition() 這個方法。
void SurfaceFlinger::handleMessageRefresh() { ... if (CC_UNLIKELY(mDropMissedFrames && frameMissed)) { // Latch buffers, but don't send anything to HWC, then signal another // wakeup for the next vsync preComposition(); repaintEverything(); } else { preComposition(); rebuildLayerStacks(); setUpHWComposer(); doDebugFlashRegions(); doComposition(); //重點關注對象 postComposition(); } ... }
在 doComposition 中,完成 Surface 的合成之後,都會調用 DisplayDevice::flip(),它會使用變量 mPageFlipCount 統計我們進行合成的次數,這個變量就是我們統計 FPS 的核心原始數據。mPageFlipCount 記錄了 SurfaceFlinger 一共進行了多少次合成,也可以簡單理解為,SurfaceFlinger 向屏幕提交了多少幀的數據。
void SurfaceFlinger::doComposition() { ATRACE_CALL(); const bool repaintEverything = android_atomic_and(0, &mRepaintEverything); for (size_t dpy=0 ; dpy
& hw(mDisplays[dpy]); if (hw->isDisplayOn()) { // transform the dirty region into this screen's coordinate space const Region dirtyRegion(hw->getDirtyRegion(repaintEverything)); // repaint the framebuffer (if needed) doDisplayComposition(hw, dirtyRegion); hw->dirtyRegion.clear(); hw->flip(hw->swapRegion);//重點關注對象 hw->swapRegion.clear(); } // inform the h/w that we're done compositing hw->compositionComplete(); } postFramebuffer(); } void DisplayDevice::flip(const Region& dirty) const { mFlinger->getRenderEngine().checkErrors(); if (kEGLAndroidSwapRectangle) { if (mFlags & SWAP_RECTANGLE) { const Region newDirty(dirty.intersect(bounds())); const Rect b(newDirty.getBounds()); eglSetSwapRectangleANDROID(mDisplay, mSurface, b.left, b.top, b.width(), b.height()); } } mPageFlipCount++; }
不僅如此, Android 還為我們獲取這個基礎數據提供了比較方便的方法。通過執行 adb 命令:service call SurfaceFlinger 1013,我們就可以得出當前的 mPageFlipCount。
C:\Users\xiaosongluo>adb shell shell@cancro:/ $ su su root@cancro:/ # service call SurfaceFlinger 1013 service call SurfaceFlinger 1013 Result: Parcel(00aea4f4 '....')
這條命令將使得 SurfaceFlinger 接收到 1013 的指令,從而將 mPageFlipCount 打印出來,具體過程見下述源碼。
status_t SurfaceFlinger::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { ... case 1013: { Mutex::Autolock _l(mStateLock); sp
hw(getDefaultDisplayDevice()); reply->writeInt32(hw->getPageFlipCount()); return NO_ERROR; } ... } uint32_t DisplayDevice::getPageFlipCount() const { return mPageFlipCount; }
FPS 的計算方法
根據 FPS 的定義,我們不難逆推得出 FPS 的計算方法:
在 t1 時刻獲取 mPageFlipCount 的數值 v1,在在 t2時刻獲取 mPageFlipCount 的數值 v2,FPS 的計算公式:
FPS = (v2 - v1) / (t2 - t1);
需要注意的是:mPageFlipCount 的原始數據是 16 進制的,一般而言計算之前需要先進行進制轉換。
基礎數據:應用層級(Surface)的繪制過程中每一幀的關鍵時間點(FrameInfo)
請大家先注意 FrameInfo 是由 Android 6.0(具體來講是 Android M Preview) 引入到 HWUI 模塊中的統計功能。 因此,目前來講絕大多數系統上的大多數應用都暫時無法獲取這一基礎數據。不過 This IsTheFuture。
我們再來仔細瞧瞧 Google 給出的顯示性能測試的十全大補丸 《Testing Display Performance : Aggregate frame stats》 。其中,特別值得關注的是 adb shell dumpsys gfxinfo framestats 這一條命令。通過這條命令,我們獲取每一幀繪制過程中每個關鍵節點的耗時情況,從而仔細的分析潛在的性能問題。
>adb shell dumpsys gfxinfo
framestats 0,27965466202353,27965466202353,27965449758000,27965461202353,27965467153286,27965471442505,27965471925682,27965474025318,27965474588547,27965474860786,27965475078599,27965479796151,27965480589068,0,27965482993342,27965482993342,27965465835000,27965477993342,27965483807401,27965486875630,27965487288443,27965489520682,27965490184380,27965490568703,27965491408078,27965496119641,27965496619641,0,27965499784331,27965499784331,27965481404000,27965494784331,27965500785318,27965503736099,27965504201151,27965506776568,27965507298443,27965507515005,27965508405474,27965513495318,27965514061984,0,27965516575320,27965516575320,27965497155000,27965511575320,27965517697349,27965521276151,27965521734797,27965524350474,27965524884536,27965525160578,27965526020891,27965531371203,27965532114484, 不得不說,按照 Google 給出的這種測試方法進行測試得到的顯示性能數據是非常全面的。
這些基礎數據都是記錄在 FrameInfo 之中,由 CanvasContext 在doFrame()時進行記錄。相關的主要源碼如下:
//源碼:FrameInfo.cpp #include "FrameInfo.h" #include
namespace android { namespace uirenderer { const std::string FrameInfoNames[] = { "Flags", "IntendedVsync", "Vsync", "OldestInputEvent", "NewestInputEvent", "HandleInputStart", "AnimationStart", "PerformTraversalsStart", "DrawStart", "SyncQueued", "SyncStart", "IssueDrawCommandsStart", "SwapBuffers", "FrameCompleted", }; void FrameInfo::importUiThreadInfo(int64_t* info) { memcpy(mFrameInfo, info, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t)); } } /* namespace uirenderer */ } /* namespace android */ //源碼:CanvasContext.cpp // Called by choreographer to do an RT-driven animation void CanvasContext::doFrame() { if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) { return; } ATRACE_CALL(); nsecs_t vsync = mRenderThread.timeLord().computeFrameTimeNanos(); int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE]; UiFrameInfoBuilder(frameInfo).addFlag(FrameInfoFlags::RTAnimation).setVsync(vsync, vsync); TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState()); prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC)); if (info.out.canDrawThisFrame) { draw(); } } void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued) { ... mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo); ... }
Aggregate frame stats 指標的計算方法
首先需要說明的是 Aggregate frame stats 不是一個指標,而是一系列指標集合。我們來看一個具體的 Aggregate frame stats 的例子:
Stats since: 752958278148ns
Total frames rendered: 82189
Janky frames: 35335 (42.99%)
90th percentile: 34ms
95th percentile: 42ms
99th percentile: 69ms
Number Missed Vsync: 4706
Number High input latency: 142
Number Slow UI thread: 17270
Number Slow bitmap uploads: 1542
Number Slow draw: 23342以上統計信息的實現可以詳見源碼:GfxMonitorImpl.java
在 Android M 以上的系統上,上述信息的獲取十分方便(事實上也只有這些系統能夠獲取這些信息)。僅需要執行以下命令即可:
adb shell dumpsys gfxinfo
Jankiness count、Max accumulated frames、Frame rate 指標的計算方法
首先需要說明的是:Jankiness count、Max accumulated frames、Frame rate 與 Aggregate frame stats的基礎數據並不一致,它們的基礎屬於來源於 gfxinfo(Profile data in ms)。
只是在 Android M 中 gfxinfo(Profile data in ms) 的基礎數值來源於 FrameInfo,詳見源碼:FrameInfoVisualizer。但在更早的系統之上, gfxinfo(Profile data in ms) 的數值也可以獲取。
這裡需要特別指出的是, gfxinfo(Profile data in ms)只保存了 Surface 最近渲染的128幀的信息,因此,Jankiness count、Max accumulated frames、Frame rate 也僅僅是針對這 128 幀數據所計算出來的結果,它們的具體含義分別是:
Jankiness count:根據相鄰兩幀繪制時間的差值,“估計”是否存在跳幀並進行跳幀次數的統計; Max accumulated frames: 根據相鄰兩幀繪制時間的差值,“估計”這 128 幀繪制過程中可能形成的最大連續跳幀數; Frame rate:計算所得平均(繪制)幀率。如果你對具體的計算過程感興趣,可以參考詳見源碼:JankTestBase
基礎數據:應用層級(Surface)的繪制過程中每一幀的關鍵時間點(Choreographer)
先說一句有點繞口的話: Choreographer 是依據 Choreographer 繪制的 Surface 在 UI 繪制過程中最為核心的機制。
Choreographer 的工作機制簡單來說就是,使用者首先通過 postCallback 在 Choreographer 中設置的自己回調函數:
CALLBACK_INPUT:優先級最高,和輸入事件處理有關。 CALLBACK_ANIMATION:優先級其次,和Animation的處理有關。 CALLBACK_TRAVERSAL:優先級最低,和UI等控件繪制有關。那麼,當 Choreographer 接收到 VSYNC 信號時,Choreographer 會調用 doFrame 函數依次對上述借口進行回調,從而進行渲染。
那麼顯然,Choreographer 的執行效率(次數、頻率)也就是我們需要的顯示性能數據。而這些基礎數據,Choreographer 自身也進行了記錄:
// Set a limit to warn about skipped frames. // Skipped frames imply jank. private static final int SKIPPED_FRAME_WARNING_LIMIT = SystemProperties.getInt("debug.choreographer.skipwarning", 30); void doFrame(long frameTimeNanos, int frame) { ... long intendedFrameTimeNanos = frameTimeNanos; startNanos = System.nanoTime(); final long jitterNanos = startNanos - frameTimeNanos; if (jitterNanos >= mFrameIntervalNanos) { final long skippedFrames = jitterNanos / mFrameIntervalNanos; if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); } final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; if (DEBUG_JANK) { Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms " + "which is more than the frame interval of " + (mFrameIntervalNanos * 0.000001f) + " ms! " + "Skipping " + skippedFrames + " frames and setting frame " + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); } frameTimeNanos = startNanos - lastFrameOffset; } ... }
上述數據的獲取並不是那麼的直接,所以需要一定的手段。方法一共有三種,都不難:
Logcat 方案缺點:該方案需要系統授權 “Adb Root” 權限,用於修改系統屬性;對於丟幀信息只能統計分析,無法進行實時處理。
優點:設置完成後,可以獲取系統中所有應用各自的繪制丟幀情況(丟幀發生的時間以及連續丟幀的數量)。其實,仔細觀察代碼,我們就可以注意到 Choreographer 源碼中本身就有輸出的方案:
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); }
唯一阻礙我們獲取數值的是:skippedFrames 的數值只有大於 SKIPPED_FRAME_WARNING_LIMIT 才會輸出相關的警告。而 SKIPPED_FRAME_WARNING_LIMIT 的數值可以由系統參數 debug.choreographer.skipwarning 來設定。
注意:初始條件下,系統中不存在 debug.choreographer.skipwarning 參數,因此 SKIPPED_FRAME_WARNING_LIMIT 將取默認值 30。因此,正常情況下,我們能夠看見上訴 Log 出現的機會極少。
因此,如果我們修改(設定)系統屬性 debug.choreographer.skipwarning 為 1,Logcat 中將打印出每一次丟幀的Log。需要說明的是,由於為 SKIPPED_FRAME_WARNING_LIMIT 賦值的代碼段由 Zygote 在系統啟動階段加載,而其他應用都是在拷貝復用 Zygote 中的設定,因此設定系統屬性後需要重啟 Zygote 才能使得上述設定生效。
具體的設置方法如下:
setprop debug.choreographer.skipwarning 1 setprop ctl.restart surfaceflinger; setprop ctl.restart zygote
設定完成以後,我們可以直接通過 Logcat 中的信息得到系統中所有應用的繪制丟幀信息,包括丟幀發生的時間以及連續丟幀的數量。不過由於 Logcat 信息的滯後性,以上信息我們幾乎只能進行在測試完成後進行統計分析,而無法進行實時處理。
Choreographer.FrameCallback 方案缺點:該方案需要將測試代碼與待測應用打包在一起,因此理論上僅能測試自己開發的應用。
優點:可以對丟幀信息進行實時處理我們先來看看 Choreographer.FrameCallback 的定義。
Implement this interface to receive a callback when a new display frame is being rendered. The callback is invoked on theLooper thread to which the Choreographeris attached.
通過這個接口,我們可以在每一幀被渲染的時候記錄下它開始渲染的時間,這樣在下一幀被處理是,我們不僅可以判斷上一幀在渲染過程中是否出現掉幀,而整個過程都是實時處理的,這為我們可以及時獲取相關的調用棧信息來輔助定位潛在的性能缺陷有極大的幫助。
代碼注入方案缺點:該方案需要通過注入程序為指定應用注入測試代碼,因此需要系統為注入程序授權 “應用Root” 權限。
優點:與 Choreographer.FrameCallback 方案一致。該方案可以簡單理解為通過注入的方式來實現與 Choreographer.FrameCallback 方案一樣的目的。因此,這裡我們主要討論兩者在實現方式上的區別。
顯而易見,我們需要注入的對象是 Choreographer ,因此理論上任何第三方應用都是可以被注入的。但是隨著 Android 系統對”應用Root” 權限管理越來越嚴格,所以該方案可用的范圍越來越小。
SM 指標的計算方法
根據定義,SM 其實類似於 FPS,它被設計為可以衡量應用平均每秒執行 doFrame() 的次數。我們可以認為它是在衡量 Surface 渲染輪詢的次數。
針對 Logcat 方案,我們只需統計測試過程中目標進程一共掉了多少幀,由於對於絕大多數應用在沒有丟幀的情況下會針對每一次 VSYNC 信號執行一次 doFrame(),而 VSYNC 絕大多數情況下每秒會觸發 60 次,因此我們可以反向計算得出 SM 的數值:
SM = (60* totalSeconds - totalSkippedFrames) / totalSeconds;
針對 Choreographer.FrameCallback 方案 以及 代碼注入方案,我們需要在代碼中自己進行統計輸出(可以是設計成實時的,也可以設計成測試結束後進行統計計算的)。
Skipped frames 指標的計算方法
這個指標的就是指當前應用在丟幀發生時的丟幀幀數。
針對 Logcat 方案, 該數值直接在 Logcat 中輸出,並且帶有時間信息。
04-18 16:31:24.957 I/Choreographer(24164): Skipped 4 frames! The application may be doing too much work on its main thread. 04-18 16:31:25.009 I/Choreographer(24164): Skipped 2 frames! The application may be doing too much work on its main thread.
針對 Choreographer.FrameCallback 方案 以及 代碼注入方案,我們可能很方便的通過計算前後兩幀開始渲染的時間差獲得這一數值,同樣方便。同樣與 Logcat 方案 不同的是,它也是可以設計成實時計算的。
小結
通過對各個顯示性能指標的分析,我們可以知道,雖然目前指標眾多,但其實有本質區別的指標確很少:
系統層面:
合成(上屏)幀率:FPS 應用層面:
跳幀次數:Aggregate frame stats、Jankiness count、Skipped frames 跳幀幅度:Aggregate frame stats、Max accumulated frames、Skipped frames 繪制幀率:Frame rate 繪制輪詢頻率:SM更為重要的是,我們從上述的分析中知道了各個指標都有著自己的優勢和不足,這也從根本上決定了它們各自有各自的用法。
Step 3:你要到哪兒去——這些指標如何落地來指導優化
其實指標的用法也是多種多樣的,為了方便討論,我們僅從日常監控、缺陷定位以及數據上報三個方面來討論各個顯示性能指標是如何落地的。
日常監控
FPS:數據形式最為直觀(FPS 是最早的顯示性能指標,而且在多個平台中都有著類似的定義),且對系統平台的要求最低(API level 1),游戲、視頻等連續繪制的應用可以考慮選用,但不適用於絕大多數非連續繪制的應用; SM:數據形式與 FPS 類似,可以很好的彌補 FPS 無法准確刻畫非連續繪制的應用顯示性能的缺陷; Aggregate frame stats:除了對系統平台有較高的要求以外,其采集方式最為簡單(系統自帶功能); Skipped frames:與 Aggregate frame stats 類似, 信息量相對較少,但可適用范圍更廣特別說明:Jankiness count、Max accumulated frames、Frame rate 只統計了128幀的信息(約2~3秒),而且 Jankiness count、Max accumulated frames對於掉幀情況的計算並非是一個准確值,因此這些指標都不太適用於日常監控
缺陷定位
Skipped frames:基於 Choreographer.FrameCallback 方案實現的 Skipped frames 指標,可以在卡頓出現的時刻獲取應用堆棧信息,可以在一定程度上進行缺陷定位特別說明:
1. FrameInfo 相關指標無法直接進行缺陷定位,但 FrameInfo 當中包含了大量詳盡的繪制基礎數據,對於缺陷定位也有較大幫助;
2. 關於缺陷定位過程中連續掉幀阈值的選取,可參考維基百科中提到幾個重要的幀率數值:
- 12 fps:由於人類眼睛的特殊生理結構,如果所看畫面之幀率高於每秒約10-12幀的時候,就會認為是連貫的
- 24 fps:有聲電影的拍攝及播放幀率均為每秒24幀,對一般人而言已算可接受
- 30 fps:早期的高動態電子游戲,幀率少於每秒30幀的話就會顯得不連貫,這是因為沒有動態模糊使流暢度降低
- 60 fps:在實際體驗中,60幀相對於30幀有著更好的體驗以上各數據分別對應: 0 幀、1幀、2.5幀、5幀。
數據上報
Aggregate frame stats:除了對系統平台有較高的要求以外,其采集方式最為簡單(系統自帶功能)、數據也比較清晰,相信基於這類指標實現性能數據上報是特別方便的 Skipped frames :基於 Choreographer.FrameCallback 方案實現的 Skipped frames 指標,采集方式簡單,實現基礎性能數據上報、卡頓數據上報也是很方便的小結
發現了沒有 Skipped frames 的用處很大有沒有? 而且通讀全篇,你會發現 Aggregate frame stats、Jankiness count、Max accumulated frames 這些指標都有提供類似的功能。
友情附贈: 現有顯示性能指標對比
本來寫到這裡本文的主要內容就應該結束了,但是如果不對比一下顯示性能指標神馬的,總會讓人覺得缺少了一些什麼。
友情提示:下述內容相對主觀,建議各位讀者依據項目情況自行進行選擇。
指標名稱 指標意義 基礎數據來源 采集方式 適用系統 適用應用 用途 FPS 系統合成幀率 SurfaceFlinger adb shell 無 略 監控 Aggregate frame stats 應用跳幀次數、幅度 FrameInfo adb shell 最低23 HW Rendering 監控/上報 Jankiness count (估算)應用跳幀次數 FrameInfo(128幀) adb shell 無 HW Rendering 定位 Max accumulated frames (估算)應用跳幀幅度 FrameInfo(128幀) adb shell 無 HW Rendering 定位 Frame rate 應用繪制幀率 FrameInfo(128幀) adb shell 無 HW Rendering 定位 SM 應用繪制輪詢頻率 Choreographer 多種方式 最低16 SW/HW Rendering 及 部分 OpenGL Rendering 監控 Skipped frames 應用跳幀次數、幅度 Choreographer 多種方式 最低 16 SW/HW Rendering 及 部分 OpenGL Rendering 監控/定位/上報
最近忙找實習,加上實驗室在推新項目,需要學習新知識。所以很長一段時間沒去整理了官博客了,github也蠻久沒更新,很慚愧。接下來還是要堅持寫。今天就簡單的寫一下我在項目中
listview是開發中必見的功能應用,各種需求也不盡相同,今天給大家一個帶來一個簡單方便的自定義listview,希望對大家有幫助,閒話不說,先上幾張效果圖1、功能示例
最近在做Android的項目,其中部分涉及到圖像處理的內容.這裡先講述如何調用Camera應用程序進行拍照,並截圖和保存顯示在ImageView控件中以及遇到的困難和解決
本文實例為大家介紹了Android ListView下拉刷新功能的實現方法和功能,供大家參考,具體內容如下1、ListView優化方式界面緩存:ViewHolder+co