Android性能优化简要笔记

hloong 于 2016-09-19 发布

布局优化

0, UI渲染机制

人眼所看到的流畅画面,需要的帧数在40帧每秒到60帧每秒之间, 最佳的ftp在60左右,在Android中,系统通过VSYNC信号出发对UI的渲染、重绘,其间隔时间是16ms,这就意味着程序的大多数操作都必须在16ms内完成,这个16ms其实就是1000ms中显示60帧画面的单位时间。即1000、60,如果系统每次渲染都保持在16ms之内,那么我们看到的UI将十分的流畅,但这也是需要将所有的逻辑都保证在16ms里,如果16ms不能完成绘制,那么就会造成丢帧的现象,即当前该重绘的帧被未完成的逻辑阻塞,用户便感知到卡顿;

1,include标签
同样的布局抽出用include
解析xml布局时,检测到include就解析然后再解析被include进来的布局的root view元素

2,merge标签

image

如果如上图的布局会多一层嵌套,此时用merge标签来替换FrameLayout2可以减少嵌套

注意:默认一个activity会有一层FrameLayout(base_content)在外层,所以如果include的布局中是FrameLayout则应在activity最外层用merge标签,这样减少了层级

3,ViewStub视图
当布局需要延迟加载,点击可见的时候,采用 viewstub只占位不做具体事,宽高都为0,用户手动设置viewstub的inflate或者setVisibility为Visible时,ViewStub就从父控件移除并加载目标布局,完成视图的动态替换

4,减少视图树层级
能用RelativeLayout一个布局搞定的就不要用多层嵌套; 总结:

1,尽量多使用RelativeLayout,减少层级
2,在ListView等列表组件中避免使用Linearlayout的layout_weight属性,因为Linearlayout采用了weight属性后子view要绘制2次
3,将可复用的组件抽取用include标签
4,使用ViewStub标签加载一些不常用的布局
5,使用merge标签减少布局的嵌套布局

内存优化

0,什么是内存

由于Android的沙箱机制,每个应用所分配的内存大小是有限度的,内存太低就会触发LMK-Low Memory Killer机制。那么到底什么是内存呢?通常情况下我们所说的内存是指手机的RAM,它包括以下几个部分

寄存器(Registers) 速度最快的存储场所,因为寄存器位于处理器内部.在程序中无法控制

栈(Stack) 存放基本类型的数据和对象的引用.但对像本身不存放在栈中,而是存放在堆中 堆内存(Heap)

堆内存用来存放由new创建的对象和数组.在堆中分配的内存,由java虚拟机的自动垃圾回收(GC)管理 静态存储区域(static Field)

静态存储区域就是指在固定的位置存放应用程,子运行时一直存在的数据,java在内存中专门划分了一个静态存储区域来管理一些特殊的 数据变量如静态的数据变量 常量池(Constant Pool)

JVM虚拟机必须为每个被装载的类型维护一个常量池,常量池就是该类型所用到常量的一个有序集合,包括直接常量(基本类型,String)和对其他类型. 字段和方法的符号引用 在这些概念中最容易搞错的就是堆和栈的区分。

当定义一个变量,Java虚拟机就会在栈中为该变量分配内存空间 这部分内存空间会马上被用作新的空间进行分配,如果使用new的方式创建一个变量, 那么就会在堆中为这个对象分配内存空间,即使该时象的作用域结束, 这部分内存也不会立即被回收.而是等待系统Gc进行回收,堆的大小随着手机的不断发展而不断变大.

在过程中.可以使用如下所示的代码来获得堆的大小,所谓的 内存分析,正是分析Heap中的内存状态。

ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int heapSize = manager.getLargeMemoryClass();

1,珍惜Services资源
限制Service最好的方法是使用IntentService,它会在处理完后扔给它的intent任务之后尽快结束自己
启动一个service,系统会为了保留这个Service而一直保留Service所在的进程。持续保留Service会导致APP因RAM的限制而性能糟糕。

2,当UI隐藏时释放内存
当用户切换到其他应用而你的APP不可见,应该释放UI上所占用的所有资源,这个时候可以增加系统缓存进程的能力。
实现Activity里面的onTrimMemory()回调方法,使用该方法监听到TRIM_MEMORY_UI_HIDDEN级别的回调
扩展:http://www.cnblogs.com/xiajf/p/3993599.html

public void onTrimMemory(int level) {  
    super.onTrimMemory(level);  
    switch (level) {  
    case TRIM_MEMORY_UI_HIDDEN:  
        // 进行资源释放操作  
        break;  
    }  
} 

3,当内存紧张时释放部分内存
扩展:http://blog.csdn.net/guolin_blog/article/details/42238627
根据onTrimMemory方法中的内存级别来决定释放那些资源:
API14才支持的onTrimMemory,对于低版本,可以用onLowMemory来兼容, onLowMemory相当于TRIM_MEMORY_COMPLETE
Note:当系统开始清除LRU缓存中的进程时,尽管它首先按照LRU的顺序来操作,但是,它同样会考虑进程的内存使用量。因此,消耗越少的进程越容易被留下来。

