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團(tuán)隊也是在不斷想辦法提高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進(jìn)行數(shù)據(jù)計算,然后通過Skia庫進(jìn)行軟件繪制,但是CPU對于圖形處理并不高效。
于是從3.0開始,Android支持了硬件加速,到Android4.0默認(rèn)開啟硬件加速。
開啟硬件加速后,就是由CPU進(jìn)行圖形緩存數(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文件進(jìn)行布局,但是XML解析也是很耗時的,并在這個解析過程在主線程進(jìn)行。
所以我們有的時候也許可以通過Java代碼或者kotlin進(jìn)行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 線程的負(fù)載
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,如相機,藍(lán)牙,WIFI等等。借助現(xiàn)有的Java,swift ,object c , c++代碼實現(xiàn)對原生系統(tǒng)的調(diào)用。60fps超高性能:Flutter編寫的應(yīng)用可以達(dá)到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進(jìn)行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);
}
});
想細(xì)細(xì)研究的可以看看這個庫(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)化的詳細(xì)內(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-05
Android實現(xiàn)擴(kuò)大View點擊區(qū)域的三種方式
在 Android 應(yīng)用開發(fā)中,有時候需要擴(kuò)大 View 的點擊區(qū)域以提高用戶交互的便利性,尤其是當(dāng)視圖元素較小或用戶界面密集時,以下提供幾種擴(kuò)大點擊區(qū)域的思路,感興趣的小伙伴跟著小編一起來看看吧2024-08-08
Android中ImageView.src設(shè)置圖片拉伸、填滿控件的方法
最近公司有個需求,要展示客戶公司的企業(yè)形象,用一張圖片放在ImageView中實現(xiàn),但是發(fā)現(xiàn)圖片并沒有填滿,而是在上下邊上留出了一點空白,下面這篇文章主要跟大家介紹了Android中ImageView.src設(shè)置圖片拉伸、填滿控件的方法,需要的朋友可以參考下。2017-06-06
Android ActionBar完全解析使用官方推薦的最佳導(dǎo)航欄(上)
Action Bar是一種新増的導(dǎo)航欄功能,在Android 3.0之后加入到系統(tǒng)的API當(dāng)中,它標(biāo)識了用戶當(dāng)前操作界面的位置,并提供了額外的用戶動作、界面導(dǎo)航等功能2017-04-04

