【凱子哥帶你學Android】Andriod性能優化之列表卡頓——以“簡書”APP爲例

這幾天閒得無聊,就打開手機上的開發者模式裏面的“GPU過度繪製”功能,看看別家的App做的咋樣,然後很偶然的打開了“簡書”,然後就被它的過度繪製驚呆了,於是寫了這篇性能分析的文章,從一個只有APK文件的角度,說下如何尋找佈局中可能存在的性能問題,以及解決方案。本文章以簡書Android最新版本1.9.1進行分析。

GPU過度繪製

首先打開下面兩個功能開關

  • 開發者模式->調試GPU過度繪製
  • 開發者模式->GPU呈現模式分析

然後就得到了下面這幅圖

我們可以看到,簡直慘不忍睹!重繪現象嚴重,幀率超過16ms標準線好多,我就被這個界面給嚇到了!這說明簡書的這個界面有待優化,我們再用Hierarchy View看一下佈局。

Hierarchy View

打開之後,我們可以得到下面的數據

從圖上可以看到,主界面共有143個View,真不少,不過更重要的是下面的數據

  • Mesure:2.937 ms
  • Layout:9.113 ms
  • Draw:15.679 ms

Mesure的數據比較正常,而Layout和Draw的時間明顯超長,這樣,每次繪製的總時間 = 2.937+9.113+15.679 = 27.729 ms

超出了16ms很多,每秒的幀率就達不到60fps,所以就會出現丟幀卡頓的現象。

那麼爲啥Draw這麼費時間呢?

因爲主界面有輪播圖。

輪播圖的圖片很大,佔用的內存也不小,畫這樣一張圖很費時間,不信?看下面這張圖,這是停留在主界面一段時間之後的效果

可以看到,丟幀現象是有規律的,時間間隔和輪播圖的自動播放間隔一樣,所以這也說明了輪播大圖是導致丟幀的重要嫌疑犯。

但是不光這個原因,這個界面還有很多不必要佈局被重繪。

下面這張圖是顯示每一篇文章的Item的佈局,Item的容器用的還是ListView,別問我怎麼知道的,不告訴你~

我們可以發現,一個Item嵌套了三層ViewGroup。我不知道是不是有什麼特殊的用途或者是出於什麼考慮,但是爲了使用權重來控制ImageView的佔位,使用二層佈局就可以完成這個界面的顯示,這樣就能避免1層佈局重繪,可以減少Draw花費的時間。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:id="@id/rootView"
    //這一層可以省略
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <LinearLayout
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="110.0dip"
        android:layout_marginLeft="15.0dip"
        android:layout_marginRight="15.0dip"
        android:layout_centerInParent="true">

        <RelativeLayout
            android:layout_width="0.0dip"
            android:layout_height="wrap_content"
            android:layout_marginRight="10.0dip"
            android:layout_weight="1.0">

            <TextView
            android:textSize="12.0sp"
            android:textColor="@color/text_blue"
            android:ellipsize="end"
            android:id="@id/author_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:maxWidth="200.0dip"
            android:text="author"
            android:singleLine="true" />
            ...

        </RelativeLayout>

        <ImageView
        android:id="@id/image"
        android:visibility="visible"
        android:layout_width="80.0dip"
        android:layout_height="80.0dip"
        android:layout_marginRight="5.0dip"
        android:src="@drawable/image_list"
        android:scaleType="centerCrop" />
    </LinearLayout>
</RelativeLayout>

要不然你看,主佈局三等全亮,這就說明佈局寫的可能存在有問題。

咱們接着往下找,順藤摸瓜,看看到底哪一塊有問題

我們可以看到,佈局的層級非常深,使用的是雙ViewPager嵌套的機制,這個主要和簡書的界面設計有關,因爲在主界面上有3個Tabs,爲了實現頭部兩個Tabs的切換功能,只能雙層嵌套ViewPager。

但是我對於這種設計並不是很認可,一個是因爲Google的設計規範中就提到,不要使用過多的Tab,更不要在上部和底部同時使用Tabs,更不要說是三個Tab了,這會造成用戶的迷惑。

不過以國內的產品設計行情來說,要遵守這個設計規範還是比較困難的。

下面是Item的佈局層級,不算上系統佈局的層級,有14層之多,所以說這樣的設計,肯定就會造成佈局層級巨多,佈局、繪製緩慢的結果。

