1. 程式人生 > >Android效能優化-過度渲染

Android效能優化-過度渲染

過度渲染

去除過度渲染可以從下面渲染階段的幾方面入手:

  • 交換緩衝區階段,表示 CPU 等待 GPU 完成其工作的時間。 如果此豎條升高,則表示應用在 GPU 上執行太多工作。
  • 命令問題階段, 表示 Android 的 2D 渲染器向 OpenGL 發起繪製和重新繪製顯示列表的命令所花的時間。 此豎條的高度與它執行每個顯示列表所花的時間的總和成正比—顯示列表越多,紅色條就越高。
  • 同步和上傳階段,表示將點陣圖資訊上傳到 GPU 所花的時間。 大區段表示應用花費大量的時間載入大量圖形。
  • 繪製階段,表示用於建立和更新檢視顯示列表的時間。 如果豎條的此部分很高,則表明這裡可能有許多自定義檢視繪製,或 onDraw 函式執行的工作很多。
  • 測量/佈局階段,表示在檢視層次結構中的 onLayout 和 onMeasure 回撥上所花的時間。 大區段表示此檢視層次結構正在花很長時間進行處理。
  • 動畫階段,表示評估執行該幀的所有動畫程式所花的時間。 如果此區段很大,則表示您的應用可能在使用效能欠佳的自定義動畫程式,或因更新屬性而導致一些意料之外的工作。
  • 輸入處理階段,表示應用執行輸入 Event 回撥中的程式碼所花的時間。 如果此區段很大,則表示此應用花太多時間處理使用者輸入。 考慮將此處理任務分流到另一個執行緒。
  • 其他時間/VSync 延遲階段,表示應用執行兩個連續幀之間的操作所花的時間。 它可能表示介面執行緒中進行的處理太多,而這些處理任務本可以分流到其他執行緒。

具體如何分析和優化過度渲染,可以檢視我之前的部落格關於過度繪製和渲染的介紹

補充

GAPID

GAPID是一個開發工具,用於記錄和檢查應用程式對圖形驅動程式的呼叫。

在這裡插入圖片描述

一旦捕獲了目標應用程式,GAPID允許您斷開與目標的連線,並檢查應用程式執行的所有圖形命令。

GAPID能夠重放命令流,通過逐步遍歷每個命令並檢查流中任何點的驅動程式狀態,使框架組合視覺化。Replay還支援修改,允許您調整命令引數和著色器源,以便立即檢視這會對幀產生什麼影響。

GAPID還可以視覺化應用程式所使用的紋理、著色器和繪製呼叫幾何結構

Capturing a trace

GAPID支援從Android裝置和Windows /Linux桌面機器上捕獲。在Android裝置上,GAPID支援跟蹤由純Java、本地或混合應用程式生成的所有OpenGL ES和Vulkan呼叫。在Windows /Linux桌面機上,GAPID支援跟蹤Vulkan呼叫。

依賴和前提條件

Android:

  • 執行在5.0裝置以上
  • 要麼是可除錯的應用程式,要麼是執行“rooted”使用者除錯生成的裝置
  • 電腦安裝了Android SDK
  • 通過USB連線的Android硬體裝置
  • 該裝置必須啟用USB除錯,並且主機必須被授權進行除錯
捕捉

單擊歡迎螢幕中的Capture Trace文字,或者單擊File → Capture Trace工具欄項開啟跟蹤對話方塊。 在這裡插入圖片描述

  1. 選擇要跟蹤的裝置。
  2. 選擇要跟蹤的圖形API。
  3. 選擇Android Activity或瀏覽要跟蹤的應用程式。
  4. 新增程式所需的任何命令列引數。
  5. 為您的程式選擇工作目錄,只適用於在Windows /Linux機器上進行跟蹤。
  6. 設定用於跟蹤程式的環境變數,僅適用於在Windows /Linux機器上進行跟蹤。
  7. 如果希望在N幀後自動停止跟蹤,則使用非零數字停止。
  8. 如果希望在啟動應用程式後立即開始跟蹤,請啟用“從開始”選項。如果未設定此選項,則在跟蹤對話方塊中,必須按“開始”來開始捕獲。
  9. Disable Buffering使跟蹤裝置上的捕獲資料的緩衝無效,這將減慢跟蹤過程。但是,如果發生碰撞,將提供更多的最新資料。
  10. 如果您想在跟蹤之前擦除包快取,請啟用清除包快取選項。
  11. Hide Unknown Extensions 在跟蹤Vulkan呼叫時隱藏GAPID不支援應用程式的Vulkan擴充套件。對於GELS呼叫,它沒有做任何事情。在跟蹤OpenGL ES呼叫時,GAPID總是隱藏未知的副檔名。
  12. 如果跟蹤OpenGL ES應用程式,您可能希望啟用禁用的預編譯著色器選項。此選項不篡改對OpenGL ES的預編譯著色器的驅動程式支援,通常迫使應用程式使用glShaderSource()。GAPID當前無法重放在跟蹤OpenGL ES時使用預編譯著色器的捕獲。此選項在跟蹤Vulkan呼叫時無效。
  13. 選擇輸出目錄。
  14. 選擇輸出檔名。

