Android性能優(yōu)化系列篇UI優(yōu)化
前言
從網(wǎng)上匯總搜集眾多大佬的性能優(yōu)化文章,整理出來部分知識點,主要包含:
UI優(yōu)化/啟動優(yōu)化/崩潰優(yōu)化/卡頓優(yōu)化/安全性優(yōu)化/弱網(wǎng)優(yōu)化/APP深度優(yōu)化等等等~
本篇是第一篇:UI優(yōu)化! [非商業(yè)用途,如有侵權(quán),請告知我,我會刪除]
一、UI優(yōu)化
UI優(yōu)化知識點主要分為三部分:
- 第一部分,系統(tǒng)為我們做的優(yōu)化。由于前端中UI展示的特殊性和重要性,Android團隊也是在不斷想辦法提高UI方面的渲染速度,所以也是更新了很多系統(tǒng)優(yōu)化方案,比如:
硬件加速、黃油計劃、RenderThread。
- 第二部分,我們可以具體實施的優(yōu)化方案。主要包括:
java代碼布局、View重用、異步創(chuàng)建View、xml布局優(yōu)化、異步布局框架Litho、屏幕適配、Flutter、Jetpack Compose
- 第三部分,工具使用,主要包括:
Choreographer、monitor、Systrace
1.1 系統(tǒng)做的優(yōu)化
1.1.1 硬件加速
之前我們說過,一個圖形的繪制是CPU,GPU和屏幕三方合作的結(jié)果。
在Android3.0
之前,還沒有硬件加速,都是通過CPU進行數(shù)據(jù)計算,然后通過Skia庫進行軟件繪制,但是CPU對于圖形處理并不高效。
于是從3.0開始,Android
支持了硬件加速,到Android4.0
默認(rèn)開啟硬件加速。
開啟硬件加速后,就是由CPU進行圖形緩存數(shù)據(jù)的繪制。這樣CPU和GPU就能比較好的分工,各司其職了。CPU
用于控制復(fù)雜繪制邏輯、構(gòu)建或更新DisplayList(基礎(chǔ)元素);GPU
用于完成圖形計算、渲染DisplayList(基礎(chǔ)元素)。
這里也找了一張各種場景下,硬件加速前后的流程與加速效果(Android6.0背景):
但是硬件加速也是有缺點的:
- 啟用硬件加速需要更多資源,因此應(yīng)用會占用更多內(nèi)存。
- 比較低的版本,由于有些
Canvas API
還沒有支持,所以使用硬件加速可能會有問題。那么我們就可以手動關(guān)閉某個view的硬件加速:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
Project Butter
黃油計劃,你有可能沒怎么聽說,但是其實之前兩章內(nèi)容都提到過,Android4.1
之后,Google提出了黃油計劃,主要包括兩個內(nèi)容:
- VSYNC
- Triple Buffering(三重緩存)
這些都熟悉了吧,上兩節(jié)都說過的,這里再簡單提一下:
- VSYNC
垂直同步信號,每當(dāng)收到這個信號后,CPU就開始準(zhǔn)備Buffer數(shù)據(jù),并在16ms之內(nèi)和GPU把屏幕需要的緩存數(shù)據(jù)準(zhǔn)備好。
- Triple Buffering(三重緩存)
在Android4.1
之前,是雙緩存機制,大部分是沒問題的。但是當(dāng)CPU/GPU
繪制過程較長,超過一個vsync信號周期,一般是16ms,就會導(dǎo)致丟幀,CPU無法使用被GPU或者屏幕占用的緩存區(qū)。如果下一幀繪制如果又超時,那么又會丟幀。
所以再加上一個緩存區(qū),這樣,CPU、GPU、Display
三者都有各自的緩存區(qū),互不影響,就能保證時間的最大化利用,也就能減少上述的情況了。
RenderThread
RenderThread
是在Android5.0
提出的,從這個名字就能知道,它是一個線程,一個專門執(zhí)行GL命令
的線程,也就是一部分的繪制工作。
有了它之后,當(dāng)CPU
處理數(shù)據(jù)給GPU
后,就不需要等GPU
渲染完畢了,而是將一些繪制任務(wù)交給RenderThread
,這樣就能減少主線程的工作,保證畫面的流暢。同時還提供了RenderNode
,用來做view的屬性封裝,渲染幀的信息等等。
1.2 優(yōu)化方案
1.2.1 java代碼布局
我們一般都是用XML文件
進行布局,但是XML解析也是很耗時的,并在這個解析過程在主線程進行。
所以我們有的時候也許可以通過Java
代碼或者kotlin
進行View
的創(chuàng)建?
理論中,這樣確實能減少布局加載的消耗時間,但是Java代碼創(chuàng)建View
太麻煩了,而且無法可視化。
當(dāng)然,也有一些庫可以幫助我們將xml
代碼轉(zhuǎn)換成java
代碼,比如X2C(github.com/iReaderAndr… ),但是它并不支持所有的情況,比如merge
標(biāo)簽,appCompat
兼容控件等等。
所以我們需要在這之中找到平衡點,有的時候,UI簡單并且要求高性能的前提下,我們可以試試用這樣的方法,即用java代碼代替XmL代碼。
1.2.2 View重用
參照Recyclerview的做法,我們也可以將一些常用的view保存到緩存池中,這樣在不同的界面中就能復(fù)用緩存池里面的view。
1.2.3 異步創(chuàng)建view
這是Google提出的一個方案——AsyncLayoutInflater
。它可以異步加載布局文件,并且回調(diào)給主線程,從而減少主線程耗時。簡單貼下主要代碼:
new AsyncLayoutInflater(MainActivity.this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() { @Override public void onInflateFinished(@NonNull View view, int i, @Nullable ViewGroup viewGroup) { //回調(diào)給主線程 setContentView(view); } });
1.2.4 xml布局優(yōu)化
在寫xml布局文件的時候,我們要做的也有很多,比如:
- 減少布局嵌套。多使用ViewStub、Merge、ConstraintLayout來代替。
- 優(yōu)化開銷。RelativeLayout和 使用weight的LinearLayout 開銷比較大,建議使用ConstraintLayout,LinearLayout代替。
1.2.5 異步布局框架Litho
Litho是Facebook開源的一款在Android上高效建立UI的聲明式框架。
主要有以下特點:
1)聲明式:它使用了聲明式的API來定義UI組件。
2)異步布局:它把 measure
和 layout
都放到了后臺線程,只留下了必須要在主線程完成的 draw,這大大降低了 UI 線程的負載
3)視圖扁平化:由于 Litho
使用了自有的布局引擎(Yoga),在布局階段就可以檢測不必要的層級、減少 ViewGroups,來實現(xiàn) UI 扁平化。
4)優(yōu)化 RecyclerView:Litho 還優(yōu)化了 RecyclerView 中 UI 組件的緩存和回收方法。
1.2.6 屏幕適配
關(guān)于屏幕適配問題,也是老生常談了。主要有以下幾種方案:
- dp適配方案。
這是系統(tǒng)自帶的適配單位,dp是基于屏幕物理分辨率一個抽象的單位,用于說明與密度無關(guān)的尺寸和位置。所以它能在不同分辨率的手機上有相對大小的適配性。計算公式是:px=dp * (dpi/160)
。但是dpi有可能會被人為調(diào)整(比如幾部相同分辨率不同尺寸的手機的ppi可能分別是是430,440,450
,那么在Android系統(tǒng)中,可能dpi會全部指定為480
),所以還是有可能在一些設(shè)備上出現(xiàn)適配問題。
- 寬高限定符適配方案。
簡單地說,這個方案就是窮舉
市面上所有的Android手機的寬高像素值。然后找到對應(yīng)的文件夾使用下面的資源文件所對應(yīng)的px值。
但是這方案有個缺陷,就是必須精確命中才行。比如1920x1080
的手機就一定要找到1920x1080
的限定符,否則就只能用統(tǒng)一的默認(rèn)的dimens
文件了。
所以容錯性太低,不推薦。
- smallestWidth適配方案。
這個方案就是通過手機的寬度值來找到對應(yīng)限定符文件夾下的資源文件,可以看做寬高限定符屏幕適配方案的升級版。
假如我們的設(shè)計圖寬為360dp
,那么就創(chuàng)建values-sw360dp
文件夾,并添加資源文件:
<?xml version="1.0" encoding="UTF-8"?> <resources> <dimen name="dp_1">1dp</dimen> <dimen name="dp_2">2dp</dimen> <dimen name="dp_3">3dp</dimen> ... <dimen name="dp_359">359dp</dimen> <dimen name="dp_360">360dp</dimen> </resources>
很簡單,分為360份
,然后我們實際寫布局文件的時候,直接引用對應(yīng)的dimen值即可。
然后新建其他設(shè)備寬度的文件夾,并在每個文件夾里添加對應(yīng)的資源文件,這里以400dp為例:
├── src/main │ ├── res │ ├── ├──values │ ├── ├──values-sw320dp │ ├── ├──values-sw400dp │ ├── ├──... │ ├── ├──values-sw640dp
<?xml version="1.0" encoding="UTF-8"?> <resources> <dimen name="dp_1">1.1111dp</dimen> <dimen name="dp_2">2.2222dp</dimen> <dimen name="dp_3">3.3333dp</dimen> <dimen name="dp_4">4.4444dp</dimen> ... <dimen name="dp_359">398.8889dp</dimen> <dimen name="dp_360">400.0000dp</dimen> </resources>
也就是說,所有的設(shè)備都分為360份
了,這樣就能保證在不同寬度設(shè)備上都能有差不多的效果。
如果我們的設(shè)備寬度為400dp
,那么就會調(diào)用values-sw400dp
對應(yīng)的資源文件,如果找不到,就會向下查找。比如我們寬度是402dp
,找不到對應(yīng)的,就會向上找到400dp
對應(yīng)的資源文件,所以也有比較好的容錯性。也是一個比較好的適配方案。
當(dāng)然這種重復(fù)性工作肯定不需要我們自己手動去實現(xiàn),有專門的插件可以生成相應(yīng)的文件和文件夾,這里也推薦一個:github.com/ladingwu/di…
- 今日頭條適配方案。
這個大家應(yīng)該都很熟悉了,主要是通過動態(tài)修改density值
來保證所有設(shè)備的屏幕寬度都是固定的dp值。用到的公式就是px = density * dp
。
比如設(shè)計圖寬為360dp
,我們只要保證所有設(shè)備的寬度都是360dp
就能適配了。而寬度的px值我們是已知的,所以就是要修改這個 density
值來完成我們的適配目的。具體代碼我就不貼了,網(wǎng)上很多。
這種方案侵入性低,使用方便,是個不錯的適配方案。
1.2.7 Flutter
Flutter是 Google 推出并開源的移動應(yīng)用開發(fā)框架,開發(fā)者可以通過 Dart 語言開發(fā) App,一套代碼同時運行在 iOS 和 Android 平臺。
Flutter
框架現(xiàn)在也是特別火,實際運用到很多的大廠項目,比如閑魚今日頭條。它相對于Android其實是另外一套APP架構(gòu)了,它沒有基于系統(tǒng)本身的渲染引擎,而是app中自帶Skia
引擎,虛擬機也是使用的Dart虛擬機。
主要有以下幾個特點:
跨平臺
:現(xiàn)在flutter至少可以跨5種平臺,常見的平臺:MacOS,Windows ,Linux ,Android ,iOS ,到目前為止,F(xiàn)lutter算是支持平臺最多的框架了。良好的跨平臺性,大大減少了開發(fā)成本。絲滑般的體驗
:使用Flutter內(nèi)置的Material Design(android風(fēng)格)和Cupertino(ios風(fēng)格)風(fēng)格組件,以及豐富的motion API,平滑而自然的滑動效果和平臺感知,為用戶帶來全新的體驗。響應(yīng)式框架
:使用一系列基礎(chǔ)組件和響應(yīng)式框架,可以輕松構(gòu)建用戶界面。使用功能強大且靈活的API可以實現(xiàn)復(fù)雜的界面效果。支持插件
:使用插件可以訪問平臺本地API,如相機,藍牙,WIFI等等。借助現(xiàn)有的Java,swift ,object c , c++代碼實現(xiàn)對原生系統(tǒng)的調(diào)用。60fps超高性能
:Flutter編寫的應(yīng)用可以達到60fps(每秒傳輸幀數(shù))。Flutter采用GPU渲染技術(shù),所以性能很好。完全可以勝任游戲開發(fā)。
1.2.8 Jetpack Compose
Jetpack Compose 是用于構(gòu)建原生 Android 界面的新工具包
原來我們的布局文件都是寫在xml
文件中的,現(xiàn)在提供了一種新的view構(gòu)建方式,也就是Compose
。
它是一種聲明式UI
,不再使用xml,而是使用kotlin
進行UI布局。其實就跟我們之前提到的一點,用java代碼去構(gòu)建view一樣的效果。這樣就減少了xml解析
的時間,提高了效率。
聲明式UI。指的是只需要把界面聲明出來,不需要手動更新。比如我們這里的Compose只需要寫一遍,后續(xù)的UI改變會隨著變量自動更新。而傳統(tǒng)的xml布局方式就無法做到這一點,屬于命令式UI,需要我們手動命令紙牌屋UI的修改。
官方也是宣稱有以下幾點優(yōu)勢:
更少更直觀的代碼,更強大的功能,能提高開發(fā)速度。
最后貼一段代碼,感受下Compose
的寫法:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Greeting("Android") } } } ? @Composable fun Greeting(name: String) { Text (text = "Hello $name!") }
復(fù)制
1.3 工具篇
1.3.1 Choreographer
Choreographer
其實也是一個監(jiān)控應(yīng)用幀率的工具。它主要有以下特性:
- 能獲取整體的幀率。
- 能在線上使用。
- 獲取的幀率幾乎是實時的。
主要原理就是利用postFrameCallback
計算兩次繪制的間隔時間,簡單貼下代碼:
private long mLastFrameTime; Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { if (mLastFrameTime == 0) { mLastFrameTime = frameTimeNanos; } float diff = (frameTimeNanos - mLastFrameTime) / 1000000.0f;//得到毫秒,正常是 16.66 ms if (diff > 500) { double fps = (((double) (mFrameCount * 1000L)) / diff); mFrameCount = 0; mLastFrameTime = 0; Log.d("doFrame", "doFrame: " + fps); } else { ++mFrameCount; } Choreographer.getInstance().postFrameCallback(this); } });
想細細研究的可以看看這個庫(github.com/friendlyrob… )
1.3.2 LayoutInspector/Android Device Monitor
LayoutInspector
是AndroidStudio
種的一個布局檢查器,可以通過Tools > Layout Inspector
找到,他可以檢查應(yīng)用中的某個界面的視圖結(jié)構(gòu),但是無法查看非調(diào)式狀態(tài)的應(yīng)用。
如果要看其他應(yīng)用的布局情況,可以使用Android Device Monitor
,在Android Studio 3.1
以后,需要單獨從文件夾打開了:
android-sdk/tools/monitor
1.3.3 Systrace
Systrace
是分析Android
性能問題的神器,獲取Systrace文件的方式有兩種:
- 一是
AndroidSDK/tools
目錄下,通過monitor.bat
用Android Device Monitor
可視化工具得到。 - 二是通過
python
腳本獲取。
以上就是Android性能優(yōu)化系列篇UI優(yōu)化的詳細內(nèi)容,更多關(guān)于Android性能UI優(yōu)化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Android Webview加載網(wǎng)頁時發(fā)送HTTP頭信息
這篇文章主要介紹了詳解Android Webview加載網(wǎng)頁時發(fā)送HTTP頭信息的相關(guān)資料,需要的朋友可以參考下2017-05-05Android實現(xiàn)擴大View點擊區(qū)域的三種方式
在 Android 應(yīng)用開發(fā)中,有時候需要擴大 View 的點擊區(qū)域以提高用戶交互的便利性,尤其是當(dāng)視圖元素較小或用戶界面密集時,以下提供幾種擴大點擊區(qū)域的思路,感興趣的小伙伴跟著小編一起來看看吧2024-08-08Android中ImageView.src設(shè)置圖片拉伸、填滿控件的方法
最近公司有個需求,要展示客戶公司的企業(yè)形象,用一張圖片放在ImageView中實現(xiàn),但是發(fā)現(xiàn)圖片并沒有填滿,而是在上下邊上留出了一點空白,下面這篇文章主要跟大家介紹了Android中ImageView.src設(shè)置圖片拉伸、填滿控件的方法,需要的朋友可以參考下。2017-06-06Android ActionBar完全解析使用官方推薦的最佳導(dǎo)航欄(上)
Action Bar是一種新増的導(dǎo)航欄功能,在Android 3.0之后加入到系統(tǒng)的API當(dāng)中,它標(biāo)識了用戶當(dāng)前操作界面的位置,并提供了額外的用戶動作、界面導(dǎo)航等功能2017-04-04