Android性能全面分析与优化方案研究—几乎是史上最全最实用的

Android性能全面分析与优化方案研究—几乎是史上最全最实用的,第1张

Android性能全面分析与优化方案研究—几乎是史上最全最实用的

内存大小:峰值越低越好,需要优化前后做对比

内存泄漏:需要用工具检查对比优化前后

3、功耗

单位时间内的掉电量,掉电量越少越好,业内没有固定标准。华为有专门测试功耗的机器,以及自己的标准。


一、渲染问题


先来看看造成应用UI卡顿的常见原因都有哪些?

1、人为在UI线程中做轻微耗时 *** 作,导致UI线程卡顿;

2、布局Layout过于复杂,无法在16ms内完成渲染;

3、同一时间动画执行的次数过多,导致CPU或GPU负载过重;

4、View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重;

5、View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;

6、内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染 *** 作;

7、冗余资源及逻辑等导致加载和执行缓慢;

8、臭名昭著的ANR;

大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。(Google官方说的)

Android系统每隔16ms发出VSYNC信号(vertical synchronization --场扫描同步,场同步,垂直同步),触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数 *** 作都必须在16ms(1000/60=16.67ms)内完成。

如果你的某个 *** 作花费时间是24ms,系统在得到VSYNC信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那么用户在32ms内看到的会是同一帧画面。

1、过度绘制

