本文记录使用工具来对app进行优化过程,主要包括UI界面优化、内存优化、代码优化以及电量优化;各个优化模块是相互关联的,各个模块优化后才能达到app整体的性能提升。
界面优化方面主要是减少GPU过渡绘制(也就是同一个像素点多次绘制)以及优化渲染时间,优化点主要是:
代码优化范围就很广,涉及的知识也很多!首先,在编码时,我们要打造一个 适合具体项目代码框架,复用相同的代码,打造高内聚、低耦合的代码;不同业务场景使用不同的数据结构,权衡时间和空间效率,复杂耗时操作要活用线程或者进程;然后,当我们编写好代码后,我们反向来检查优化代码时,可以使用一些工具来优化代码
monkey是adb中的一个测试脚本工具,当我们输入命令后,monkey可以模拟点击、滑动、长按等操作,随机去操控我们的App,以检测App中意料之外的bug,最后输出一份测试日志文件供我们去查看出现的bug;具体的monkey测试步骤如下:
操作指令:
adb shell monkey -p packagename -s 20 --throttle 1500 --ignore-crashes --ignore-timeouts -vvv 1000
-p: 测试的包名
-s: 种子数,可以理解为各种触摸点击次数顺序是通过一个算法实现的,而这个算法需要输入一个时间因子,相同的时间因子,产生的触摸事件是一模一样的
–ignore-crashes --ignore-timeouts: 忽略测试过程中产生异常和超时,否则测试会终止
-v:表示日志等级,默认是v,vvv是verbose最详细等级
1000:表示事件的次数
–throttle:事件操作后的延时
为了保证测试覆盖App所有页面,可以写个脚本,多执行几次,变换不同的时间因子;最后去日志文件查询崩溃日志
此项功能要打开USB模拟点击功能,不然无法调试
当我们调试代码时,如遇到某段业务逻辑执行速率慢时,无法定位出哪块代码导致时,可以使用traceView工具进行定位;traceView工具可以展示代码执行的耗时、次数
该工具位于android sdk的tools/monitor目录下
先来看一下traceView工具界面
如上图:
区域A:展示的是多个线程以及线程号
区域B:A区域每个线程执行函数的时间分布图,线程中每个函数标记为一种唯一的颜色,颜色的长度标明这个函数执行时间的长度;所以从这个分布图中可以很明确的找出谁执行得慢
区域C:和区域B相关,每个函数的执行速率结果,还有函数执行顺序,当前函数由谁调起,它又调用了哪些其他函数;重点阐述横向的参数:
incl cpu time:函数获取CPU时间片执行时间,包含函数内部调用其他函数时间;
excl cpu time:函数自身CPU执行时间,不包含调用的下级函数时间
incl real time:函数执行时间,包括函数内调用其他函数时间,也包含被线程中断休眠阻塞的时间
excl real time:同上,但是不包括调用其他函数时间
Calls+RecurCalls/Total:程序调用次数+递归调用次数/总次数
Cpu Time/Call:函数执行获取时间片平均时间
Real Time/Call:函数执行时间包括休眠时间,平均时间
两个关键点:(1) 函数本身比较耗时;(2) 函数调用次数过多
首先,可以很直观的在上图B区域,快速找出颜色最长的也就是函数耗时最长的;其次,也可以在C区域点击CPU TIme/Call,C区域会自动排序长度;同理点击Calls+RecurCalls/Total自动排序调用次数;但是C区域函数比较多,我们可以在C区域下方有个Find进行输入过滤,能快速找到低效率函数,然后我们去查看逻辑进行优化即可
下面,我将使用上述方法来优化一段打开相机扫一扫的逻辑,进行优化:
public Parameters getParameters() {
Parameters p = new Parameters();
String s = native_getParameters();
//耗时主要在下面这个方法
p.unflatten(s);
return p;
}
p.unflatten会遍历相机的所有参数并封装构造到一个Map结构;这也是它耗时的原因,并且这个函数是库函数无法修改;但是我们程序多个函数都回去掉这个函数,处理的办法就是缓存获取的相机参数,并且为了保持参数的有效性,加入了一个过期时间;修改如下:
private Camera.Parameters getCamerPara(){
if(cachParams == null || System.currentTimeMillis() - lastParaTime > 60000){
cachParams = mCamera.getParameters();
lastParaTime = System.currentTimeMillis();
}
return cachParams;
}
把之前的用到Camera.getParameters()的地方全部改为这个函数即可;优化后重新trace结果如下图:
优化点2,setContentView布局
从traceView文件中发现setContentView也比较耗时:
setContentView时间的长短取决于布局文件xml的层叠嵌套,查看布局发现,根部据里面嵌套了一层ToolBar,在ToolBar里面有我们的左右标题TextView;我修改为去掉ToolBar布局,把左右布局提出来在根布局下面,这样整个布局文件就只有一层布局了
最后,优化后trace的结果,setContentView耗时减少:
移动端设备内存有限,需要合理使用内存资源,及时释放不需要的内存,减少内存泄漏;这里用到的工具是Android Studio的Profile,点击Run下面的Profile功能,进入MEMORY内存下面,大致如下图所示:
简单介绍下上述界面各个区域:
C区域:内存时间线,内存是实时的,可以查看过去到现在任意时刻的内存状况,内存增加时,图形会上升,内存释放或者垃圾回收时,图形会下降,如C区域右下角的垃圾桶符号就是一个垃圾回收标志。
B区域:垃圾桶标志,点击后就会主动进行垃圾回收;下载符号,是对app内存进行dump标志,后面的allocation Tracking菜单选项,是内存app dump选项,有sample和full选项,sample方式dump内存是对app影响较小,而full较大;dump内存后,内存明细就时上图下班部分的样子
A区域:实时展示app内各个模块内存大小;
D区域:dump的内存实例详细展示,左上方方框内是查看方式选择;
右侧的菜单中,选择如何安排分配:
D区域右侧还有许多菜单:
Allocations:java内存实例数量
native size:此java对象使用的native层的内存量,字节单位
shallow size:此java对象使用java内存总量
Retained Size:为此类的所有实例而保留的内存总大小
当我们选择D区域某个对象实例时,会在右侧展示EF区域
E区域:为D区域该实例拥有的所有成员变量
F区域:对象实例被别的对象持有的引用
memory的功能介绍完了,那这个工具是如何进行内存优化呢?
首先,需要给app一些压力,极限测试下,实时查看内存分配请看,有没有内存激增的异常状况;其次,主动垃圾回收后,dump内存下来,查看内存实例,主要看内存泄漏,一些该释放而没有被释放的实例。这里我们也可以配合monkey测试框架自动完成内存优化
我所采用的调试方法:
我先使用monkey自动测试,随机操作app,最后然其停留在主页面HomePage,手动点击垃圾回收,dump内存查看检查,和主页面不相关的实例都不应该存在,需要检查实例的对象数量,占用内存是否太大不合理等;app的其他页面也类似,都可以按照此操作进行,以下时我dump内存发现的异常问题:
ServiceCreator这个类时我底层网络框架的基础类,网络访问接口都是由这个类生成的;我代码设计的本意ServiceCreator时唯一的,不会存在多个实例,那这里时不对的,查看源码如下:
//网络底层服务
@Binds
abstract IServiceCreator serviceCreator(ServiceCreator serviceCreator);
我使用的时dagger2框架,这里采用的时@Binds接口和实例绑定,而问题出在没有给他设置单例唯一,解决的办法加个注解@Singleton解决问题;不清楚dagger2的可以参考这篇文章
window.findViewById(R.id.tv_mv_phone).setOnClickListener(this::onClickViews);
window.findViewById(R.id.tv_check_user).setOnClickListener(this::onClickViews);
window.findViewById(R.id.tv_check_private).setOnClickListener(this::onClickViews);
window.findViewById(R.id.about_us).setOnClickListener(this::onClickViews);
window.findViewById(R.id.btn_logout).setOnClickListener(this::onClickViews);
userIcon = window.findViewById(R.id.btn_login);
userIcon.setOnClickListener(this::onClickViews);
上面使用lambda表达式设置组件view监听,熟悉lambda表达式原理的人都知道这时怎么回事,不熟悉的点击这里,解决的办法就是不用lambda,直接设置为一个监听
这两块主要网络模块和能耗模块,查看网络流以及一些服务能耗。
关于Network模块,查看一些网络访问数据和频率,减少不必要的发送数据,以及减小发送频率;
Energy模块,主要是一些服务耗电,如定位等,不使用定位时,关闭定位服务。
以上就是性能调试的记录!如有不正或更好的办法,望不吝赐教!