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