Android App優(yōu)化之Layout怎么擺

系列文:

  1. 背景:Android App優(yōu)化, 要怎么做?
  2. Android App優(yōu)化之性能分析工具
  3. Android App優(yōu)化之提升你的App啟動速度之理論基礎
  4. Android App優(yōu)化之提升你的App啟動速度之實例挑戰(zhàn)
  5. Android App優(yōu)化之Layout怎么擺
  6. Android App優(yōu)化之ANR詳解
  7. Android App優(yōu)化之消除卡頓
  8. Android App優(yōu)化之內存優(yōu)化
  9. Android App優(yōu)化之持久電量
  10. Android App優(yōu)化之如何高效網絡請求

優(yōu)化完App的啟動速度, 接下來我們要關注的就是UI布局怎么更高效了.

欲善其事, 先利其器. 分析布局, 就不得不用到Hierarchy Viewer了.

本文工具使用皆以GithubApp的詳情界面RepoDetailActivity為例說明.
為了不影響閱讀體驗, 對應的布局文件activity_repo_detail.xml的代碼放在文末

1, Hierarchy Viewer怎么用

Hierarchy發(fā)音 [美: 'ha??rɑrki] [英: 'ha??rɑ?k?] 層次結構的意思.
之前一直念不順這個單詞Hierarchy, 就簡稱為H Viewer了. 下文就這么簡稱吧.

官網描述, H Viewer是用來分析調試和優(yōu)化我們的UI的一個圖形化工具. 它會展示當前界面的View層級.

1.1 啟用H Viewer

比較早接觸Android開發(fā)的同學可能知道, H Viewer只能在root過的機器才能使用. 主要是在沒有root過的機器中view server這個服務是沒有開啟的. H Viewer就無法連接到機器獲取view層級信息.

正所謂高手在民間, 大家都嘗試在未root的機器中啟用view server來使用H Viewer. 最具代表性的就是romainguy的ViewServer, 只需集成少量代碼到你的Activity, 相當于在手機端開啟了view server服務, 建立socket通道與PC端的H Viewer通信.

此工程被Android官網吸收, 作為開啟H View的方案之一.

完整開啟H Viewer的套路如下:

  1. 手機開啟開發(fā)者模式, USB調試.
  2. 根據手機的Android系統(tǒng)版本:
    • 4.0及以下, 沒有root. 使用上述的開源工程ViewServer提供的方式.
    • 4.0及以下, 已經root. 無需其他額外設置.
    • 4.1及以上. 需要在PC端設置ANDROID_HVPROTO環(huán)境變量.

設置系統(tǒng)環(huán)境變量: ANDROID_HVPROTO, 值為ddm
具體設置系統(tǒng)環(huán)境變量根據PC系統(tǒng)不同而異.

做完上述配置后, 你就可以打開H Viewer了, 打開DDMS, 如下操作進入H Viewer界面:


ddms_open_hviewer

1.2 H Viewer界面詳解

GithubApp的詳情界面RepoDetailActivity為例說明:

Snip20160902_1.png

界面分為四個部分:

  1. Window
    顯示當前連接的設備和供分析的界面. 可手動選擇.

  2. Tree View
    樹狀圖的形式展示該Activity中的View層級結構. 可以放大縮小, 每個節(jié)點代表一個View, 點擊可以彈出其屬性, 當前值, 并且在LayoutView中會顯示其在界面中相應位置.
    Tree View是我們主要要分析的視圖.

  3. Tree Overview
    Tree View的概覽圖. 有一個選擇框, 可以拖動選擇查看. 選中的部分會在Tree View中顯示.

  4. Layout View
    匹配手機屏幕的視圖, 按照View的實際顯示位置展示出來的框圖.

1.3 H Viewer參數解讀

  1. 通過Tree View可以很直觀的看到View的層級.
  2. 點擊Tree View的RepoItemView這個節(jié)點:
14728281715494.jpg

關于三個小圓點的性能指示, 在App優(yōu)化之性能分析工具一文中有提到, 再強調一遍:

三個小圓點, 依次表示Measure, Layout, Draw, 可以理解為對應View的onMeasure, onLayout, onDraw三個方法.

  • 綠色, 表示該View的此項性能比該View Tree中超過50%的View都要快.
  • 黃色, 表示該View的此項性能比該View Tree中超過50%的View都要慢.
  • 紅色, 表示該View的此項性能是View Tree中最慢的.

如果你的界面的Tree View中紅點較多, 那就需要注意了. 一般來說:

1, Measure紅點, 可能是布局中嵌套RelativeLayout, 或是嵌套LinearLayout都使用了weight屬性.
2, Layout紅點, 可能是布局層級太深.
3, Draw紅點, 可能是自定義View的繪制有問題, 復雜計算等.

由上圖, 可以看到我們的RepoItemView的三項指標都不合格, 證明其還有很多優(yōu)化空間. 層級, 繪制都可以優(yōu)化.