Overdraw(过度绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。

在多层次的UI结构里面,如果不可见的UI也在做绘制的 *** 作,这就会导致某些像素区域被绘制了多次。

这就浪费大量的CPU以及GPU资源,找出界面滑动不流畅、界面启动速度慢、手机发热。

如何查看过度绘制?

设置 — 开发中选项 — 调试GPU过度绘制

来看看手雷里的过度绘制和优化效果(目前手雷还存在很多待优化的页面)

上图中的各种颜色都代表什么意思?

每个颜色的说明如下:

原色:没有过度绘制

紫色:1 次过度绘制

绿色:2 次过度绘制

粉色:3 次过度绘制

红色:4 次及以上过度绘制

造成过度优化的关键是什么?多余的背景(Background)

接下来举例说明:

1、MainTabActivity

在MainTabActivity的Theme中修改背景

去除布局(main_activity_linerlayout.xml)中的background

如果不给当前Activity设置主题,默认主题是什么,默认主题背景是什么?

可以在默认主题中添加通用主题背景

@drawable/common_layout_content_bkg

去除背景

null

2、除了布局中多余背景,还有可能在代码里添加了多余的背景。

查看分享d窗的布局代码发现只有一个background,但为什么会过度绘制呢?

代码修改(SharePlatformsDialog.java)

3、d窗底部布局不会导致d窗本身过度绘制

d窗的绘制是属于剪切式绘制不是覆盖绘制,蒙层是透明度亮度的调节不是绘制一层灰色。

如果我们不想用系统dialog而是自定义一个d窗view,就需要考虑过度绘制问题。

4、自定义view时,通过Canvas的clipRect方法控制每个视图每次刷新的区域,这样可以避免刷新不必要的区域,从而规避过渡绘制的问题。还可以使用canvas.quickreject()来判断是否和某个矩形相交,从而跳过那些非矩形区域内的绘制 *** 作。参考:http://jaeger.itscoder.com/android/2016/09/29/android-performance-overdraw.html

优化方法和步骤关键总结

总结一下,优化步骤如下:

1、移除或修改Window默认的Background

2、移除XML布局文件中非必需的Background

3、按需显示占位背景图片

4、控制绘制区域

2、布局优化


布局太过复杂,层级嵌套太深导致绘制 *** 作耗时,且增加内存的消耗。

我们的目标就是,层级扁平化

布局优化的建议:

1、第一个建议:可以使用相对布局减少层级的就使用相对布局,否则使用线性布局。Android中RelativeLayout和LinearLayout性能分析,参考:http://www.jianshu.com/p/8a7d059da746#

2、第二个建议:用merge标签来合并布局,这可以减少布局层次。

3、第三个建议:用include标签来重用布局,抽取通用的布局可以让布局的逻辑更清晰明了,但要避免include乱用。

4、第四个建议:避免创建不必要的布局层级。(最容易发生的!)

5、第五个建议:使用惰性控件ViewStub实现布局动态加载

如何借助工具查看代码布局?

Android SDK 工具箱中有一个叫做 Hierarchy Viewer 的工具,能够在程序运行时分析 Layout。

可以用这个工具找到 Layout 的性能瓶颈

该工具的使用条件:模拟器或者Root版真机。

如何开启该功能:AndroidStudio中,Tools — Android — Android Devices Monitor

该工具的缺点:使用起来麻烦。

看看项目中遇到的问题(MainTabAvtivity)。

1、merge标签的使用

未使用merge,例如:XLTabLayout.java

使用merge,例如:账号信息页的条目UserAccountItem

2、include标签的使用导致的问题

3、避免创建不必要的层级(MainTabActivity)

4、ViewStub的使用

这个标签最大的优点是当你需要时才会加载,使用他并不会影响UI初始化时的性能。

通常情况下我们需要在某个条件下使用某个布局的时候会通过gone或者invisible来隐藏,其实这样的方式虽然隐藏了布局,但是当显示该界面的时候还是将该布局实例化的。使用ViewStub可以避免内存的浪费,加快渲染速度。

其实ViewStub就是一个宽高都为0的一个View,它默认是不可见的,只有通过调用setVisibility函数或者Inflate函数才会将其要装载的目标布局给加载出来,从而达到延迟加载的效果,这个要被加载的布局通过android:layout属性来设置。

当准备inflate ViewStub时,调用inflate()方法即可。还可以设定ViewStub的Visibility为VISIBLE或INVISIBLE,也会触发inflate。注意的是,使用inflate()方法能返回布局文件的根View。

((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);

// or

View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

setVisibility的时候会触发了inflate

注意:使用ViewStub加载的布局中不能使用merge标签。

看看Space标签(不常用)

space标签可以只在布局文件中占位,不绘制,Space标签有对应的java类Space.java,

通过阅读源码可以发现,它继承至View.java,并且复写了draw方法,

该方法为空,既没有调用父类的draw方法,也没有执行自己的代码,表示该类是没有绘制 *** 作的,

但onMeasure方法正常调用,说明是有宽高的。

主要功能用来设置间距,这个标签不常用,常使用margin或padding。

3、介绍一下查看渲染性能的工具


GPU呈现模式分析(大致定位问题)

开发者选项 — GPU呈现模式分析 — 选择“在屏幕上显示为条形图”

![这里写图片描述](https://img-blog.csdn.net/2018052414193242?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2h1YW5neGlhb2d1bzE=/font
/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

Android开发者选项——Gpu呈现模式分析,参考:http://www.voidcn.com/blog/gjy211/article/p-6210447.html

自动播放的视频停止的时候会有两条很长的柱线,下个视频播放的时候还会有一条。这里有一个明显的卡顿。

播放器 *** 作(DefaultPlayerView.java - doPlay,player_auto_control_layout.xml):

上图的E total time = 68 是播放器停止播放的时候耗费的时间。

total time = 29 是播放器开始播放的时候耗费的时间。

其中,大部分时间耗费在了 5total time = 18 上面,这个是inflate播放器界面的时候耗费的时间。

是不是所有的inflate都很耗费时间,看一下账号信息页:

GPU Monitor

启用严格模式(不止渲染性能)

应用在主线程上执行长时间 *** 作时会闪烁屏幕。

通过代码进行严格模式(StrictMode)调试,参考:http://www.tuicool.com/articles/ueeM7b6

二、内存问题


1、内存浪费


程序内存的管理是否合理高效对应用的性能有着很大的影响。

推荐阅读Android性能优化典范-第3季,参考:http://hukai.me/android-performance-patterns-season-3/

ArrayMap(我们项目中没有用到,Android源码中很多使用)

Android为移动 *** 作系统特意编写了一些更加高效的容器,例如ArrayMap、SparseArray。

为了解决HashMap更占内存的弊端,Android提供了内存效率更高的ArrayMap。

1、先来看看HashMap的原理

HashMap的整体结构如下:

存储位置的确定流程:

2、再看来看看ArrayMap是如何优化内存的

它内部使用两个数组进行工作,其中一个数组记录key hash过后的顺序列表,

另外一个数组按key的顺序记录Key-Value值,如下图所示:

当你想获取某个value的时候,ArrayMap会计算输入key转换过后的hash值,然后对hash数组使用二分查找法寻找到对应的index,

然后我们可以通过这个index在另外一个数组中直接访问到需要的键值对。

既然ArrayMap中的内存占用是连续不间断的,那么它是如何处理插入与删除 *** 作的呢?它跟HashMap有什么区别?

二者之间的删除插入效率有什么差异?请看下图所示,演示了Array的特性:

HashMap与ArrayMap之间的内存占用效率对比图如下:

与HashMap相比,ArrayMap在循环遍历的时候更加高效。

什么时候使用ArrayMap呢?

1、对象个数的数量级最好是千以内,没有频繁的插入删除 *** 作

2、数据组织形式包含Map结构

Autoboxing(避免自动装箱)

Autoboxing的行为还经常发生在类似HashMap这样的容器里面,对HashMap的增删改查 *** 作都会发生了大量的autoboxing的行为。

当key是int类型的时候,HashMap和ArrayMap都有Autoboxing行为。

SparseArray(项目中用到较多 – 后面再说如何利用工具查找该用SparseArray而没有用到的地方)

为了避免Autoboxing行为Android提供了SparseArray,此容器使用于key为int类型。

SparseBooleanMap,SparseIntMap,SparseLongMap等容器,是key为int,value类型相应为boolean、int、long等。

Enum(枚举,项目中较多使用,应尽量避免)

Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.

Android官方强烈建议不要在Android程序里面使用到enum。

关于enum的效率,请看下面的讨论。假设我们有这样一份代码,编译之后的dex大小是2556 bytes,

在此基础之上,添加一些如下代码,这些代码使用普通static常量相关作为判断值:

增加上面那段代码之后,编译成dex的大小是2680 bytes,相比起之前的2556 bytes只增加124 bytes。假如换做使用enum,情况如下:

使用enum之后的dex大小是4188 bytes,相比起2556增加了1632 bytes,增长量是使用static int的13倍。

不仅仅如此,使用enum,运行时还会产生额外的内存占用,如下图所示:

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5708527.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存