天天看點

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

這幾天閑得無聊,就打開手機上的開發者模式裡面的“GPU過度繪制”功能,看看别家的App做的咋樣,然後很偶然的打開了“簡書”,然後就被它的過度繪制驚呆了,于是寫了這篇性能分析的文章,從一個隻有APK檔案的角度,說下如何尋找布局中可能存在的性能問題,以及解決方案。本文章以簡書Android最新版本1.9.1進行分析。
    • GPU過度繪制
    • Hierarchy View
    • SysTrace
    • TraceView
    • 總結
    • PS
    • 分析資源下載下傳

GPU過度繪制

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

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

然後就得到了下面這幅圖

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

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

Hierarchy View

打開之後,我們可以得到下面的資料

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

從圖上可以看到,主界面共有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這麼費時間呢?

因為主界面有輪播圖。

輪播圖的圖檔很大,占用的記憶體也不小,畫這樣一張圖很費時間,不信?看下面這張圖,這是停留在主界面一段時間之後的效果

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

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

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

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

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

我們可以發現,一個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>
           

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

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

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

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

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

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

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

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

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

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

經過我驗證,發現每個ViewPager裡面的Fragment不會銷毀,也就是說,你打開多少個界面子產品,就有多少個Fragment執行個體在記憶體中。

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

  • 可以儲存每個界面的使用者狀态,比如下拉位置
  • 可以減少使用者的等待時間,不需要重新擷取
  • 減少流量消耗

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

但是,我覺得簡書的使用者應該是以80後、90後的網際網路人群為主體,這群人的消費水準應該不會太低,手機裝置的配置應該也不會太低,是以結合這個考慮,不對Fragment進行回收也是完全可以接受的方案。

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

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

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

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

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

SysTrace

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

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

在後面有一次掉幀,可能是因為UIL加載圖檔導緻記憶體不夠用,發生的掉幀,因為在這次掉幀之後,就由于記憶體不夠用,Alloc Memory 産生了一次GC,是以說布局嵌套過多再加上圖檔的記憶體緩存,可能會造成這種結果。

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

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

TraceView

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

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

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

在重新整理界面。

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

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

ViewRootImpl和ListView的計算也是CPU大戶

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

Chreographer占用了很大一部分CPU

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

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

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

總結

到現在,我們終于知道了簡書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) 侵權必究!

關注我的微網誌,可以獲得更多精彩内容

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