1. 程式人生 > >關於React Native專案在android上UI效能除錯實踐

關於React Native專案在android上UI效能除錯實踐

我們盡最大的努力來爭取使UI元件的效能如絲般順滑,但有的時候這根本不可能做到。要知道,Android有超過一萬種不同型號的手機,而在框架底層進行軟體渲染的時候是統一處理的,這意味著你沒辦法像iOS那樣自由。不過有些時候,你還是可以想辦法提升應用的效能(有的時候問題根本不是出在原生程式碼上!)

要想解決應用的效能問題,第一步就是搞明白在每個16毫秒的幀中,時間都去哪兒了。為此,我們會使用一個標準的Android效能分析工具systrace,不過在此之前……

請先確定JS的開發者模式已經關閉!

你應該在應用的日誌裡看到__DEV__ === false, development-level warning are OFF, performance optimizations are ON

等字樣(你可以通過adb logcat來檢視應用日誌)

使用Systrace進行效能分析

Systrace是一個標準的基於標記的Android效能分析工具(如果你安裝了Android platform-tool包,它也會一同安裝)。被除錯的程式碼段在開始和結束處加上標記,在執行的過程中標記會被記錄,最後會以圖表形式展現統計結果。包括Android SDK自己和React Native框架都已經提供了標準的標記供你檢視。

收集一次資料

注意:

Systrace從React Native v0.15版本開始支援。你需要在此版本下構建專案才能收集相應的效能資料。

首先,把你想分析的、執行不流暢的裝置使用USB線連結到電腦上,然後操作應用來到你想分析的導航/動畫之前,接著這樣執行systrace:

$ <AndroidSDK所在目錄>/platform-tools/systrace/systrace.py --time=10 -o trace.html sched gfx view -a <你的應用包名>

對於此命令做一個簡單的說明:

  • time引數控制本次資料收集的持續時間,單位是秒。
  • schdgfx, 和view是我們所關心的Android SDK內建的tag(標記的集合):schd提供了你的裝置的每個CPU核心正在做什麼的資訊,gfx提供了你的圖形相關資訊,譬如每幀的時間範圍,而view提供了一些關於檢視佈局和渲染相關效能的資訊。
  • -a <你的應用包名>啟用了針對應用的過濾。在這裡填寫你用React Native建立的應用包名。你的應用包名
    可以在你應用中的AndroidManifest.xml裡找到,形如com.example.app

譯註:實際上,AndroidManifest.xml裡的應用包名會被app/build.gradle裡的applicationId取代。如果二者不一致,應當以app/build.gradle裡的為準。

一旦systrace開始收集資料,你可以操作應用執行你所關心的動畫和操作。在收集結束後,systrace會給你提供一個連結,你可以在瀏覽器中開啟這個連結來檢視資料收集的結果。

檢視效能資料

在瀏覽器中開啟資料頁面(建議使用Chrome),你應該能看到類似這樣的結果:

Example

提示: 你可以使用WSAD鍵來滾動和縮放效能資料圖表。

啟用垂直同步高亮

接下來你首先應該啟用16毫秒幀區間的高亮。在螢幕頂端點選對應的複選框:

Enable VSync Highlighting

然後你應該能在螢幕上看到類似上圖的斑馬狀條紋。如果你無法看到這樣的條紋,可以嘗試換一臺裝置來進行分析:部分三星手機顯示垂直同步高亮存在已知問題,而Nexus系列大部分情況都相當可靠。

找到你的程序

滾動圖表直到你找到你的應用包名。在上面的例子裡,我正在分析com.facebook.adsmanager,由於核心的執行緒名字長度限制,它會顯示成book.adsmanager

在左側,你應該能看到一系列執行緒對應著右邊的時間軸。有3到4個執行緒是我們必須關注的:UI執行緒(名字可能是UI Thread或者是你的包名), mqt_jsmqt_native_modules。如果你在Android 5.0以上版本執行,我們還需要關注Render(渲染)執行緒。

UI 執行緒

標準的Android佈局和繪製都在UI執行緒裡發生。右側顯示的執行緒名字會是你的包名(在我的例子裡是book.adsmanager)或者UI Thread.你在這個執行緒裡看到的事件可能會是一些Choreographertraversals或者DispatchUI

UI Thread Example