單擊“OK”開始跟蹤。

例子:

第一步:下載GAPID並安裝。 第二步:開啟GAPID並按照上面的捕捉步驟操作。

在這裡插入圖片描述 在這裡插入圖片描述

在這裡插入圖片描述

從上面圖中可以看到應用程式每一幀的渲染,以及每一幀從CPU上傳到GPU的圖片紋理。如果發現有上傳重複的圖片或者渲染無用的漸變、陰影,可以針對這些進行優化,因為這些都會增加每一幀的渲染時間。

systrace命令允許您在系統級收集並檢查在裝置上執行的所有程序的實時資訊。它將來自Android核心的資料(如CPU排程程式、磁碟活動和應用程式執行緒)組合起來生成HTML報告,類似於圖1所示。 在這裡插入圖片描述 圖1. 一個systrace HTML報表例子,顯示了與應用程式互動的5秒。報告強調了systrace認為可能沒有被正確渲染的幀。

該報告提供了Android裝置在給定時間內的系統程序的總體圖。它還檢查捕獲的跟蹤資訊,以突出它觀察到的問題,如顯示運動或動畫時的UI jank,並提供關於如何修復它們的建議。但是,systrace沒有收集應用程式過程中有關程式碼執行的資訊。有關應用程式正在執行哪些方法和使用多少CPU資源的詳細資訊,請使用Android Studio CPU profiler。您還可以生成跟蹤日誌,並使用CPU profiler匯入和檢查它們。

語法

要為app生成HTML報告,您需要使用以下語法從命令列執行systrace:

$ python systrace.py [options] [categories]

systrace.py 位於 android-sdk/platform-tools/systrace/ 目錄下。在執行命令前,確保你已經安裝了python。

要檢視已連線裝置支援的類別列表,請執行以下命令:

$ python systrace.py --list-categories

在這裡插入圖片描述

如果未指定任何類別或選項,systrace將生成包含所有可用類別的報告並使用預設設定。 可用的類別取決於您使用的連線裝置。

全域性選項
全域性選項 描述
-h | --help 顯示幫助資訊。
-l | --list-categories 列出所連線裝置可用的跟蹤類別。
命令和命令選項
命令和選項 描述
-o file 將HTML跟蹤報告寫入指定的檔案。 如果未指定此選項,systrace會將報表儲存到與systrace.py相同的目錄中,並將其命名為trace.html。
-t N | --time=N 跟蹤裝置活動N秒。 如果未指定此選項,則systrace會提示您通過從命令列按Enter鍵來結束跟蹤。
-b N | --buf-size=N 使用N千位元組的跟蹤緩衝區大小。 此選項允許您限制跟蹤期間收集的資料的總大小。
-k functions | --ktrace=functions 跟蹤在逗號分隔列表中指定的特定核心函式的活動。
-a app-name | --app=app-name 啟用應用程式的跟蹤,指定為以逗號分隔的程序名稱列表。 應用程式必須包含Trace類的跟蹤檢測呼叫。 您應該在配置應用程式時指定此選項,許多庫(例如RecyclerView)包括跟蹤檢測呼叫,這些呼叫在您啟用應用程式級別跟蹤時提供有用資訊。 有關更多資訊,請轉到有關如何檢測應用程式程式碼的部分。
--from-file=file-path 從檔案建立互動式HTML報告,例如包含原始跟蹤資料的TXT檔案,而不是執行實時跟蹤。
-e device-serial--serial=device-serial 在特定連線裝置上進行跟蹤,由裝置序列號標識。
categories 包括您指定的系統程序的跟蹤資訊,例如用於呈現圖形的系統程序的gfx。 您可以使用-l命令執行systrace,以檢視連線裝置可用的服務列表。

調查UI效能問題

systrace對於檢查應用程式的UI效能特別有用,因為它可以分析您的程式碼和幀速率,以識別問題區域並提出可能的解決方案建議。 首先,按以下步驟操作:

  1. 在連線的裝置上執行您的應用。
  2. 使用以下命令執行systrace:
$ python systrace.py -t 10 [other-options] [categories]
  1. 在systrace繼續執行時與您的應用互動。
  2. 在您定義的時間限制結束後,systrace會生成HTML報告。
  3. 使用Web瀏覽器開啟HTML報告。

通過與此報告互動,您可以在記錄期間檢查裝置CPU使用情況。

