Android App优化之Layout怎么摆

作者: anly_jun | 来源:发表于2016-09-03 00:34 被阅读11505次

    系列文:

    1. 背景:Android App优化, 要怎么做?
    2. Android App优化之性能分析工具
    3. Android App优化之提升你的App启动速度之理论基础
    4. Android App优化之提升你的App启动速度之实例挑战
    5. Android App优化之Layout怎么摆
    6. Android App优化之ANR详解
    7. Android App优化之消除卡顿
    8. Android App优化之内存优化
    9. Android App优化之持久电量
    10. Android App优化之如何高效网络请求

    优化完App的启动速度, 接下来我们要关注的就是UI布局怎么更高效了.

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

    本文工具使用皆以GithubApp的详情界面RepoDetailActivity为例说明.
    为了不影响阅读体验, 对应的布局文件activity_repo_detail.xml的代码放在文末

    1, Hierarchy Viewer怎么用

    Hierarchy发音 [美: 'haɪərɑrki] [英: 'haɪərɑːkɪ] 层次结构的意思.
    之前一直念不顺这个单词Hierarchy, 就简称为H Viewer了. 下文就这么简称吧.

    官网描述, H Viewer是用来分析调试和优化我们的UI的一个图形化工具. 它会展示当前界面的View层级.

    1.1 启用H Viewer

    比较早接触Android开发的同学可能知道, 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. 手机开启开发者模式, USB调试.
    2. 根据手机的Android系统版本:
      • 4.0及以下, 没有root. 使用上述的开源工程ViewServer提供的方式.
      • 4.0及以下, 已经root. 无需其他额外设置.
      • 4.1及以上. 需要在PC端设置ANDROID_HVPROTO环境变量.

    设置系统环境变量: ANDROID_HVPROTO, 值为ddm
    具体设置系统环境变量根据PC系统不同而异.

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


    ddms_open_hviewer

    1.2 H Viewer界面详解

    GithubApp的详情界面RepoDetailActivity为例说明:

    Snip20160902_1.png

    界面分为四个部分:

    1. Window
      显示当前连接的设备和供分析的界面. 可手动选择.

    2. Tree View
      树状图的形式展示该Activity中的View层级结构. 可以放大缩小, 每个节点代表一个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这个节点:
    14728281715494.jpg

    关于三个小圆点的性能指示, 在App优化之性能分析工具一文中有提到, 再强调一遍:

    三个小圆点, 依次表示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的三项指标都不合格, 证明其还有很多优化空间. 层级, 绘制都可以优化.

    除了用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, 怎么优化你的布局

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

    正所谓授之以鱼不如授之以渔. 在此也就不太详细去讲怎么优化了, 几点建议, 大家自行实践吧:)

    尽量减少布局层级和复杂度

    1. 尽量不要嵌套使用RelativeLayout.
    2. 尽量不要在嵌套的LinearLayout中都使用weight属性.
    3. Layout的选择, 以尽量减少View树的层级为主.
    4. 去除不必要的父布局.
    5. 善用TextView的Drawable减少布局层级
    6. 如果H Viewer查看层级超过5层, 你就需要考虑优化下布局了~

    善用Tag

    1. <include>
      使用include来重用布局.
    2. <merge>
      使用<merge>来解决include或自定义组合ViewGroup导致的冗余层级问题. 例如本例中的RepoItemView的布局文件实际可以用一个<merge>标签来减少一级.
    3. <ViewStub>

    ListView优化

    1. contentView复用
    2. 引入holder来避免重复的findViewById.
    3. 分页加载

    4, 附示例代码

    因github上的源码会持续更新, 特留对应代码在此.

    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>
    

    优化不同于做功能, 可能分析的多, 出的成果少~ 比较枯燥, 然而优化也是App发展的必经之路, 欢迎大家分享经验.

    转载请注明出处, 欢迎大家分享到朋友圈, 微博~

    相关文章

      网友评论

      • 世道无情:第4个没有看懂,麻烦能说下么
      • 小K猪:牛人
      • Android深夜食堂:关于useless layout那个问题,我一直存在,我的场景就是,一个大layout中,希望把文字和图片都居中显示,我就会在他们俩外面包一层relatelayout,然后centerInParent,然后这个relatelayout就是useless,但是又不知道有什么好办法去去掉它,并且还能保证两个小view还能居中显示。
        来世记忆:@1种微笑 使用约束布局
        117eb4f82123:对于这种业务需求,个人觉得基本上就需要两层以上的viewgroup,所以没法去掉任何一层。我感觉并不是说所有的view层都可以进行优化,以为业务逻辑定死了。
      • 左手木亽:如果H Viewer查看层级超过5层, 你就需要考虑优化下布局了~

        这句,超过5层是作者你的建议还是H Viewer的建议?
        想学习一下,谢谢。
        来世记忆:官方建议不超过15层
        左手木亽: @anly_jun 谢谢。
        anly_jun:@左手木亽 个人一般性的建议, 因为一般层级过多, H Viewer就可能会显示红色了~, 层级过多也会伴随着overdraw等问题, 故而建议根据实际情况去尽量减少布局层级. 而非是说不超过5层就没有问题了~ :blush:
      • i冰点:在4.1以上的系统上还用使用开源工程ViewServer吗?为什么我的没有那三个小圆点?
        anly_jun:@i冰点 不需要, 设置系统环境变量: ANDROID_HVPROTO, 值为ddm即可. 没有三个小圆点的, 参考1.2 H Viewer界面详解中的图片, 选中某个节点, 点击TreeView视图中右边的图形菜单栏中的那个"三个圆形的菜单", 试下.
      • a26157bbb36b:

        求教为什么我查看的时候没有显示三个小圆点。都是显示n/a,比如 Measure:n/a
        ----------------------------------------------------------------------
        三个小圆点, 依次表示Measure, Layout, Draw, 可以理解为对应View的onMeasure, onLayout, onDraw三个方法.

        绿色, 表示该View的此项性能比该View Tree中超过50%的View都要快.
        黄色, 表示该View的此项性能比该View Tree中超过50%的View都要慢.
        红色, 表示该View的此项性能是View Tree中最慢的.

        anly_jun:@a26157bbb36b 参考1.2 H Viewer界面详解中的图片, 选中某个节点, 点击TreeView视图中右边的图形菜单栏中的那个"三个圆形的菜单", 试下.
      • 捡淑:马克

      本文标题:Android App优化之Layout怎么摆

      本文链接:https://www.haomeiwen.com/subject/arekettx.html