程序优化
对用户体验影响
```
所有的控件绘制、组件生命周期、控件回调 都是在主线程执行,因此减少主线程负担 是在16ms内能绘制完一帧 ,提升用户体验的主要因素之一
```
工具
Traceview
关于UI卡顿问题我们还可以通过运行Traceview工具进行分析,他是一个分析器,记录了应用程序中每个函数的执行时间;我们可以打开DDMS然后选择一个进程,接着点击上面的“Start Method Profiling”按钮(红色小点变为黑色即开始运行),然后操作我们的卡顿UI(小范围测试,所以操作最好不要超过5s),完事再点一下刚才按的那个按钮,稍等片刻即可出现分析图表
整个界面包括上下两部分,上面是你测试的进程中每个线程运行的时间线,下面是每个方法(包含parent及child)执行的各个指标的值。通过上图的时间面板可以直观发现,整个trace时间段main线程做的事情特别多,其他的做的相对较少。当我们选择上面的一个线程后可以发现下面的性能面板很复杂,其实这才是TraceView的核心图表,它主要展示了线程中各个方法的调用信息(CPU使用时间、调用次数等),这些信息就是我们分析UI性能卡顿的核心关注点。如下
* Incl CPU Time 当前方法(包含内部调运的子方法)执行占用的CPU时间;
* Excl CPU Time 当前方法(不包含内部调运的子方法)执行占用的CPU时间;
* Incl Real Time 当前方法(包含内部调运的子方法)执行的真实时间,ms单位;
* Excl Real Time 当前方法(不包含内部调运的子方法)执行的真实时间,ms单位;
* Calls+Recur Calls/Total 当前方法被调运的次数及递归调运占总调运次数百分比;
* CPU Time/Call 当前方法调运CPU时间与调运次数比,即当前方法平均执行CPU耗时时间;
* Real Time/Call 当前方法调运真实时间与调运次数比,即当前方法平均执行真实耗时时间;(重点关注)
有了对上面Traceview图表的一个认识之后我们就来看看具体导致UI性能后该如何切入分析,一般Traceview可以定位两类性能问题:
* 方法调运一次需要耗费很长时间导致卡顿;
* 方法调运一次耗时不长,但被频繁调运导致累计时长卡顿。
譬如我们来举个实例,有时候我们写完App在使用时不觉得有啥大的影响,但是当我们启动完App后静止在那却十分费电或者导致设备发热,这种情况我们就可以打开Traceview然后按照Cpu Time/Call或者Real Time/Call进行降序排列,然后打开可疑的方法及其child进行分析查看,然后再回到代码定位检查逻辑优化即可;当然了,我们也可以通过该工具来trace我们自定义View的一些方法来权衡性能问题
dmtracedump
可以看见,Traceview能够帮助我们分析程序性能,已经很方便了,然而Traceview家族还有一个更加直观强大的小工具,那就是可以通过dmtracedump生成方法调用图。具体做法如下:
dmtracedump -g result.png target.trace //结果png文件 目标trace文件
每次用到Traceview分析就已经搞定问题了,所以说dmtracedump自己酌情使用吧。
Systrace
Systrace其实有些类似Traceview,它是对整个系统进行分析(同一时间轴包含应用及SurfaceFlinger、WindowManagerService等模块、服务运行信息),不过这个工具需要你的设备内核支持trace(命令行检查/sys/kernel/debug/tracing)且设备是eng或userdebug版本才可以,所以使用前麻烦自己确认一下```
监控
目前在debug阶段 对cpu进行监控
优化点
缓冲
a. 线程池 连接池
b. 网络缓存
http response,
根据http头信息中的Cache-Control域确定缓存过期时间。
c. 文件IO缓存
使用具有缓存策略的输入流,BufferedInputStream替代InputStream,BufferedReader替代 Reader,BufferedReader替代BufferedInputStream.对文件、网络IO皆适用。
d. layout缓存
数据结构选择
ArrayList和LinkedList的选择,ArrayList根据index取值更快,LinkedList更占内存、随机插入删除更快速、扩容效率更高。一般推荐ArrayList。
ArrayList、HashMap、LinkedHashMap、HashSet的选择,hash系列数据结构查询速度更优,ArrayList存储有序元素,HashMap为键值对数据结构,LinkedHashMap可以记住加入次序的hashMap,HashSet不允许重复元素。
HashMap、WeakHashMap选择,WeakHashMap中元素可在适当时候被系统垃圾回收器自动回收,所以适合在内存紧张型中使用。
Collections.synchronizedMap和ConcurrentHashMap的选择,ConcurrentHashMap为细分锁,锁粒度更小,并发性能更优。Collections.synchronizedMap为对象锁,自己添加函数进行锁控制更方便。
Android也提供了一些性能更优的数据类型,如SparseArray、SparseBooleanArray、SparseIntArray、Pair。
Sparse系列的数据结构是为key为int情况的特殊处理,采用二分查找及简单的数组存储,加上不需要泛型转换的开销,相对Map来说性能更优。不过我不太明白为啥默认的容量大小是10,是做过数据统计吗,还是说现在的内存优化不需要考虑这些东西,写16会死吗,还是建议大家根据自己可能的容量设置初始值
延迟操作
Java中延迟操作可使用ScheduledExecutorService,不推荐使用Timer.schedule;
Android中除了支持ScheduledExecutorService之外,还有一些delay操作,如
handler.postDelayed,handler.postAtTime,handler.sendMessageDelayed,View.postDelayed,AlarmManager定时等
避免在主线程执行过多操作。尤其某些轻微耗时操作
可以通过TraceView工具找出主线程的耗时操作和其他耗时的线程并作优化。另外减少主线程的GC停顿,因为即使并行GC,也会对heap加锁,如果主线程请求分配内存的话,也会被挂起,所以尽量避免在主线程分配较多对象和较大的对象,特别是在onDraw等函数中,以减少被挂起的时间。另外可以通过去掉ListView ,ScrollView等控件的EdgeEffect效果,来减少内存分配和加快控件的创建时间
线程优先级
注意线程的优先级,对于占用CPU较多时间的函数,也要判断线程的优先级。重要工作 及 主线程优先级要高,其他如埋点 日志等优先级放低
减少GC次数
安卓上的GC会引起性能卡顿,必须重点优化。除了第三章会详细介绍对于图片内存引起GC的优化,我们还做了如下工作:
* 减少对象分配,找出不必要的对象分配,如可以使用非包装类型的时候,使用了包装类型;字符串的+号和扩容;Handler.post(Runnable r)等频繁使用。
* 对象的复用,对于频繁分配的对象需要使用复用池。
* 尽早释放无用对象的引用,特别是大对象和集合对象,通过置为NULL,及时回收。
* 防止泄露,除了最基本的文件、流、数据库、网络访问等都要记得关闭以及unRegister自己注册的一些事件外,还要尽量少的使用静态变量和单例。
* 控制finalize方法的使用,在高频率函数中使用重写了finalize的类,会加重GC负担,使得性能上有几倍的差别。
* 合理选择容器,在性能上优先考虑数组,即使我们现在习惯了使用容器,也要注意频繁使用容器在性能上的隐患点:首先是扩容开销, HashMap扩容时重新Hash的开销较大。其次是内存开销,HashMap需要额外的Map.Entry对象分配 ,需要额外内存,也容易产生更多的内存碎片。SparseArray和ArrayList等在内存方面更有优势。再次是遍历,对于实现了RandomAccess接口的容器如ArryList的遍历,不应该使用foreach循环。
* 用工具监控和精雕细琢:在页面滑动过程中,通过Memory Monitor查看内存波动和GC情况,还可通过AlloCation Tracker工具观察内存的分配,发现很多小对象的分配问题。
* 利用Trace For OpenGL工具找出界面上导致硬件加速耗时的点,例如一些圆角图片的处理等。
优化中间件代码
中间件的代码被上层业务方调用的比较频繁,容易有较多的高频率函数,也容易产生细节上的问题。除了频繁分配对象外,例如类初始化性能,同步锁的额外开销,接口的调用时间,枚举的使用 stringbuffer&stringbuilder 的使用 等等都是不能忽视的问题。
其他
异步 并发 算法优化 jni