除了用H Viewer來做代碼后分析, Android還提供了Lint, 在我們編寫xml布局文件時就即時的給出一些相關提示.

2, Lint tool

打開RepoDetailActivity的布局文件activity_repo_detail.xml, 在Android Studio菜單欄中開啟Lint檢查:

14728313149102.jpg

選擇當前文件:

14728313382536.jpg

會在下方彈出分析結果:

14728314908964.jpg

分析結果包括用法檢測(例如版本特有屬性), 國際化(字符串是否提取到strings.xml, Rlt支持等), 以及我們今天的主題---性能分析結果.

點開"Android -> Lint -> Performance"項, 可以看到關于布局性能的建議項. 此例中是說ScrollView的父級LinearLayout是不必要的.

3, 怎么優(yōu)化你的布局

通過以上工具的使用和分析, 也基本能找到布局的一些常見的好與不好的了.

正所謂授之以魚不如授之以漁. 在此也就不太詳細去講怎么優(yōu)化了, 幾點建議, 大家自行實踐吧:)

盡量減少布局層級和復雜度

  1. 盡量不要嵌套使用RelativeLayout.
  2. 盡量不要在嵌套的LinearLayout中都使用weight屬性.
  3. Layout的選擇, 以盡量減少View樹的層級為主.
  4. 去除不必要的父布局.
  5. 善用TextView的Drawable減少布局層級
  6. 如果H Viewer查看層級超過5層, 你就需要考慮優(yōu)化下布局了~

善用Tag

  1. <include>
    使用include來重用布局.
  2. <merge>
    使用<merge>來解決include或自定義組合ViewGroup導致的冗余層級問題. 例如本例中的RepoItemView的布局文件實際可以用一個<merge>標簽來減少一級.
  3. <ViewStub>

ListView優(yōu)化

  1. contentView復用
  2. 引入holder來避免重復的findViewById.
  3. 分頁加載

4, 附示例代碼

因github上的源碼會持續(xù)更新, 特留對應代碼在此.

activity_repo_detail.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:id="@+id/root_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/md_white_1000"
    android:orientation="vertical"
    android:padding="@dimen/dimen_10">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true"
        android:scrollbars="none">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <com.anly.githubapp.ui.widget.RepoItemView
                android:id="@+id/repo_item_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/md_grey_300"
                android:elevation="@dimen/dimen_2"/>

            <LinearLayout
                android:id="@+id/contributor_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/dimen_10"
                android:orientation="vertical"
                >

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/dimen_40"
                    android:gravity="center_vertical"
                    android:orientation="horizontal"
                    android:background="@drawable/button_bg"
                    android:paddingLeft="@dimen/dimen_10">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:gravity="center_vertical"
                        android:text="{oct-organization} Contributors"/>

                    <TextView
                        android:id="@+id/contributors_count"
                        android:layout_width="match_parent"
                        android:layout_height="@dimen/dimen_40"
                        android:gravity="center_vertical"/>

                </LinearLayout>

                <android.support.v7.widget.RecyclerView
                    android:id="@+id/contributor_list"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/dimen_60"
                    android:layout_marginTop="@dimen/dimen_2"
                    />

            </LinearLayout>

            <LinearLayout
                android:id="@+id/fork_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/dimen_10"
                android:orientation="vertical"
                >


                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/dimen_40"
                    android:gravity="center_vertical"
                    android:orientation="horizontal"
                    android:background="@drawable/button_bg"
                    android:paddingLeft="@dimen/dimen_10"
                    >

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:gravity="center_vertical"
                        android:text="{oct-gist_fork} Forks"/>

                    <TextView
                        android:id="@+id/forks_count"
                        android:layout_width="match_parent"
                        android:layout_height="@dimen/dimen_40"
                        android:gravity="center_vertical"/>

                </LinearLayout>

                <android.support.v7.widget.RecyclerView
                    android:id="@+id/fork_list"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/dimen_60"
                    android:layout_marginTop="@dimen/dimen_2"
                    />

            </LinearLayout>

            <LinearLayout
                android:id="@+id/code_layout"
                android:layout_width="match_parent"
                android:layout_height="@dimen/dimen_40"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:layout_marginTop="@dimen/dimen_10"
                android:background="@drawable/button_bg"
                android:paddingLeft="@dimen/dimen_10">

                <TextView
                    android:id="@+id/code_label"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/dimen_40"
                    android:gravity="center_vertical"
                    android:text="{oct-file_code} Code"/>

            </LinearLayout>

            <LinearLayout
                android:id="@+id/readme_layout"
                android:layout_width="match_parent"
                android:layout_height="@dimen/dimen_40"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:layout_marginTop="@dimen/dimen_10"
                android:background="@drawable/button_bg"
                android:paddingLeft="@dimen/dimen_10">

                <TextView
                    android:id="@+id/readme_label"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/dimen_40"
                    android:gravity="center_vertical"
                    android:text="{oct-info} README"/>

            </LinearLayout>

        </LinearLayout>

    </ScrollView>
