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

Android 卡顿优化 3 布局优化 工具 Hierarchy Viewer的更多相关文章

  1. Android 卡顿优化 1 卡顿解析

    1, 感知卡顿 用户对卡顿的感知, 主要来源于界面的刷新. 而界面的性能主要是依赖于设备的UI渲染性能. 如果我们的UI设计过于复杂, 或是实现不够好, 设备又不给力, 界面就会像卡住了一样, 给用户 ...

  2. Android性能优化之布局优化

    最新最准确内容建议直接访问原文:Android性能优化之布局优化 本文为Android性能优化的第二篇——布局优化,主要介绍使用抽象布局标签(include, viewstub, merge).去除不 ...

  3. 【转】Android性能优化之布局优化篇

     转自:http://blog.csdn.net/feiduclear_up/article/details/46670433 Android性能优化之布局优化篇 分类: andorid 开发2015 ...

  4. Android UI学习 - FrameLayou和布局优化(viewstub)

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://android.blog.51cto.com/268543/308090 Fram ...

  5. Android 性能优化 三 布局优化ViewStub标签的使用

    小黑与小白的故事,通过虚拟这两个人物进行一问一答的形式来共同学习ViewStub的使用 小白:Hi,小黑,ViewStub是什么?听说能够用来进行布局优化. 小黑:ViewStub 是一个隐藏的,不占 ...

  6. Android实习生 —— 屏幕适配及布局优化

    为什么要进行屏幕适配.对哪些设备进行适配?在近几年的发展当中,安卓设备数量逐渐增长,由于安卓设备的开放性,导致安卓设备的屏幕尺寸大小碎片化极为严重.从[友盟+]2016年手机生态发展报告H1中看截止1 ...

  7. Android优化——UI检视利器:Hierarchy Viewer

    在Android的SDK工具包中,有很多十分有用的工具,可以帮助程序员开发和测试Android应用程序,大大提高其工作效率.其中的一款叫 Hierachy Viewer的可视化调试工具,可以很方便地在 ...

  8. Android 实用工具Hierarchy Viewer实战

    在Android的SDK工具包中,有很多十分有用的工具,可以帮助程序员开发和测试Android应用程序,大大提高其工作效率.其中的一款叫Hierachy Viewer的可视化调试工具,可以很方便地在开 ...

  9. Android 卡顿优化 4 布局优化实际技巧

    今天分享一些layout布局书写中的一些技巧,希望看过之后你也一样可以写出性价比高的布局.我个人的目标是用最少的View写出一样效果的布局.因为我相信View的数量减少伴随着的就是层级的减少.从而达到 ...

随机推荐

  1. 基于UDP套接字编程实例

    data.h #ifndef DATA_H #define DATA_H #include <stdio.h> #include <string.h> #include < ...

  2. [PAT] 1144 The Missing Number(20 分)

    1144 The Missing Number(20 分) Given N integers, you are supposed to find the smallest positive integ ...

  3. Spring boot 集成hessian - LocalDateTime序列化和反序列化

    - 反序列化 import com.caucho.hessian.HessianException; import com.caucho.hessian.io.AbstractDeserializer ...

  4. react生命周期函数使用箭头函数,导致mobx-react问题

    最近新人加入了项目,遇到了一个很奇怪的问题.mobx observable 属性,onChange的时候就是页面不会刷新. 试来试去,就是不知道什么原因,后来其他同事查到是因为componentWil ...

  5. msyql安装

    1.安装msyql yum install -y ncurses-devel automake autoconf bison libtool-ltdl-devel cd /soft wget http ...

  6. undefined symbol: _ZNSt8ios_base4InitD1

    undefined symbol: _ZNSt8ios_base4InitD1 用gcc编译C++动态库导致,用g++编译就好了

  7. STL模板整理 全排列

    概念: 从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列.当m=n时所有的排列情况叫全排列.如果这组数有n个,那么全排列数为n!个. 比如a, ...

  8. thinkphp之自动完成

    1.自动完成 自动完成是ThinkPHP提供用来完成数据自动处理和过滤的方法,使用create方法创建数据对象的时候会自动完成数据处理.  因此,在ThinkPHP使用create方法来创建数据对象是 ...

  9. 制作启动U盘

    概述 将普通的u盘制作成启动u盘,用于引导安装操作系统. 材料: 普通U盘 需要有足够的存储空间,里面的内容请提前备份. 操作系统iso文件 PowerISO 商业软件,有试用期:用来制作启动u盘 正 ...

  10. 【JavaScript】JS将Java的Timestamp转为Date类型

    遇到一个小需求,由于要填充日期插件里的数据,前台要把java后台传来的Date类型的数据转成YYYY-MM-DD格式的时间数据.通过json传输,Java的Date类型的数据自动转成了时间戳,例如 “ ...