經過我驗證,發現每個ViewPager裏面的Fragment不會銷燬,也就是說,你打開多少個界面模塊,就有多少個Fragment實例在內存中。

這種產品設計當然也是有利有弊,好處就是

  • 可以保存每個界面的用戶狀態,比如下拉位置
  • 可以減少用戶的等待時間,不需要重新獲取
  • 減少流量消耗

弊端也是有的,那就是增加了內存的消耗,在低配置手機上,可能出現頻繁的GC,造成卡頓。

但是,我覺得簡書的用戶應該是以80後、90後的互聯網人羣爲主體,這羣人的消費水平應該不會太低,手機設備的配置應該也不會太低,所以結合這個考慮,不對Fragment進行回收也是完全可以接受的方案。

我們再來看一下ListView的繪製,也是三燈全紅,這就說明這個佈局可能有問題。

然後我找到了每個Item的繪製

從圖上可以看到,Measure和Layout的時間超長,但是我並沒有發現是什麼原因可能導致這個問題,因爲這個佈局裏面就是5個TextView,並沒有太複雜的View,如果說Relativelayout佈局的Layout操作比較費時,這還可以理解,那麼爲什麼Measure會花費那麼長時間呢?有知道的告訴我下。

SysTrace

後來又使用SysTrace抓了下數據,在抓取的時候滑動整個ListView,得到如下結果

在後面有一次掉幀,可能是因爲UIL加載圖片導致內存不夠用,發生的掉幀,因爲在這次掉幀之後,就由於內存不夠用,Alloc Memory 產生了一次GC,所以說佈局嵌套過多再加上圖片的內存緩存,可能會造成這種結果。

而且有一點很奇怪,就是在滑動過程中,一直在調用的都是Looper.dispatchMessage(),到底是什麼操作一直在處理消息呢?換上TraceView看看。

TraceView

話說前面在滑動的時候,我們看到Looper.dispatchMessage()一直在執行,這是在幹嘛呢?爲了解決這個問題,我又使用TraceView抓取了在列表滑動時候的數據

從圖上我們可以看出,Loop.loop()操作佔用了70%的CPU時間,和Handler相關的方法也佔用了50%以上的時間,這些操作都在幹嘛呢?

在刷新界面。

handler機制大家族在瘋狂佔用CPU

ViewRootImpl和ListView的計算也是CPU大戶

Chreographer佔用了很大一部分CPU

這裏熟悉了麼,這裏就開始繪製界面了,不知道整個繪製流程的朋友,請參考我的文章【凱子哥帶你學Framework】Activity界面顯示全解析

總結

到現在,我們終於知道了簡書Android客戶端爲什麼卡頓了,我們總結一下

  • 由於多Tab設計,造成界面佈局嵌套嚴重,不算上系統佈局,就有14層之多
  • 由於界面實現不合理,造成部分佈局重繪嚴重
  • 由於Fragment常駐內存的設計,造成內存消耗偏多,頻繁的GC可能造成界面卡頓
  • 由於Banner的圖片太大,造成圖片佔用內存偏多,頻繁的GC可能造成界面卡頓

由於以上四個原因,造成了CPU和內存佔用過多的結果,知道了可能的原因,那麼解決方案就很簡單了

  • 重新進行界面設計,剔除多Tab佈局,優化交互
  • 對佈局代碼進行優化,去除不必要的界面元素和ViewGroup
  • 考慮是否放棄Fragment常駐內存的方案,不使用hide()和show()對Fragment進行控制,改用replace()等方案
  • 減小Banner圖片大小或者是分辨率

PS

爲了防止朋友們誤會,特意說明一下,任何拋開業務需求談優化都是扯淡,簡書的佈局層級過深是由於業務需求決定的,除了小部分佈局可以優化之外,大部分都實現的很不錯,在我的錘子T1上也沒有卡頓,虛擬機由於性能和配置問題會把這個問題放大,但是正常用是很流暢的。

所以這篇文章並不是針對某個App,只是以簡書爲例子,介紹一下性能分析的方法和思路,單純的技術交流,程序員都很單純,不要多心~

分析資源下載


尊重原創,轉載請註明:From 凱子哥(http://blog.csdn.net/zhaokaiqiang1992) 侵權必究!

關注我的微博,可以獲得更多精彩內容

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章