Android性能優化之繪製優化

前言

成爲一名優秀的Android開發,須要一份完備的知識體系,在這裏,讓咱們一塊兒成長爲本身所想的那樣~。

前一段時間,筆者帶你們一塊兒深刻探索Android佈局優化深刻探索Android卡頓優化,內容難度比較大,所以,本篇文章就是上述兩篇文章的基礎篇,掌握這篇文章的知識後,閱讀上面兩篇文章的難度會小不少。html

咱們都知道,形成繪製不流暢最大的罪魁禍首就是卡頓,而卡頓的主要場景有不少,按場景能夠分紅4類:UI繪製、應用啓動、頁面跳轉、事件響應,其中又可細分爲以下:python

一、UI

  • 繪製
  • 刷新

二、啓動

  • 安裝啓動
  • 冷啓動
  • 熱啓動

三、跳轉

  • 頁面間跳轉
  • 先後臺切換

四、響應

  • 按鍵
  • 系統事件
  • 滑動

而形成其產生的根本緣由能夠分爲兩大類:android

一、界面繪製

  • 繪製層級深
  • 頁面複雜
  • 刷新不合理

二、數據處理

  • 數據處理在UI線程
  • 佔用CPU高,致使主線程拿不到時間片
  • 內存增長致使GC頻繁,從而引發卡頓

1、Android系統顯示原理

Android的顯示過程能夠簡單歸納爲:Android應用程序把通過測量、佈局、繪製後的surface緩存數據、經過SurfaceFlinger把數據渲染到顯示屏幕上,經過Android的刷新機制來刷新數據。也就是說應用層負責繪製,系統層負責渲染,經過進程間通訊把應用層須要繪製的數據傳遞到系統層服務,系統層服務經過刷新機制把數據更新到屏幕git

一、繪製原理

應用層

在Android的每一個View都會通過Measure和Layout來肯定當前須要繪製的View所在的大小和位置,而後,再經過Draw繪製到surface上。在Android系統中總體的繪製源碼是在ViewRootImpl類的performTraversals()方法,經過這個方法能夠看出Measure和Layout都是遞歸來獲取View的大小和位置,而且以深度做爲優先級。顯然,層級越深,元素越多,耗時就越長。github

對於繪製,Android支持兩種繪製方式:算法

  • 軟件繪製(CPU)
  • 硬件繪製(GPU)

硬件加速從Android 3.0開始支持,它在UI顯示和繪製效率方面遠高於軟件繪製。但它的侷限以下:shell

  • 耗電:GPU功耗高於CPU。
  • 兼容性:不兼容某些接口和函數。
  • 內存大:使用OpenGL的接口須要佔用內存8MB。

系統層

將數據渲染到屏幕上是經過系統級進程中的SurfaceFlinger服務來實現的,它的主要工做流程以下:json

  • 一、響應客戶端事件,建立Layer與客戶端的Surface創建鏈接
  • 二、接收客戶端數據和屬性,修改Layer屬性,如尺寸、顏色、透明度等
  • 三、將建立的Layer內容刷新到屏幕上
  • 四、維持Layer的序列,並對Layer的最終輸出作裁剪計算

其中,SurfaceFlinger系統進程和應用進程使用了匿名共享內存SharedClient,而且,每個應用和SurfaceFlinger之間都會建立一個SharedClient,在每一個SharedClient中,最多能夠建立31個SharedBufferStack,每個SharedBufferStack對應一個Surface,即一個window。(其中包含了兩個(小於4.1版本)或者三個(4.1及以上版本)緩衝區)canvas

所以,從上可知,一個Android應用程序最多能夠包含31個窗口。最後,顯示的總體流程以下:緩存

  • 一、應用層繪製到緩衝區
  • 二、SurfaceFlinger把緩衝區數據渲染到屏幕,其中使用了Android匿名共享內存SharedClient緩存須要顯示的數據來達到目的

繪製的過程首先是CPU準備數據,經過Driver層把數據交給CPU渲染,其中CPU主要負責Measure、Layout、Record、Execute的數據計算工做,GPU負責Rasterization(柵格化)、渲染。由於圖形API不容許CPU直接和GPU通訊,因此要經過一個圖形驅動的中間層來進行鏈接,在圖形驅動裏面維護了一個隊列,CPU把display list(待顯示的數據列表)添加到隊列中,GPU從這個隊列中取出數據進行繪製,最終纔在顯示屏上顯示出來

Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,若是每次渲染都成功,這樣就可以達到流暢畫面所需的60FPS。

二、刷新機制

