Android性能優化 - 卡頓和布局優化

  • 布局可以說是APP最重要的一項了,用戶感知極強,無論你的代碼寫的如何,用戶也不知道,用戶只能看到和操作APP,更漂亮合理的布局,更流暢的體驗才是好APP。
  • 比如微信,操作起來卡,用戶只會覺得是手機不行,而不會是微信不行,但其他APP卡,用戶就覺得是APP不行,而不是手機不行。┓( ′?` )┏
  • Android性能優化 - 啟動速度優化 也可一起學習。

1.卡頓分析

1.1 刷新率

  • 大多數用戶感知到的卡頓等性能問題的最主要根源都是因為渲染性能。從設計師的角度,他們希望App能夠有更多的動畫,圖片等時尚元素來實現流暢的用戶體驗。但是Android系統很有可能無法及時完成那些復雜的界面渲染操作。Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,如果每次渲染都成功,這樣就能夠達到流暢的畫面所需要的60fps,為了能夠實現60fps,這意味著程序的大多數操作都必須在16ms內完成。
  • 上面那段話就是,APP做的越來越炫酷,動畫和視頻一大堆,啟動就要顯示視頻廣告,這些對于旗艦Android機是無壓力的,但對于老手機,或者是入門手機,復雜的頁面,計算量大,CPU、GPU處理不過來,也就無法流暢顯示了。


    16ms刷新
  • 如果你的某個操作花費時間是24ms,系統在得到VSYNC信號的時候就無法進行正常渲染,這樣就發生了丟幀現象。那么用戶在32ms內看到的會是同一幀畫面。


    24ms刷新

1.2 PerfDog

騰訊PerfDog,性能監控

  • 下圖,在PerfDog,使用華為P30Pro,查看微博的刷新情況,靜態的微博內容在不滑動的時候,刷新率就是0fps,快速滑動時,刷新率在60fps左右,還能查看CPU和內存是使用情況。


    微博
  • 下圖,而在微博播放視頻時,刷新率一直就是60fps左右了。


    微博視頻
  • 下圖,普通的APP都基本能達到60fps,相機就不是了,相機拍照穩定在30fps,而自拍時,開啟美顏,降到24fps了,看來相機加AI美顏是比較吃性能的。

  • 小知識,電影或者是網上看的視頻一般是24幀/秒的速度播放的,即可以省性能,效果也不錯,索尼A7M3相機可以錄制120幀的慢動作,可以做4倍或者5倍升格。


    相機

1.3 CPU Profile

2.布局優化

2.1 過度繪制

  • Overdraw(過度繪制)描述的是屏幕上的某個像素在同一幀的時間內被繪制了多次。在多層次的UI結構里面,如果不可見的UI也在做繪制的操作,這就會導致某些像素區域被繪制了多次。這就浪費大量的CPU以及GPU資源。

  • 藍色綠色是比較好的情況,紅色就是層級較多了,為了實現好看的效果,就會套多層布局,過度繪制的多了消耗性能,對與入門機就會卡頓了。


    過度繪制
  • 手機進入開發人員選項,調試GPU過度繪制,打開顯示過度繪制區域。

  • 貝殼APP的布局大多是藍色綠色的,說明他們APP就沒什么過度繪制的情況,非常好。


    貝殼
  • 到下面的列表就會有過度繪制的情況,但區域不大,只有內容的部分。


    在這里插入圖片描述
  • 開發人員選項,顯示布局邊界,可以看到貝殼的布局層級確實不多,也非常的清晰工整。


    貝殼
  • 微博APP的過度繪制區域基本占滿整個屏幕了,除了微博還有微信淘寶等列表APP也是大多數紅色的,原因可能是列表類的APP,除了父布局,里面還要套RecyclerView,再套itemview,無法避免的過度繪制;但整個item都過度繪制了,貝殼就會比較好一些。


    微博
  • 微博的布局看起來就會復雜一些了。


    在這里插入圖片描述

2.2 解決過度繪制

  • 1.上面的微博跟貝殼比較,微博的item是有過度繪制的情況,那么我們在寫RecyclerView的時候,如果RecyclerView的父布局、RecyclerView、item三者的背景只要其中一個設置就可以了,沒有設置背景就不會渲染,否則就會有過度繪制的情況。
  • 2.父布局套子布局也是盡量只設置其中一個背景,除非沒辦法都需要背景。
  • 3.子view一般繪制后是會覆蓋父view,所以一般選擇把背景設置在子view。
  • 4.視圖的層級結構能減少就減少,層級越多繪制速度越慢。
  • 5.盡量少設置view的透明度,如果一個view設置了alpha,那他需要知道下面的view是什么內容,再繪制自己,就是過度繪制。如果是文字有透明度,可以在色號里就設置好。

2.3 層級優化

  • Android studiol有布局層級的工具,Layout Inspector,運行起來app后,可以看到每個頁面的層級結構。層級太多,肯定就會造成卡頓,啟動慢,在啟動優化有說,
  • 左邊可以看到布局樹的具體內容。
    布局樹
  • 像ScrollView里面只能放一個ViewGroup,是不可縮減的,但 LinearLayout套LinearLayout 是可以通過ConstraintLayout解決的,約束布局可以說是結合了線性布局跟相對布局的優點,能有效減少層級。
    view tree

2.4 使用merge

  • 我們有一些布局是可以通用的,避免重復代碼,就可以使用 include
  • 但是,如果使用 include,但里面的布局又是一個 ViewGroup 的話,就會造成層級過多,這個時候就可以使用 merge 標簽了,里面的子view根據會外部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

  • 我們有時根據需求,先把布局畫好,然后把 android:visibility 設置成 "invisible" 或者是 "gone"invisiblegone 雖然看不見,但他們還是有初始化,占用這內存和資源,前者還占用著位置。
  • 我們可以使用 ViewStub 來包裹這些需要隱藏顯示的 view,它是一個輕量級的view,不可見不占用資源,只有當設置 inflate 時才初始化顯示。

<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() 即可顯示,但不可重復調用inflate();否則報異常:
    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.其他優化

3.1 不要在onDraw里創建對象

  • 我們經常需要自定義view,在 onDraw() 方法里不要創建對象,因為自定義view繪制,會非常頻繁的調用 onDraw,雖然方法里的對象創建用完就被回收掉,但頻繁的創建銷毀對象會導致內存抖動和GC ,而 GC 多了就會卡頓。
    避免ondraw里創建對象

3.2 異步加載布局

Google Asynclayoutinflater庫

  • LayoutInflater加載xml布局的過程會在主線程使用IO讀取XML布局文件進行XML解析,再根據解析結果利用反射創建布局中的View/ViewGroup對象。這個過程隨著布局的復雜度上升,耗時自然也會隨之增大。Android為我們提供了 Asynclayoutinflater 把耗時的加載操作在異步線程中完成,最后把加載結果再回調給主線程。
  • Asynclayoutinflater 注意的地方:
  • 1、使用異步 inflate,那么需要這個 layout 的 parent 的 generateLayoutParams 函數是線程安全的。
  • 2、所有構建的 View 中必須不能創建 Handler 或者是調用 Looper.myLooper;(因為是在異步線程中加載的,異步線程默認沒有調用 Looper.prepare )。
  • 3、AsyncLayoutInflater 不支持設置 LayoutInflater.Factory 或者 LayoutInflater.Factory2;。
  • 4、不支持加載包含 Fragment 的 layout。
  • 5、如果 AsyncLayoutInflater 失敗,那么會自動回退到UI線程來加載布局。

參考文章

Android性能優化典范

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容