</LinearLayout>

com.anly.githubapp.ui.widget.RepoItemView對應的布局:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="@dimen/dimen_10">

        <TextView
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="left|center_vertical"
            android:maxLines="1"
            android:text="@string/app_name"
            android:textColor="@android:color/black"
            android:textSize="@dimen/text_size_18"/>

        <TextView
            android:id="@+id/desc"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="left|center_vertical"
            android:maxLines="2"
            android:text="@string/app_name"
            android:textColor="@android:color/darker_gray"
            android:textSize="@dimen/text_size_12"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/dimen_5"
            android:gravity="center_vertical"
            android:orientation="horizontal">

            <ImageView
                android:id="@+id/image"
                android:layout_width="@dimen/dimen_32"
                android:layout_height="@dimen/dimen_32"
                android:scaleType="centerInside"
                android:src="@mipmap/ic_launcher"
                android:visibility="visible"/>

            <TextView
                android:id="@+id/owner"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginLeft="@dimen/dimen_10"
                android:gravity="left|center_vertical"
                android:text="@string/app_name"
                android:textColor="@android:color/black"
                android:textSize="@dimen/text_size_14"/>

        </LinearLayout>

        <View
            android:layout_marginTop="@dimen/dimen_5"
            android:layout_width="match_parent"
            android:layout_height="1px"
            android:background="@color/grey"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="@dimen/dimen_32"
            android:gravity="center_vertical"
            android:orientation="horizontal"
            android:paddingTop="@dimen/dimen_10">

            <TextView
                android:id="@+id/update_time"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="left|center_vertical"
                android:text="@string/app_name"
                android:textColor="@android:color/black"
                android:textSize="@dimen/text_size_12"
                />

            <View
                android:layout_width="1px"
                android:layout_height="match_parent"
                android:background="@color/grey"/>

            <LinearLayout
                android:id="@+id/star_view"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:orientation="horizontal">

                <ImageView
                    android:id="@+id/star_icon"
                    android:layout_width="@dimen/dimen_16"
                    android:layout_height="@dimen/dimen_16"
                    android:scaleType="centerInside"
                    android:src="@drawable/ic_star"/>

                <TextView
                    android:id="@+id/star"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_marginLeft="@dimen/dimen_5"
                    android:gravity="center"
                    android:text="@string/app_name"
                    android:textColor="@android:color/black"
                    android:textSize="@dimen/text_size_12"
                    />

            </LinearLayout>


        </LinearLayout>

    </LinearLayout>

    <com.flyco.labelview.LabelView
        android:id="@+id/label_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        app:lv_background_color="@color/md_yellow_500"
        app:lv_gravity="TOP_RIGHT"
        app:lv_text="TEST"
        app:lv_text_size="@dimen/text_size_12"/>
</FrameLayout>

優(yōu)化不同于做功能, 可能分析的多, 出的成果少~ 比較枯燥, 然而優(yōu)化也是App發(fā)展的必經之路, 歡迎大家分享經驗.

轉載請注明出處, 歡迎大家分享到朋友圈, 微博~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末跑揉,一起剝皮案震驚了整個濱河市超凳,隨后出現(xiàn)的幾起案子欺矫,更是在濱河造成了極大的恐慌绣溜,老刑警劉巖仿耽,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岩榆,死亡現(xiàn)場離奇詭異,居然都是意外死亡恭金,警方通過查閱死者的電腦和手機操禀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來横腿,“玉大人颓屑,你說我怎么就攤上這事斤寂。” “怎么了揪惦?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵遍搞,是天一觀的道長。 經常有香客問我丹擎,道長尾抑,這世上最難降的妖魔是什么歇父? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任蒂培,我火速辦了婚禮,結果婚禮上榜苫,老公的妹妹穿的比我還像新娘护戳。我一直安慰自己,他們只是感情好垂睬,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布媳荒。 她就那樣靜靜地躺著,像睡著了一般驹饺。 火紅的嫁衣襯著肌膚如雪钳枕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天赏壹,我揣著相機與錄音鱼炒,去河邊找鬼。 笑死蝌借,一個胖子當著我的面吹牛昔瞧,可吹牛的內容都是我干的。 我是一名探鬼主播菩佑,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼自晰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了稍坯?” 一聲冷哼從身側響起酬荞,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瞧哟,沒想到半個月后混巧,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡绢涡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年牲剃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雄可。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡凿傅,死狀恐怖缠犀,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情聪舒,我是刑警寧澤辨液,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站箱残,受9級特大地震影響滔迈,放射性物質發(fā)生泄漏。R本人自食惡果不足惜被辑,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一燎悍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧盼理,春花似錦谈山、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至臊诊,卻和暖如春鸽粉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抓艳。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工触机, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人壶硅。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓威兜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親庐椒。 傳聞我的和親對象是個殘疾皇子椒舵,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容