在4.1版本的Project Butter中對Android Display系統進行了重構,引入了三個核心元素:VSYNC(Vertical Synchronization)、Triple Buffer(三級緩衝)、Choreographer。其中做爲Project Buffer核心的VSYNC,即垂直同步可認爲是一種定時中斷而Choreographer起調度的做用,將繪製工做統一到VSYNC的某個時間點上,使應用的繪製工做有序

那麼,爲何要推出Project Butter呢?

目的是解決刷新不一樣步的問題。

在Tripe Buffer出現以前,Android的顯示系統採用的是雙緩衝技術。

爲何要使用雙緩衝技術?

在Linux上一般使用 Framebuffer 來作顯示輸出,當用戶進程更新Framebuffer中的數據後,顯示驅動會把FrameBuffer中每一個像素點的值更新到屏幕,可是若是上一幀數據還沒顯示完,Framebuffer中的數據又更新了,就會帶來殘影的問題,用戶會以爲有閃爍感,因此採用了雙緩衝技術

雙緩衝的含義?

雙緩衝意味着要使用兩個緩衝區(在上文說起的SharedBufferStack中),其中一個稱爲Front Buffer,另外一個稱爲Back Buffer。UI老是先在Back Buffer中繪製,而後再和Front Buffer交換,渲染到顯示設備中。即只有當另外一個buffer的數據準備好後,纔會經過io_ctl系統調用來通知顯示設備切換Buffer

當第一幀數據沒有及時處理時,爲何CPU不能在第二個16ms處即VSync到來就開始工做呢?

由於只有兩個Buffer;因此4.1版本後,出現了第三個緩衝區:Triple Buffer。它利用CPU/GPU的空閒等待時間提早準備好數據,並不必定會使用

注意

除非必要,大部分狀況下只是用到雙緩衝。並且,緩衝區並非越多越好,要作到平衡到最佳效果。

Google作了這麼多的優化,爲何實際開發中應用還存在卡頓現象?

由於VSync 中斷處理的線程優先級必定要最高,不然即便接收到VSync中斷,不能及時處理,也是徒勞無功。

Choreographer的做用是什麼?

當收到VSYNC信號時,調用用戶設置的回調函數。回調類型的優先級從高到低爲CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL

三、卡頓的根本緣由

  • 繪製任務過重、繪製一幀內容耗時太長。
  • 主線程太忙,致使VSync信號到來時尚未準備好數據從而致使丟幀。

2、性能分析工具

Android經常使用的繪製優化工具通常有以下幾種:

  • Hierarchy View:查看Layout層次
  • Android Studio自帶的Profile CPU工具
  • 靜態代碼檢查工具Lint
  • Profile GPU Rendering
  • TraceView
  • Systrace

這裏咱們來說解後面三種分析工具。

一、卡頓檢測工具Profile GPU Rendering

它是Android手機上自帶的一個輔助工具,打開Profile GPU Rendering後能夠看到實時刷新的彩色圖,其中每一根豎線表示一幀,由多個顏色組成。

Android M以前

在Android M版本以前,每一條柱狀圖都由紅、黃、藍、紫組成,分別對應每一幀在不一樣階段的實際耗時不一樣顏色的解釋以下:

  • 藍色:表示測量繪製的時間,須要多長時間去建立和更新DisplayList。在藍色的線很高時,有多是由於須要從新繪製,或者自定義視圖的onDraw函數處理事情太多
  • 紅色:表示Android進行2D渲染Display List的執行的時間。當紅色的線很是高時,多是因爲從新提交了視圖致使的
  • 橙色:處理時間或CPU告訴GPU渲染一幀的地方,若是柱狀圖很高,就意味着GPU太繁忙了
  • 紫色:將資源轉移到渲染線程的時間。(4.0版本以上提供)

Android M以後

而且,從Android M開始變成了渲染八步驟:

一、橙色-Swap Buffers

表示GPU處理任務的時間。

二、紅色-Command Issue

進行2D渲染顯示列表的時間,越高表示須要繪製的視圖越多。

三、淺藍-Sync&Upload

準備有待繪製的圖片所耗費的時間,越高表示圖片數量越多或圖片越大。

四、深藍-Draw

測量和繪製視圖所需的時間,越高表示視圖越多或onDraw方法有耗時操做。

五、一級綠-Measure/Layout

onMeasure與onLayout所花費的時間。

六、二級綠-Animation

執行動畫所須要花費的時間。越高表示使用了非官方動畫工具或執行中有讀寫操做。

七、三級綠-Input Handling

系統處理輸入事件所耗費的時間。

八、四級綠-Misc Time/Vsync Delay