4,检查你应该使用多少内存
通过调用getMemoryClass()来获取你的App的可用heap大小。特殊情况下,通过在manifest的application标签下添加==largeHeap=true==的属性来申明一个更大的heap空间,然后通过getLargeMemoryClass()来获取到一个更大的heap size。当然这种主要用于图片视频编辑类APP(需要消耗大量RAM)

5,避免Bitmap的浪费
加载Bitmap时,通过 BitmapFactory.Options采样压缩 ,只需要保留适配当前屏幕分辨率的数据,原图高于分辨率就需要做缩小的动作,因为增加Bitmap的尺寸会对内存呈现出2次方的增加;
扩展:http://blog.csdn.net/yudajun/article/details/9323941

6,使用优化的数据容器
用SparseArray替换HashMap,通常Hashmap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外SparseArray更加高效在于其避免了对key和value的autobox自动封装,也避免了装箱后的解箱。其他替换如:SparseBooleanArray,LongSparseArray Framework里面优化过的容器类。

7,注意内存开销
Enums的内存消耗通常是static constants的2倍,所以要避免在Android上用enums。 在Java中的每一个类都会使用大概500 bytes,每一个类的实例产生的花销是12-16 bytes,往HashMap添加一个entry需要一个额外占用的32 bytes的entry对象。

抽象是好的编程实践,但是代码会被map到内存中,所以如果抽象没有显著提升效率,就避免使用

使用Protocol Buffers 代替 JSON 扩展:
http://www.oschina.net/translate/choose-protocol-buffers?cmp
http://cxshun.iteye.com/blog/1974498
JSON数据有一个缺点:冗余太大–每条数据都包含前缀(如下面的Name和Gender),据统计,JSON数据至少有20%是无效的

[{"Name": "Jane", "Gender": 0}, {"Name": "Waith", "Gender": 1}]

与JSON不同,Protocol Buffers用二进制编码数据,而且数据的格式是事先通过一个后缀名为.proto的文件指定的, 如:

message Person {
  required string Name = 2;
  optional int32 Gender = 3;
}

8,避免使用依赖注入和外部库
针对Guice或者RoboGuice类似的注入框架,这些框架会扫描代码,消耗RAM来map代码;
外部库过多影响效率,能用自己实现的就自己实现,而不是导入一个大而全的方案;

9,使用ProGuard来删除不需要的代码
ProGuard能移除不需要的代码,重命名类,等方法,对代码进行压缩,优化和混淆。

10,对最终的APK使用zipalign 注:GooglePlay不接受没有经过zipalign的APK

11,使用多进程
如果使用不当会增加内存,当APP需要在后台运行与前天一样的大量的任务才可以用多进程
==典型的例子:==
创建一个可长时间后台播放的Music Player,如果整个APP运行在一个进程中,当后台播放 时候,前台的UI资源无法得到释放,这样类似的APP可以切分成2个进程:一个用于UI操作,一个用于后台Service; 可以通过manifest文件中申明android:process属性来实现某个组件运行在另外一个进程的操作:

<servcice  android:name=".PlaySercice"  android:process=":background" />

内存泄漏

0,使用Memory Monitor和DDMS下的Heap可以查看内存使用信息
扩展:http://blog.csdn.net/itfootball/article/details/48712595
1,内存泄漏检测–LeakCanary
扩展:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0509/2854.html

性能优化

0,过度绘制
用户界面卡顿,原因Overdraw–屏幕某像素在同一帧的时间内被绘制多次

View的onDraw方法不要执行大量操作(不要创建新的局部对象,占内存)
View的onDraw方法不要执行耗时操作,也不要执行成千上万的循环(循环抢占cpu时间片,绘制卡顿)

Android中的图形渲染,经过三个阶段:测量onMeasure,布局onLayout,绘制onDraw 通常视图层级越深,测量视图的时间就越长; 在视图渲染期间,每个View都要向它的父View提供自己的尺寸。如果父View发现了任意一个尺寸问题,它就会强制要求所有的子View重新测量。即便没有错误发生,重新测量也可能出现。

例如:为了正确地进行布局,RelativeLayout通常会对它们的子视图进行2次测量,子视图使用了layout_weight属性的LinearLayout也会对它的子视图进行2次测量; 测量和重新测量的代价昂贵,会严重影响渲染速度。想确保应用流畅需要移除那些非必须的View以及减少View层级(面试题必备)

1,使用Hierarchy Viewer
如下图:三个点分别表示,测量,布局,绘制三个阶段耗费的时间
红色表示该View的渲染速度比其他的所有参与测试的节点都慢
黄色表示该View的渲染速度慢于50%以上的其他测试节点
绿色表示该View的渲染速度至少快于一半以上的其他测试节点

image 知道哪些界面渲染比较慢再对症下药

2,数据采集和分析–TraceView
扩展:http://blog.jobbole.com/78995/

参考书目:《Android群英传》《Android开发艺术探索》《Android从小工到专家》