- 布局可以說是APP最重要的一項(xiàng)了,用戶感知極強(qiáng)甜无,無論你的代碼寫的如何扛点,用戶也不知道,用戶只能看到和操作APP岂丘,更漂亮合理的布局陵究,更流暢的體驗(yàn)才是好APP。
- 比如微信奥帘,操作起來卡铜邮,用戶只會覺得是手機(jī)不行,而不會是微信不行寨蹋,但其他APP卡松蒜,用戶就覺得是APP不行,而不是手機(jī)不行已旧。┓( ′?` )┏
- Android性能優(yōu)化 - 啟動速度優(yōu)化 也可一起學(xué)習(xí)秸苗。
1.卡頓分析
1.1 刷新率
- 大多數(shù)用戶感知到的卡頓等性能問題的最主要根源都是因?yàn)殇秩拘阅堋脑O(shè)計(jì)師的角度运褪,他們希望App能夠有更多的動畫难述,圖片等時(shí)尚元素來實(shí)現(xiàn)流暢的用戶體驗(yàn)萤晴。但是Android系統(tǒng)很有可能無法及時(shí)完成那些復(fù)雜的界面渲染操作。Android系統(tǒng)每隔16ms發(fā)出VSYNC信號胁后,觸發(fā)對UI進(jìn)行渲染店读,如果每次渲染都成功,這樣就能夠達(dá)到流暢的畫面所需要的60fps攀芯,為了能夠?qū)崿F(xiàn)60fps屯断,這意味著程序的大多數(shù)操作都必須在16ms內(nèi)完成。
-
上面那段話就是侣诺,APP做的越來越炫酷殖演,動畫和視頻一大堆,啟動就要顯示視頻廣告年鸳,這些對于旗艦Android機(jī)是無壓力的趴久,但對于老手機(jī),或者是入門手機(jī)搔确,復(fù)雜的頁面彼棍,計(jì)算量大,CPU膳算、GPU處理不過來座硕,也就無法流暢顯示了。
-
如果你的某個(gè)操作花費(fèi)時(shí)間是24ms涕蜂,系統(tǒng)在得到VSYNC信號的時(shí)候就無法進(jìn)行正常渲染华匾,這樣就發(fā)生了丟幀現(xiàn)象。那么用戶在32ms內(nèi)看到的會是同一幀畫面机隙。
1.2 PerfDog
-
下圖蜘拉,在PerfDog,使用華為P30Pro有鹿,查看微博的刷新情況诸尽,靜態(tài)的微博內(nèi)容在不滑動的時(shí)候,刷新率就是0fps印颤,快速滑動時(shí)您机,刷新率在60fps左右,還能查看CPU和內(nèi)存是使用情況年局。
-
下圖际看,而在微博播放視頻時(shí),刷新率一直就是60fps左右了矢否。
下圖仲闽,普通的APP都基本能達(dá)到60fps,相機(jī)就不是了僵朗,相機(jī)拍照穩(wěn)定在30fps,而自拍時(shí)赖欣,開啟美顏屑彻,降到24fps了,看來相機(jī)加AI美顏是比較吃性能的顶吮。
-
小知識社牲,電影或者是網(wǎng)上看的視頻一般是24幀/秒的速度播放的,即可以省性能悴了,效果也不錯(cuò)搏恤,索尼A7M3相機(jī)可以錄制120幀的慢動作,可以做4倍或者5倍升格湃交。
1.3 CPU Profile
- Android性能優(yōu)化 - 啟動速度優(yōu)化 里有講怎么使用 Profile 看各個(gè)方法的耗時(shí)熟空,布局的加載也是會顯示的,也可以用來分析卡頓的可能情況搞莺。
2.布局優(yōu)化
2.1 過度繪制
Overdraw(過度繪制)描述的是屏幕上的某個(gè)像素在同一幀的時(shí)間內(nèi)被繪制了多次息罗。在多層次的UI結(jié)構(gòu)里面,如果不可見的UI也在做繪制的操作才沧,這就會導(dǎo)致某些像素區(qū)域被繪制了多次迈喉。這就浪費(fèi)大量的CPU以及GPU資源。
-
藍(lán)色綠色是比較好的情況糜工,紅色就是層級較多了弊添,為了實(shí)現(xiàn)好看的效果录淡,就會套多層布局捌木,過度繪制的多了消耗性能,對與入門機(jī)就會卡頓了嫉戚。
手機(jī)進(jìn)入開發(fā)人員選項(xiàng)刨裆,調(diào)試GPU過度繪制,打開顯示過度繪制區(qū)域彬檀。
-
貝殼APP的布局大多是藍(lán)色綠色的帆啃,說明他們APP就沒什么過度繪制的情況,非常好窍帝。
-
到下面的列表就會有過度繪制的情況努潘,但區(qū)域不大,只有內(nèi)容的部分坤学。
-
開發(fā)人員選項(xiàng)疯坤,顯示布局邊界,可以看到貝殼的布局層級確實(shí)不多深浮,也非常的清晰工整压怠。
-
微博APP的過度繪制區(qū)域基本占滿整個(gè)屏幕了,除了微博還有微信淘寶等列表APP也是大多數(shù)紅色的飞苇,原因可能是列表類的APP菌瘫,除了父布局蜗顽,里面還要套RecyclerView,再套itemview雨让,無法避免的過度繪制雇盖;但整個(gè)item都過度繪制了,貝殼就會比較好一些宫患。
-
微博的布局看起來就會復(fù)雜一些了刊懈。
2.2 解決過度繪制
- 1.上面的微博跟貝殼比較,微博的item是有過度繪制的情況娃闲,那么我們在寫RecyclerView的時(shí)候虚汛,如果RecyclerView的父布局、RecyclerView皇帮、item三者的背景只要其中一個(gè)設(shè)置就可以了卷哩,沒有設(shè)置背景就不會渲染,否則就會有過度繪制的情況属拾。
- 2.父布局套子布局也是盡量只設(shè)置其中一個(gè)背景将谊,除非沒辦法都需要背景。
- 3.子view一般繪制后是會覆蓋父view渐白,所以一般選擇把背景設(shè)置在子view尊浓。
- 4.視圖的層級結(jié)構(gòu)能減少就減少,層級越多繪制速度越慢纯衍。
- 5.盡量少設(shè)置view的透明度栋齿,如果一個(gè)view設(shè)置了alpha,那他需要知道下面的view是什么內(nèi)容襟诸,再繪制自己瓦堵,就是過度繪制。如果是文字有透明度歌亲,可以在色號里就設(shè)置好菇用。
2.3 層級優(yōu)化
- Android studiol有布局層級的工具,Layout Inspector陷揪,運(yùn)行起來app后惋鸥,可以看到每個(gè)頁面的層級結(jié)構(gòu)。層級太多悍缠,肯定就會造成卡頓卦绣,啟動慢,在啟動優(yōu)化有說扮休,
-
左邊可以看到布局樹的具體內(nèi)容迎卤。
- 像ScrollView里面只能放一個(gè)ViewGroup,是不可縮減的玷坠,但 LinearLayout套LinearLayout 是可以通過ConstraintLayout解決的蜗搔,約束布局可以說是結(jié)合了線性布局跟相對布局的優(yōu)點(diǎn)劲藐,能有效減少層級。
2.4 使用merge
- 我們有一些布局是可以通用的樟凄,避免重復(fù)代碼聘芜,就可以使用 include。
- 但是缝龄,如果使用 include汰现,但里面的布局又是一個(gè) ViewGroup 的話,就會造成層級過多叔壤,這個(gè)時(shí)候就可以使用 merge 標(biāo)簽了瞎饲,里面的子view根據(jù)會外部include地方的ViewGroup來排列,從而減少層級炼绘。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/layout_head" />
</LinearLayout>
<!-- layout_head -->
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_head"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="text1"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="text2"
android:textSize="16sp" />
</merge>
- 可以看到使用merge 里面的 view 直接在 LinearLayout 的層級里嗅战。
2.5 ViewStub
- 我們有時(shí)根據(jù)需求,先把布局畫好俺亮,然后把 android:visibility 設(shè)置成 "invisible" 或者是 "gone" 驮捍,invisible 和 gone 雖然看不見,但他們還是有初始化脚曾,占用這內(nèi)存和資源东且,前者還占用著位置。
- 我們可以使用 ViewStub 來包裹這些需要隱藏顯示的 view本讥,它是一個(gè)輕量級的view相速,不可見不占用資源菩貌,只有當(dāng)設(shè)置 inflate 時(shí)才初始化顯示娘侍。
<ViewStub
android:id="@+id/viewStub_title"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout="@layout/layout_title"
app:layout_constraintTop_toTopOf="parent" />
<!-- layout_title -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="@+id/image_back"
android:padding="12dp"
android:src="@drawable/ic_back" />
<TextView
android:layout_width="0dp"
android:id="@+id/tv_title"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="title" />
</LinearLayout>
- 在Activity中 viewStub.inflate() 即可顯示惯疙,但不可重復(fù)調(diào)用inflate();否則報(bào)異常:
java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.ViewStub.inflate()' on a null object reference邦危。
viewStub = findViewById(R.id.viewStub_title);
viewStub.inflate();
//之后可以初始化里面的view
ImageView ivBack = findViewById(R.id.image_back);
ivBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
TextView tvTitle = findViewById(R.id.tv_title);
3.其他優(yōu)化
3.1 不要在onDraw里創(chuàng)建對象
- 我們經(jīng)常需要自定義view盟劫,在 onDraw() 方法里不要創(chuàng)建對象祷愉,因?yàn)樽远xview繪制莱找,會非常頻繁的調(diào)用 onDraw涣仿,雖然方法里的對象創(chuàng)建用完就被回收掉勤庐,但頻繁的創(chuàng)建銷毀對象會導(dǎo)致內(nèi)存抖動和GC ,而 GC 多了就會卡頓。
3.2 異步加載布局
- LayoutInflater加載xml布局的過程會在主線程使用IO讀取XML布局文件進(jìn)行XML解析好港,再根據(jù)解析結(jié)果利用反射創(chuàng)建布局中的View/ViewGroup對象愉镰。這個(gè)過程隨著布局的復(fù)雜度上升,耗時(shí)自然也會隨之增大钧汹。Android為我們提供了 Asynclayoutinflater 把耗時(shí)的加載操作在異步線程中完成丈探,最后把加載結(jié)果再回調(diào)給主線程。
- Asynclayoutinflater 注意的地方:
- 1拔莱、使用異步 inflate碗降,那么需要這個(gè) layout 的 parent 的 generateLayoutParams 函數(shù)是線程安全的隘竭。
- 2、所有構(gòu)建的 View 中必須不能創(chuàng)建 Handler 或者是調(diào)用 Looper.myLooper讼渊;(因?yàn)槭窃诋惒骄€程中加載的动看,異步線程默認(rèn)沒有調(diào)用 Looper.prepare )。
- 3爪幻、AsyncLayoutInflater 不支持設(shè)置 LayoutInflater.Factory 或者 LayoutInflater.Factory2菱皆;。
- 4挨稿、不支持加載包含 Fragment 的 layout仇轻。
- 5、如果 AsyncLayoutInflater 失敗奶甘,那么會自動回退到UI線程來加載布局拯田。