主線程執行了太多任務,致使UI渲染跟不上vSync的信號而出現掉幀。

此外,可經過以下adb命令將具體的耗時輸出到日誌中來分析:

adb shell dumpsys gfxinfo com.**.** 
複製代碼

二、TraceView

它主要用來分析函數的調用過程,能夠對Android的應用程序以及Framework層代碼進行性能分析。

使用TraceView查看耗時,主要關注Calls + Recur Calls / Total和(該方法調用次數+遞歸次數)和Cpu Time / Call(該方法耗時)這兩個值,而後優化這些方法的邏輯和調用次數,減小耗時

注意

RealTime(實際時長)的實際執行時間要比CPU Time要長,由於它包括了CPU的上下文切換、阻塞、GC等時間消耗。

三、Systrace UI性能分析

Systrace是Android 4.1及以上版本提供的性能數據採樣和分析工具,它的主要做用能夠歸結爲以下兩點:

  • 一、收集Android關鍵子系統(如surfaceflinger、WindowManagerService等Framework部分關鍵模塊、服務、View系統等)的運行信息,這樣能夠更直觀地分析系統瓶頸,改進性能
  • 二、跟蹤系統的I/0操做、內核工做隊列、CPU負載等,在UI顯示性能分析上提供很好的數據,特別是在動畫播放不流暢、渲染卡頓等問題上

一、Systrace使用方法

使用事項以下:

  • 支持4.1版本及以上。
  • 4.3之前的系統版本須要打開Setting>Developer options>Monitoring>Enable traces。

通常咱們使用命令行來獲得輸出的html表單,在4.3版本及以上能夠省略設置跟蹤類別標籤來獲取默認值。命令以下:

cd android-sdk/platform-tools/systrace
python systrace.py --time=10 -o mynewtrace.html sched gfx view wm
複製代碼

其中,經常使用的幾個參數命令以下:

  • -o :保存的文件名。
  • -t N, --time=N:多少秒內的數據,默認爲5s,以當前時間點日後倒N秒時間。

其他標籤用法請參見此處

此外,咱們可使用代碼插樁的方式,在Android 4.3及以上版本可使用Trace類的Trace.beginSection()與Trace.endSection()方法來進行追蹤。其中須要注意:

  • 一、保證beginSection和endSection的調用次數要匹配。
  • 二、Trace的begin與end必須在同一線程中執行。

二、分析Systrace報告

使用Chrome打開文件後,其中和UI繪製關係最密切的是Alerts和Frame兩個數據:

  • Alerts:標記了性能有問題的點,單擊該點能夠查看詳細信息,右側的Alerts框還能夠看到每一個類型的Alerts的數量。
  • Frame:每一個應用都有一行專門顯示frame,繪製正常時每一幀就顯示爲一個綠色的圓圈。當顯示爲黃色或者紅色時,則代表它的渲染時間超過了16.6ms

最後,這裏再列出在Systrace便於操做的快捷鍵:

  • W:放大
  • S:縮小
  • A:左移
  • D:右移

3、佈局優化方式

一、減小層級

  • 合理使用RelativeLayout和LinearLayout。
  • 合理使用Merge。

合理使用RelativeLayout和LinearLayout

RelativeLayout也存在性能低的問題,緣由是RelativeLayout會對子View作兩次測量。但若是在LinearLayout中有weight屬性,也須要進行兩次測量,可是由於沒有更多的依賴關係,因此仍然會比RelativeLayout的效率高。

注意

因爲Android的碎片化程度很高,因此使用RelativeLayout能使構建的佈局適應性更強。

合理使用Merge

merge的原理:在Android佈局的源碼中,若是是Merge標籤,那麼直接將其中的子元素添加到Merge標籤Parent中。

注意

  • 一、Merge只能用在佈局XML文件的根元素。
  • 二、使用merge來加載一個佈局時,必須指定一個ViewGroup做爲其父元素,而且要設置加載的attachToRoot參數爲true。
  • 三、不能在ViewStub中使用Merge標籤。緣由就是ViewStub的inflate方法中根本沒有attachToRoot的設置。

二、提升顯示速度

ViewStub是一個輕量級的View,它是一個看不見的,而且不佔佈局位置,佔用資源很是小的視圖對象。能夠爲ViewStub指定一個佈局,加載佈局時,只有ViewStub會被初始化,而後當ViewStub被設置爲可見時,或是調用了ViewStub.inflate()時,ViewStub所指向的佈局纔會被加載和實例化,而後ViewStub的佈局屬性都會傳給它指向的佈局