以下部分介紹瞭如何檢查報表中的資訊以查詢和修復UI效能問題。

檢查幀率和警報

如圖2所示,報表列出呈現UI幀的每個程序,並指示沿著時間線呈現的每個幀。在16.6毫秒內渲染的幀需要保持每秒60幀的穩定性,用綠色幀圓表示。渲染時間大於16.6毫秒的幀用黃色或紅色幀圓表示。

在這裡插入圖片描述 圖2.放大長時間執行幀後的Systrace顯示。

注意:在執行Android 5.0(API級別21)或更高版本的裝置上,渲染幀的工作在UI執行緒和RenderThread之間分配。 在以前的版本中,建立幀的所有工作都在UI執行緒上完成。

單擊框架圓圈會突出顯示它,並提供有關係統完成該幀所做工作的其他資訊,包括警報。 它還會向您顯示系統在渲染該幀時執行的方法,因此您可以調查這些方法以獲取UI jank的原因。

在這裡插入圖片描述 圖3.選擇有問題的幀,跟蹤報告下方會出現一個警報,用於識別問題。

在選擇慢幀之後,您可能會在報告的底部窗格中看到警報。 圖3中顯示的警報呼叫框架的主要問題是在ListView回收和重新繫結中花費了太多時間。 跟蹤中的相關事件有連結,可以更詳細地說明系統在此期間所執行的操作

要檢視工具在跟蹤中發現的每個警報,以及裝置觸發每個警報的次數,請單擊視窗右側的“Alert”選項卡,如圖4所示。Alert面板幫助您瞭解跟蹤中出現哪些問題,以及它們對jank有多頻繁。 將面板視為要修復的錯誤列表。 通常,一個區域中的微小變化或改進可以從應用程式中消除整個類別的警報。

在這裡插入圖片描述

圖4.單擊右側的Alert按鈕顯示警告選項卡。

如果你在UI執行緒上看到太多的工作,你需要找出哪些方法消耗了太多的CPU時間。 一種方法是將跟蹤標記(請參閱儀器您的應用程式程式碼)新增到您認為導致這些瓶頸的方法中,以檢視這些函式呼叫是否出現在systrace中。 如果您不確定哪些方法可能導致UI執行緒出現瓶頸,請使用Android Studio CPU分析器。 您可以使用CPU Profiler生成跟蹤日誌並匯入和檢查它們。

HTML報告鍵盤快捷鍵

下表列出了檢視systrace HTML報告時可用的鍵盤快捷鍵。

Key 描述
W 放大跟蹤時間線。
S 縮小跟蹤時間線。
A 在追蹤時間線上左移。
D 在追蹤時間線上右移。/td>
E 將跟蹤時間軸置於當前滑鼠位置的中心。/td>
G 在當前所選任務的開頭顯示網格。
Shift + G 在當前所選任務的末尾顯示網格。
Right Arrow 選擇當前所選時間軸上的下一個事件。
Left Arrow 在當前選定的時間軸上選擇上一個事件。

檢測應用程式碼

因為systrace只在系統級別顯示關於程序的資訊,所以很難在HTML報告中知道應用程式在給定時間執行了哪些方法。在Android 4.3(API級別18)和更高級別中,可以使用程式碼中的跟蹤類來標記HTML報表中的執行事件。您不需要使用systrace來記錄程式碼來記錄跟蹤,但是這樣做可以幫助您瞭解應用程式程式碼的哪些部分可能對掛起執行緒或UI jank有所貢獻。這種方法與使用Debug類不同——Trace類只是向systrace報告新增標記,而Debug類通過生成.trace檔案幫助您檢查詳細的應用程式CPU使用情況。

要生成包含已檢測跟蹤事件的systrace HTML報告,您需要使用-a或–app命令列選項執行systrace,並指定應用程式的包名稱。

下面的示例程式碼演示如何使用Trace類來標記方法的執行,該方法包括兩個巢狀的程式碼塊:

public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
    ...
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Trace.beginSection(&quot;MyAdapter.onCreateViewHolder&quot;);
        MyViewHolder myViewHolder;
        try {
            myViewHolder = MyViewHolder.newInstance(parent);
        } finally {
            Trace.endSection();
        }
        return myViewHolder;
    }

   @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        Trace.beginSection(&quot;MyAdapter.onBindViewHolder&quot;);
        try {
            try {
                Trace.beginSection(&quot;MyAdapter.queryDatabase&quot;);
                RowItem rowItem = queryDatabase(position);
                mDataset.add(rowItem);
            } finally {
                Trace.endSection();
            }
            holder.bind(mDataset.get(position));
        } finally {
            Trace.endSection();
        }
    }
...
}

注意:beginSection()和endSection()方法需要結對出現,並且在同一個執行緒中執行。