zoukankan      html  css  js  c++  java
  • Android 卡顿优化 3 布局优化 工具 Hierarchy Viewer

    欲善其事, 先利其器. 分析布局, 就不得不用到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发展的必经之路, 欢迎大家分享经验.

  • 相关阅读:
    【有感】向香港雷锋学什么
    After NeuSoft’s Interview
    【SHELL学习】if语句
    《我是一只IT小小鸟》【1】
    MSDN微软社区 大连线下聚会 即将举行
    【毕业设计】修改用户信息
    SQL Connection
    EasyPR中文开源车牌识别系统 开发详解(1)
    EasyPR开发详解(2)车牌定位
    EasyPR开发详解(4)形态学操作、尺寸验证、旋转等操作
  • 原文地址:https://www.cnblogs.com/ldq2016/p/8483636.html
Copyright © 2011-2022 走看看