JS執行緒

這是用於執行JavaScript程式碼的執行緒。根據Android系統版本或者裝置的不同,執行緒名可能是mqt_js或者<...>。如果看不到對應的名字的話,尋找類似JSCallBridge.executeJSCall這樣的事件。

JS Thread Example

原生模組執行緒

這裡是用於原生模組執行程式碼(譬如UIManager)的執行緒,執行緒名可能是mqt_native_modules<...>。在後一種情況下,尋找類似NativeCallCallJavaModuleMethod, 還有onBatchComplete這樣的事件名:

Native Modules Thread Example

額外的:渲染執行緒

如果你在使用Android L(5.0)或者更高版本,你應該還會在你的應用裡看到一個渲染執行緒。這個執行緒真正生成OpenGL渲染序列來渲染你的UI。這個執行緒的名字可能為RenderThread或者<...>,在後一種情況下,尋找類似DrawFramequeueBuffer這樣的事件:

Render Thread Example

尋找導致卡頓的罪魁禍首

一個流暢的動畫應該看起來像這樣:

Smooth Animation

每個背景顏色不同的部分我們稱作“一幀”——記住要渲染一個流暢的幀,我們所有的介面工作都需要在16毫秒內完成。注意沒有任何一個執行緒在靠近幀的邊界處工作。類似這樣的一個應用程式就正在60FPS(幀每秒)的情況下流暢表現。

如果你發現一些起伏的地方,譬如這樣:

Choppy Animation from JS

注意在上圖中JS執行緒基本上一直在執行,並且超越了幀的邊界。這個應用就沒法以60FPS渲染了。在這種情況下,問題出在JS中

你還有可能會看到一些類似這樣的東西:

Choppy Animation from UI

在這種情況下,UI和渲染執行緒有一些重負荷的工作,以至於超越了幀的邊界。這可能是由於我們每幀試圖渲染的UI太多了導致的。在這種情況下,問題出在需要渲染的原生檢視上

並且,你還應該能看到一些可以指導接下來優化工作的有用的資訊。

JS的問題

如果你發現問題出在JS上,在你正在執行的JS程式碼中尋找線索。在上面的圖中,我們會發現RCTEventEmitter每幀被執行了很多次。這是上面的資料統計放大後的內容:

Too much JS

這看起來不是很正常,為什麼事件被呼叫的如此頻繁?它們是不同的事件嗎?具體的答案取決於你的產品的程式碼。在許多情況下,你可能需要看看shouldComponentUpdate的介紹。

TODO: 我們還在準備更多的JS效能分析的工具,會在將來的版本中加入。

原生UI問題

如果你發現問題出在原生UI上,有兩種常見的情況:

  1. 你每幀在渲染的UI給GPU帶來了太重的負載,或者:
  2. 你在動畫、互動的過程中不斷建立新的UI物件(譬如在scroll的過程中載入新的內容)

GPU負擔過重

在第一種情況下,你應該能看到UI執行緒的圖表類似這樣:

Overloaded GPU

注意DrawFrame花費了很多時間,超越了幀的邊界。這些時間用來等待GPU獲取它的操作快取。

要緩解這個問題,你應該:

  • 檢查renderToHardwareTextureAndroid的使用,有這個屬性的View的子節點正在進行動畫或變形會導致效能大幅下降(譬如Navigator提供的滑動、淡入淡出動畫)。
  • 確保你沒有使用needsOffscreenAlphaCompositing,這個預設是關閉的,因為它在大部分情況下都會帶來GPU消耗的大幅提升。

如果這還不能幫你解決問題,你可能需要更深入的探索GPU到底在做什麼。參見Tracer for OpenGL ES

在UI執行緒建立大量檢視

如果是第二種情況,你可能會看到類似這樣的結果:

Creating Views

注意一開始JS執行緒工作了很久,然後你看到原生模組執行緒幹了些事情,最後帶來了UI執行緒的巨大開銷。

這個問題並沒有什麼簡單直接的優化辦法,除非你能把建立UI的步驟推遲到互動結束以後去進行,或者你能直接簡化你所要建立的UI。React Native小組正在架構層設法提供一個方案,使得新的UI檢視可以在主執行緒之外去建立和配置,這樣就可以使得互動變得更加流暢。