题记:
不知道别人是怎么学习的,我总是觉得我学习的效率很低,昨天发生了不愉快的事情后,我痛定思痛,反思了一下自己,还是总结不够,也是工作中接触的东西太少的缘故,但是缺乏思考和总结,是我自己的问题,怪不了别人,也不怨天尤人。这段时间我把自己叫做废物,什么时候觉醒了,什么时候再改名吧。
本章主要围绕Android的性能优化来讲解,主要包含布局优化、绘制优化、内存泄露、响应速度、ListView优化、Bitap优化、线程优化等优化方式和一些目前常见到的优化工具的使用和说明,在讲解中会结合我在工作中遇到的相关问题。具体参照任玉刚-Android开发艺术探索。
一、基础
1、ANR
2、OOM
3、为什么要进行性能优化
二、布局优化
1、布局层级与测量次数
(1)合理选择父容器
(2)标签
(3)使用ConstaintLayout
2、过度绘制
(1)去掉Windwo的默认背景
(2)去掉其他不必要的背景
(3)优化onDraw方法
(4)标签
ANR,全名Application not response,应用无响应。
产生ANR的原因:通常在UI线程执行耗时任务可能会导致ANR,一般Activity中5s内无法响应按键或触摸事件,那么就会导致ANR,同样的BroadCast中10s内没有完成操作也会出现anr,service为20s。
如何分析:一般发生ANR后,系统会在data/anr目录下创建一个traces.txt文件,通过这个文件我们可以分析ANR的原因。
OOM,全称Out of Memory,内存溢出
产生内存溢出的原因:常见的比如有单例持有外部实例,Bimap没有及时释放等
如何分析:后面会讲到详细的工具和分析
(1)Android设备,通常由于体积的限制,它的cpu和内存是有限制的,因此无法和pc的大内存高cpu相比
(2)程序无限制使用cpu,会导致当前应用cpu过高,可能会导致系统卡顿甚至程序无响应(cpu都被某一块占用了,其他程序无法获得对应的cpu时间片,因此自然会出现卡顿)
(3)无限制使用内存,会导致程序使用内存过多,从而引发OOM,发生OOM后,系统的MemoryKiller会杀死一些进程,这样会导致该程序或者其他程序被杀死,从而出现闪退的现象。
Android中布局优化主要包含以下三个方面:布局层级和测量次数、布局过度绘制、绘制过程
布局层级越多,绘制耗时就会相应增加。考虑使用布局层级比较少的方案.
(1)合理选择父容器
在布局层数相同时,我们优先选择测量次数较少的父容器
通常我们选取的优先级为:FrameLayout、不带Layou_wight的LinearLayuut、RelativeLayout。因为带有Layot_weight的LinearLayout和RelativeLayout会测量两次。
总结来看,首先优先布局层级少的方案,在布局层级相同时,采用测量次数少的。
那么如何分析布局层级呢?
(1)Android Device Monitor
Android studio3.0开始,Google不建议使用它, 因此我们需要手动在sdk目录下的tools中找到它,之后运行你的apk并且选择Hierarchy View,就可以查看对应的层级关系,如下:
(2)Component Tree
在Android studio.3.0之后,我们可以使用Component Tree,它同样提供了查看组件层级的功能,具体如下:
选择并查看我们的xm布局,点击左下角的design,则可以看到左侧层级关系。
(2)标签
除了上面提到的方式外,我们还可以通过使用标签来减少层级和复用组件
(1)include标签
include标签的作用就是可以直接引用已有的布局,而不需要重新写布局。而通常会将include和merge标签相结合使用,下面会介绍merge标签。
例如:
这里include引用的是一个LinearLayout的布局,虽然我们不需要重复写这个布局,但是却增加了层级关系。
(2)merge标签
merge标签通常是作为include标签的辅助扩展,就是为了解决引入include后导致布局层级增加问题,使用merge标签后,引入的布局中的View就会作为父布局的子View。
如下:
使用include标签引入后,层级关系如下:
现在我们把标签改为merge,层级关系如下:
可以明显看到中间少了一层。
(3)ViewStub标签
ViewStub继承于View,它是一个轻量级且宽高为0的组件,本身不参与布局和绘制。因此使用它可以做到在不需要的时候不加载,在需要的时候再加载,从而提高性能。
那么如何做到需要的时候显示呢?
可以通过以下两种方式:
setVisiable(View.Visiable)或者findViewById().inflate()
(3)使用ConstaintLayout
ConstaintLayout允许在不使用任何嵌套的情况下创建复杂布局,与RelativeLayout相似,可以依赖兄弟容器和父控件之间的相对关系。常见属性:
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="@+id/iv_image"
例如:
过度绘制其实指的是屏幕内某个像素在同一帧的时间内被绘制了多次。
而在多层次重叠的UI结构里,如果不可见的UI也在绘制的话,就会导致某些像素区域被绘制多次,从而浪费大量的cpu和gpu资源。
关于绘制的原理可以参考https://blog.csdn.net/xsf50717/article/details/78444047,这里不再赘述
目前提供两种方式来检测过度绘制:
手机的开发者模式中会有一项为调试GPU过度绘制>显示GPU过度绘制,设置后,打开任何一个app,就可以看到界面上出现蓝、绿、粉、红四种颜色中的一种或者多种。
蓝色:1次过度绘制
绿色:2次过度绘制
粉丝:3次过度绘制
红色:4次过度绘制
例如:
打开Hierarchy Viewer(/'haɪərɑːkɪ/),运行模拟器,打开对应的Activity界面,就可以看到如下
其中每一个View中,下面三个点依次表示测量、布局、绘制的时间,红点和黄点表示速度慢,而蓝绿则相对好一些。
在了解了如何分析过度绘制后,我们如何去处理过度绘制?
(1)去掉Windwo的默认背景
一般来说我们使用的Activity都会有一些默认的主题,通常这个主题会有一个对应的背景,被DecoreView持有,我们自定义布局时如果又添加了一个背景图或者设置背景色,就会产生一个overdraw,因此可以考虑去掉默认的背景。
我们可以在onCreate()的setContentView之前调用
getWindow().setBackGroundDrawable(null)
或者是在theme中加入windowbackground=“null”
(2)去掉其他不必要的背景
有时候我们为layout设置一个背景,而它的子View也有背景,此时就会造成重叠。针对这种情况我们首先考虑添加背景是否需要,如果需要我们可以考虑通过selector普通状态下设置背景为透明,点击状态下设置背景来减少重绘。
(3)优化onDraw方法
在使用自定义View时,我们可以利用此方法来指定一块可见区域,用于绘制,其他区域则会被忽略,即只绘制clipRect指定的区域。
例如在图片层叠时,我们将重叠部分不再绘制,只绘制不重叠部分。直接绘制如下:
可以看到重叠部分出现了过度绘制,而实际上重叠部分我们不需要绘制底层部分,因为我们只能看到上面的图层,因此我们可以利用clipRect()来指定区域。如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
int bits = mBitmaps.length;
for (int i = 0; i < bits; i++) {
Bitmap bitmap = mBitmaps[i];
int bitW = bitmap.getWidth();
int bitH = bitmap.getHeight();
if (i != 0) {
canvas.translate(bitW / 2, 0);//每绘制完图片时,画布向前平移width/2的长度
}
canvas.save();
if (i != bits - 1) {
canvas.clipRect(0, 0, bitW / 2, bitH);//选择绘制区域,每次只绘制图片的一半
}
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.restore();
}
canvas.restore();
}
(4)标签
也就是前面提到的include、merge、viewStub标签