主要介绍内容:
- 布局优化
- 内存优化
- 常用的一些优化工具
布局优化
系统在渲染 UI 界面的时候将消耗大量的资源,一个好的 UI 不仅应该具有良好的视觉效果,更应该具有良好的使用体验,因此布局优化就显得非常重要。
第 1 招:Android UI 渲染机制
人眼所感觉的流畅画面,需要画面的帧数达到 40帧每秒到 60帧 每秒,对于玩过 PC 游戏的朋友应该对 帧数 的概念比较清楚,最佳 fps 大概在 60fps 左右,这也是评价一个显卡性能高低的标准之一。在 Android 中,系统通过 VSYNC 信号触发对 UI 的渲染、重绘,其间隔时间是 16ms。这个 16ms 其实就是 1000ms 中显示 60帧 画面的单位时间,即 1000/60。如果系统每次渲染的时间都保持在 16ms 之内,那么我们看见的 UI 界面将是非常流畅的,但这也就需要将所有程序的逻辑都保证在 16ms 内。如果不能在 16ms 内完成绘制,那么就会造成丢帧的现象,即当前该绘制的帧被未完成的逻辑阻塞,例如一次绘制任务耗时 20ms,那么在 16ms 系统发出 VSYNC 信号时就无法绘制,该帧就会被丢弃,等待下次信号才开始绘制,导致 16*2ms 内显示的都是同一帧画面,这就是画面卡顿的原因。
Android 系统提供了检测 UI 渲染时间的工具——“Profile GPU Rendering” 打开“开发者选项”,选择“Profile GPU Rendering”(我的是 vivo的,选择 “GPU 呈现模式分析”即可)。
每一条柱状线都包含三部分,蓝色代表测量绘制 Display List 的时间,红色代表 OpenGL 渲染 Display List 所需要的时间,黄色代表 CPU 等待 GPU 处理的时间。中间的绿色横线代表 VSYNC 时间 16ms。
更多详细关于“Profile GPU Rendering”的详细参数介绍请看以下博客:
第 2 招:避免 Overdraw
Overdraw ,过度绘制会浪费很多的 CPU、GPU 资源,例如系统默认会绘制 Activity 的背景,而如果再给布局绘制了重叠的背景,那么默认 Activity 的背景就属于无效的过度绘制——Overdraw。Android 系统在开发者选项中提供了这样一个检测工具——“Enable GPU Overdraw”(我的是 vivo 手机,选择“调试 GPU 过度绘制”——“显示过度绘制区域”即可打开)。激活后,可以通过界面上的颜色来判断 Overdraw 的次数,一般会有五种颜色:
- 没有颜色:意味着没有 Overdraw,像素只画了一次
- 蓝色:意味着 Overdraw 的 1倍。像素绘制了两次,大片的蓝色是可以接受的(若整个窗口都是蓝色,可以去掉考虑一层绘制)
- 绿色:意味着 Overdraw 的 2倍,像素绘制了三次,中等大小的绿色区域是可以接受的,但你应该尝试优化、减少它们。
- 浅红:意味着 Overdraw 的 3倍,像素绘制了四次,小范围可以接受。
- 暗红:意味着 Overdraw 的 4倍,像素绘制了五次或者更多,这是错误的,需要修复。
第 3 招:优化布局层次
在 Android 中,系统对 View 进行测量、布局和绘制时,都是通过对 View 树的遍历来进行操作的。如果一个 View 树的高度太高,就会严重影响测量、布局和绘制的速度,因此优化布局的第一个方法就是降低 View 树的高度, Google 也在其 API 文档中建议 View 树的高度不宜超过 10 层。
不知道大家是否注意到,在早起的 Android 版本中,Google 使用 LinearLayout 作为默认创建的 XML 文件的根布局,而现在版本的 Android 中,Google 已经使用 RelativeLayout 来替代 LinearLayout 作为默认的根布局,其原因就是通过扁平的 RelativeLayout 来降低到通过 LinearLayout 嵌套所产生布局树的高度,从而提高 UI 渲染的效率。注意一点:这里并不是说明 RelativeLayout 的效率要高于 LinearLayout,反而在布局层数比较少的情况下,LinearLayout 的效率是高于 RelativeLayout;只有在布局层数比较深的情况下,布局深度越深,RelativeLayout 的优势越明显。
第 4 招:避免嵌套过多无用布局
嵌套的布局会让 View 树的高度变得越来越高,因此在布局时,需要根据自身布局的特点来选择不同的 Layout 组件,从而避免通过某一种 Layout 组件来实现功能时的局限性,从而造成过多的情况发生。
第 5 招:使用 < include > 标签重用 Layout
在一个应用程序界面中,为了风格上的统一,很多界面都会存在一些共通的 UI,比如一个应用的 Topbar、Bottombar等。对于这些共通的 UI,如果在每个界面中都来复制一段这样的代码,不仅不利于后期代码的维护,更增加了程序的冗余度。因此,可以使用 < include > 标签来定义这样一个共通的 UI。
具体的使用例子这里就不在演示了,不过有一点是在使用 < include > 时需要注意的:如果你需要在 < include > 标签中覆盖类似原布局中 android:layout_XXXXX 的属性,就必须在 < include > 标签中同时指定 android:layout_width 和 android:layout_height 属性。
第 6 招:使用 < ViewStub > 实现 View 的延时加载
除了把一个 View 作为共通 UI,并通过 < include > 标签来进行引用之外,还可以使用 < ViewStub >标签来实现对一个 View 的引用并实现延迟加载。< ViewStub > 是一个非常轻量级的组件,它不仅不可视,而且大小为 0。下面通过一个实例来演示如何使用 < ViewStub >。
首先,创建一个布局,这个布局在初始化加载时不需要显示,只在某些情况下才显示出来,例如查看用户信息时,只有点击某个按钮时,用户详细信息才显示出来。我们写一个简单的布局文件,代码如下所示:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#00ff00"
android:gravity="center"
android:text="not often use layout"
android:textSize="30sp"></TextView>
接下来,与使用 < include > 标签类似,在主布局中的 < ViewStub > 中的 layout 属性来引用上面的布局,代码如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.layoutinflate.mk.www.bitmapdemo.MainActivity">
<Button
android:id="@+id/bt_v"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="btn Visible"/>
<Button
android:id="@+id/bt_g"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="btn Gone" />
<Button
android:id="@+id/bt_i"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="btn Inflate" />
<ViewStub
android:id="@+id/vs"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout="@layout/show_layout"/>
</LinearLayout>
运行程序后,我们发现,< ViewStub > 标签中引用的布局的确没有显示出来,那么如何重新加载显示的布局呢?
首先,通过普通的 findViewById() 方法找到 < ViewStub > 组件,这点与一般的组件基本相同;接下来,有两种方式来重新显示这个 View。
- VISIBLE
通过调用 ViewStub 的 setVisibility() 方法来显示这个 View,代码如下所示:
vs.setVisibility(View.VISIBLE);
- inflate
通过调用 ViewStub 的 inflate() 方法来显示这个 View,代码如下所示:
vs.inflate();
这两种方式都可以让 ViewStub 重新展开,显示引用的布局,而唯一的区别就是 inflate() 方法可以返回引用的布局,从而可以在通过 View.findViewById() 方法来找到对应的控件,这是两者的区别。
而不管使用那种方式,一旦 < ViewStub > 被设置为可见或者被 inflate 了, < ViewStub > 就不存在了,取而代之的是被 inflate 的 Layout,这也是为什么两次调用 inflate 方法会报错的原因。
到这里为止,可能大家会存在一个疑问? < ViewStub > 标签与设置 View.GONE 这种方式来隐藏一个 View 有什么区别呢?的确,它们的共同点都是初始化时不会显示,但是 < ViewStub > 标签只会在显示时,才去渲染整个布局,而 View.GONE,在初始化布局树的时候就已经添加在布局数上了,相比之下 < ViewStub > 标签的布局具有更高的效率。
第 7 招:使用 < merge > 标签
< merge > 标签有什么用呢?简单来说其实就是省去一个层级。
为什么这么说呢?打个比方:就用前面介绍的 < include > 标签来说吧,如果我们在使用 < include > 标签的时候采用的是竖直方向的 LinearLayout,而刚好这个要被 < include >包含的布局文件中也采用了竖直方向的 LinearLayout,那么显然被包含的布局文件中的 LinearLayout 是多余的,通过 < merge > 标签就可以去掉多余的那一层 LinearLayout,如下所示:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="one"></Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="two"></Button>
</merge>
第 8 招:Hierarchy Viewer
Hierarchy Viewer 不用所说,大家应该都用过,确实是一个很牛叉的分析工具。不过,通常情况下,Hierarchy Viewer 无法在真机上进行使用,它只能在工厂的 Demo 机和模拟器上使用,即非加密过的设备。Google 的 “大神”—— Romain Guy 提供了一个开源项目 View Server ,通过这个程序可以让普通的手机也能使用 Hierarchy Viewer,有兴趣的朋友可以去如下所示的网址了解一下。
https://github.com/romainguy/ViewServer
下面在模拟器中使用这个工具,它位于 sdk\tools 目录下,在命令行中输入 hierarchyviewer.bat 启动程序,启动后的界面如图所示:
选择要调试的进程,即测试进程,然后点击上面的 “Load View Hierarchy ”按钮,显示界面如图所示:
为了测试这个工具,这里写了一个非常冗余的布局文件,代码如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.layoutinflate.mk.www.bitmapdemo.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I'm Button" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
这里使用 三层 的 LinearLayout 嵌套,只装载了一个 Button,很显然这些 LinearLayout 都是冗余的。下面利用 Hierarchy Viewer 工具,使用前面所说的方法打开这个布局,通常情况下,重点关注 ID 为 content 的 FrameLayout 的分支,这也是 setContentView() 所设置的内容,
在这里可以看见 三层 LinearLayout,而且这 三层 LinearLayout 都没有任何的分支。这说明了这些 LinearLayout 都是可以直接去掉的,当点击其中一个 View 的时候,可以显示该 View 的绘制情况。不过,第一次点击的时候,各种显示的时间都是 NA,需要点击菜单中的 “Profile Node ”按钮重新进行计算,才能获取绘制信息,如图:
此时就可以知道每个 View 所绘制的时长,并且系统在下方也给出了三个不同颜色的小圆点,用来表示绘制的效率,绿、黄、红分别代表 好、中、差三种不同的绘制效率。
通过 Hierarchy Viewer 工具就可以 很快地在视图树中找到冗余的布局,从而有目的地优化布局。同时 Hierarchy Viewer 工具还可以显示很多有用的信息(当前布局的padding、text、Scrolling、等等),如图:
总之,Hierarchy Viewer 是进行布局优化的一个非常有用的工具,大家可以在官方 API 文档中找到更多更详细的使用教程。
内存优化
有关于内存优化主要体现在两方面:Bitmap 优化 和 代码优化
Bitmap 优化
Bitmap 是造成内存占用过高甚至是 OOM(out of Memory)的最大威胁。下面给出一些使用 Bitmap 的小技巧。
- 使用适当分辨率和大小的图片
由于 Android 系统在做资源适配的时候会从不同分辨率文件夹下的图片进行缩放来适配相应的分辨率,如果图片分辨率与资源文件夹分辨率不匹配或者图片分辨率太高,就会导致系统消耗更多的内存资源。同时,在适当的时候,应该显示合适大小的图片,例如在图片列表界面可以使用图片的缩略图,而在显示详情图片的时候在显示原图; - 及时回收内存
一旦使用完 Bitmap 后,一定要及时使用 bitmap.recycle() 方法释放内存资源。自 Android 3.0 之后,由于 Bitmap 被放置到了堆中,其内存有 GC 管理,就不要进行释放了。 - 使用图片缓存
- 通过内存缓存(LruCache)和硬盘缓存(DiskCache)可以更好地使用 Bitmap。
- 使用适当分辨率和大小的图片
- 代码优化
任何 Java 类,都将占用大约 500 字节的内存空间。创建一个类的实例会消耗大约 15字节 的内存。从代码的实现方式上,也可以对内存进行优化。
- 对常量使用 static 修饰符。
- 使用静态方法,静态方法会比普通方法提高 15% 左右的访问速度。
- 减少不必要的成员变量,这点在 Android Lint 工具上已经集成检测了,如果一个变量可以定义为局部变量,则会建议你不要定义为成员变量。
- 减少不必要的对象,使用基础类型会比使用对象更加节省资源,同时更应该避免频繁创建短作用域的变量。
- 尽量不要使用枚举、潮涌迭代器。
- 对 Cursor、Receiver、Sensor、File对象,要非常注意对它们的创建、回收与注册、解注册。
- 避免使用 IOC 框架,IOC 通常使用注解、反射来进行实现,虽然现在 Java 对反射的效率已经进行了很好的优化,但大量使用反射依然会带来性能的下降。
常用的一些优化工具
1、使用 Android Studio 的 Memory Monitor 工具
Memory Monitor 工具是 Android Studio 自带的一个内存监视工具,它可以很好地帮助我们进行内存实时分析。通过点击 Android Studio 右下角的 “Memory Monitor” 标签,打开 “Memory Monitor”工具。Memory Monitor 是Android studio 提供的性能分析工具, 可以通过视图直观的看到android应用的内存,CPU占用情况。
2、使用 TraceView 工具优化 APP 性能
关于 TraceView 的使用网上有很多栗子,这里就不在介绍了(主要是内下面给出的博客地址中已经介绍的很详细了,嘻嘻嘻)
不需要太多,自我感觉把这三篇博客搞定,TraceView 也就搞定了….
3、使用 MAT 工具分析 APP 内存状态
MAT(Memory Analyzer Tool) 工具是一个分析内存的强力助手。
具体关于 MAT 工具的使用这里也不再进行详细的说明了,网上文章多的是,给出介绍的比较好的一篇文章,有兴趣的可以自行了解:
4、使用 Dumpsys 命令分析系统状态
使用 Dumpsys 命令可以列出 Android 系统相关的信息和服务的状态,Dumpsys 命令的功能非常强大,可使用的参数配置也非常多。老规矩这里不在进行详细介绍,给出相关文章连接: Android 性能优化 五 性能分析工具dumpsys的使用
这篇文章的内容就到此为止,其实除了上面介绍的接种优化分析工具以外,还有很多工具,有兴趣了解的可以具体参看这篇文章进行深度了解:九大工具助你玩转Java性能优化