注意:

  • 一、ViewStub只能加載一次,以後ViewStub對象會被置爲空。因此它不適用於須要按需顯示隱藏的狀況。
  • 二、ViewStub只能用來加載一個佈局文件,而不是某個具體的View。
  • 三、ViewStub中不能嵌套Merge標籤。

三、佈局複用

Android的佈局複用能夠經過 include 標籤來實現。

四、小結

最後,下面列出了我日常作佈局優化時的一些小技巧:

  • 使用標籤加載一些不經常使用的佈局。
  • 儘量少用wrap_content,wrap_content會增長佈局measure時的計算成本,已知寬高爲固定值時,不用wrap_content。
  • 使用TextView替換RL、LL。
  • 使用低端機進行優化,以發現性能瓶頸。
  • 使用TextView的行間距替換多行文本:lineSpacingExtra/lineSpacingMultiplier。
  • 使用Spannable/Html.fromHtml替換多種不一樣規格文字。
  • 儘量使用LinearLayout自帶的分割線。
  • 使用Space添加間距。
  • 多利用lint + alibaba規約修復問題點。
  • 嵌套層級過多能夠考慮使用約束佈局。

4、避免過分繪製

致使過分繪製的主要緣由通常有以下兩點:

  • XML佈局:控件有重疊且都有設置背景。
  • View自繪:View.OnDraw裏面同一個區域被繪製屢次。

一、過分繪製檢測工具

打開手機開發者選項中的Show GPU Overdraw選項,會有不一樣的顏色來表示過分繪製次數,依次是無、藍、綠、淡紅、深紅,分別對應0-4次過分繪製。

二、如何避免過分繪製

一、佈局上的優化

  • 移除XML中非必需的背景,或根據條件設置。
  • 有選擇性地移除窗口背景:getWindow().setBackgroundDrawable(null)。
  • 按需顯示佔位背景圖片。

好比:在獲取Avatar的圖像以後,把ImageView的Background設置爲Transparent,只有當圖像沒有獲取到時,才設置對應的Background佔位圖片。

二、自定義View優化

經過canvas.clipRect()來幫助系統識別那些可見的區域。這個方法能夠指定一塊矩形區域,只有在這個區域內纔會被繪製。而且,它還能夠節約CPU和GPU資源,在clipRect區域以外的繪製指令都不會被執行。

在繪製一個單元以前,首先判斷該單元的區域是否在Canvas的剪切域內。若不在,直接返回,避免CPU和GPU的計算和渲染工做。

5、合理的刷新機制

一、減小刷新次數

  • 控制刷新頻率
  • 避免沒有必要的刷新

二、避免後臺線程的影響

如經過監聽ListView的onScrollStateChanged事件,在滾動時暫停圖片下載線程工做,結束後再開始,能夠提升ListView的滾動平滑度,RecyclerView同理。

三、縮小刷新區域

如自定義View通常採用invalidate方法刷新,可使用如下重載方法指定要刷新的區域:

  • invalidate(Rect dirty);
  • invalidate(int left, int top, int right, int bottom);

6、提高動畫性能

提高動畫性能主要從如下三個緯度着手:

  • 一、流暢度:控制每一幀動畫在16m內完成。
  • 二、內存:避免內存泄漏,減少內存開銷。
  • 三、耗電:減少運算量,優化算法,減少CPU佔用。

一、幀動畫

消耗資源最多,效果最差,能不用就不用。

二、補間動畫

使用補間動畫實現致使View重繪很是頻繁,更新DisplayList的次數過多,且有如下缺點:

  • 一、只能用於View對象。
  • 二、只有4種動畫操做。
  • 三、只是改變View的顯示效果,可是不會真正改變View的屬性。

三、屬性動畫

相比於補間動畫,屬性動畫重繪明顯會少不少,應優先使用。

四、使用硬件加速

一、硬件加速原理

核心類:DisplayList,每個View對應一個。

在打開硬件渲染後繪製View時,其中執行繪製的draw()方法會把全部繪製命令記錄到一個新的顯示列表(DisplayList),這個顯示列表包含了輸出的View層級的繪製代碼,但並非加入到顯示列表就馬上執行,當這個ViewTree的DisplayList全都記錄完畢後,由OpenGLRender負責將Root View中的DisplayList渲染到屏幕上。而invalidate()方法只是在顯示列表中記錄和更新顯示層級,去標記不須要繪製的View

二、硬件加速控制級別

若是應用程序中只使用了標準View或者Drawable,就能夠爲整個系統打開硬件加速的全局設置。

三、在動畫上使用硬件加速

