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发展的必经之路, 欢迎大家分享经验.

作者: 一点点征服 
出处:http://www.cnblogs.com/ldq2016/ 
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利


    本文转自 一点点征服   博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/8483636.html,如需转载请自行联系原作者



相关文章
|
2月前
|
移动开发 监控 前端开发
构建高效Android应用:从优化布局到提升性能
【7月更文挑战第60天】在移动开发领域,一个流畅且响应迅速的应用程序是用户留存的关键。针对Android平台,开发者面临的挑战包括多样化的设备兼容性和性能优化。本文将深入探讨如何通过改进布局设计、内存管理和多线程处理来构建高效的Android应用。我们将剖析布局优化的细节,并讨论最新的Android性能提升策略,以帮助开发者创建更快速、更流畅的用户体验。
52 10
|
21天前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
40 20
Android经典面试题之图片Bitmap怎么做优化
|
24天前
|
Java Android开发 UED
安卓应用开发中的内存管理优化技巧
在安卓开发的广阔天地里,内存管理是一块让开发者既爱又恨的领域。它如同一位严苛的考官,时刻考验着开发者的智慧与耐心。然而,只要我们掌握了正确的优化技巧,就能够驯服这位考官,让我们的应用在性能和用户体验上更上一层楼。本文将带你走进内存管理的迷宫,用通俗易懂的语言解读那些看似复杂的优化策略,让你的开发之路更加顺畅。
32 2
|
25天前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
2月前
|
编解码 Android开发
【Android Studio】使用UI工具绘制,ConstraintLayout 限制性布局,快速上手
本文介绍了Android Studio中使用ConstraintLayout布局的方法,通过创建布局文件、设置控件约束等步骤,快速上手UI设计,并提供了一个TV Launcher界面布局的绘制示例。
37 1
|
2月前
|
Ubuntu Android开发
安卓系统调试与优化:(一)bootchart 的配置和使用
本文介绍了如何在安卓系统中配置和使用bootchart工具来分析系统启动时间,包括安装工具、设备端启用bootchart、PC端解析数据及分析结果的详细步骤。
86 0
安卓系统调试与优化:(一)bootchart 的配置和使用
|
22天前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
43 0
|
1月前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
111 0
|
2月前
|
存储 缓存 前端开发
安卓开发中的自定义控件实现及优化策略
【8月更文挑战第31天】在安卓应用的界面设计中,自定义控件是提升用户体验和实现特定功能的关键。本文将引导你理解自定义控件的核心概念,并逐步展示如何创建一个简单的自定义控件,同时分享一些性能优化的技巧。无论你是初学者还是有一定经验的开发者,这篇文章都会让你对自定义控件有更深的认识和应用。
|
2月前
|
Android开发
Android项目架构设计问题之使用动态代理来优化GoodsApiImpl中的接口实现如何解决
Android项目架构设计问题之使用动态代理来优化GoodsApiImpl中的接口实现如何解决
16 0
下一篇
无影云桌面