此時,會使用硬件紋理操做對一個View進行動畫繪製,若是不調用invalidate()方法,就能夠減小對View自身頻繁的重繪。同時Android 3.0的屬性動畫也減少了重繪,當View經過硬件層返回時,最終全部的層疊畫面顯示到屏幕,View的屬性同時被處理好,所以只要設置這些屬性,就能夠明顯提升繪製的效率,它們不須要View重繪,設置屬性後,View會自動刷新。所以,屬性動畫中繪製的遞歸次數比補間動畫少不少。

在Android 3.0前,使用View的繪製緩衝或Canvas.saveLayer()函數對離屏緩衝進行渲染。Android 3.0後則使用View.setLayerType(type, paint)方法代替,type能夠爲如下三種Layer類型之一:

  • LAYER_TYPE_NONE:普通渲染方式,不會返回一個離屏的緩衝,默認值。
  • LAYER_TYPE_HARDWARE:若是這個應用使用了硬件加速,這個View將會在硬件中渲染爲硬件紋理。
  • LAYER_TYPE_SOFTWARE:此View經過軟件渲染爲一個Bitmap。

設計一個動畫的流程以下:

一、將要執行動畫的View的LayerType設置爲LAYER_TYPE_HARDWARE。

二、計算動畫View的屬性等信息,更新View的屬性。

三、若動畫結束,將LayerType設置爲NONE。

硬件加速須要注意的問題:

  • 在軟件渲染時,可使用重用Bitmap的方法來節省內存,可是若是開起來硬件加速,這個方案就不起做用。
  • 開啓硬件加速的View在前臺運行時,須要耗費額外的內存,加速的UI切換到後臺時,產生的額外內存有可能不釋放。
  • 當UI中存在過渡繪製時,硬件加速會比較容易發生問題。

7、卡頓監控方案與實現

目前比較流行的方案都是利用了Looper中的Printer來實現監控。

一、監控原理

利用主線程的消息隊列處理機制,經過自定義Printer,而後在Printer中獲取到兩次被調用的時間差,這個時間差就是執行時間。若是該時間超過設定的卡頓閾值(如1000ms)時,主線程卡頓發生,並拋出各類有用信息,供開發者分析。(此外,也能夠在UI線程之外開啓一個異步線程,定時向UI線程發送一個任務,並記下發送時間。任務的內容是將執行時間同步到發送線程,若是UI線程被阻塞,那麼發送過去的任務不能被準時執行。但此方法會增長系統開銷,不可取)

卡頓信息捕獲

發生卡頓時須要捕獲以下四類信息,以提升定位卡頓問題的效率與精度。

  • 一、基礎信息:系統版本、機型、進程名、應用版本號、磁盤空間、UID等。
  • 二、耗時信息:卡頓開始和結束時間。
  • 三、CPU信息:CPU的信息、總體CPU使用率和本進程CPU使用率(可粗略判斷是當前應用消耗CPU資源太多致使的卡頓,仍是其餘緣由)等。
  • 四、堆棧信息。

注意

這裏的信息建議抽樣上報或者能夠先將其保存到本地,在合適的時機以及達到必定的量時,再壓縮上報到服務器,供開發者分析。具體監控代碼實現能夠參考BlockCanary開源項目的代碼。

8、總結

至此,這裏咱們分析一下繪製優化應經歷的幾個過程:

  • 一、發現問題:除使用時感知的卡頓外,還應經過卡頓監控工具來發現總體的耗時狀況,或打開開發者選項的一些輔助工具來發現問題。
  • 二、分析問題:可使用Systrace和TraceView來分析耗時,使用Hierarhy Viewer來分析頁面層級。
  • 三、尋求緣由:深刻探索致使問題的根本緣由。
  • 四、解決問題。

應用之因此會出現卡頓,除了繪製方面的問題,還有一個影響因素就是內存,不合理地使用內存不只會致使卡頓,還會對耗電和應用的穩定性形成很大影響。下一篇性能優化文章,筆者將對Android中的內存優化進行全面的講解,若讀者以爲哪裏有寫的很差的地方或有誤的地方但願多多進行批評指正,願咱們共同進步和成長!

參考連接:

一、Android應用性能優化最佳實踐

二、必知必會 | Android 性能優化的方面方面都在這兒

Contanct Me

● 微信:

歡迎關注個人微信:bcce5360

● 微信羣:

微信羣若是不能掃碼加入,麻煩你們想進微信羣的朋友們,加我微信拉你進羣。

● QQ羣:

2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~

About me

很感謝您閱讀這篇文章,但願您能將它分享給您的朋友或技術羣,這對我意義重大。

但願咱們能成爲朋友,在 Github掘金上一塊兒分享知識。。

相關文章
相關標籤/搜索