android使用工具性能优化

简介

在这里插入图片描述
本文记录使用工具来对app进行优化过程,主要包括UI界面优化、内存优化、代码优化以及电量优化;各个优化模块是相互关联的,各个模块优化后才能达到app整体的性能提升。


UI界面优化

界面优化方面主要是减少GPU过渡绘制(也就是同一个像素点多次绘制)以及优化渲染时间,优化点主要是:

  • 减少布局层次空间
  • 视图View的绘制draw方法减少耗时操作
    如何才能完成上述的布局优化,本篇不做阐述,可以查看我的GPU调试笔记

代码优化

代码优化范围就很广,涉及的知识也很多!首先,在编码时,我们要打造一个 适合具体项目代码框架,复用相同的代码,打造高内聚、低耦合的代码;不同业务场景使用不同的数据结构,权衡时间和空间效率,复杂耗时操作要活用线程或者进程;然后,当我们编写好代码后,我们反向来检查优化代码时,可以使用一些工具来优化代码


monkey压力测试

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模拟点击功能,不然无法调试


UI优化总结

  • 布局不要嵌套太多层
  • 使用merge减少布局
  • 自定义View onDraw不要放置复杂操作
  • 合理使用validate和requestLayout

TraceView工具优化代码

当我们调试代码时,如遇到某段业务逻辑执行速率慢时,无法定位出哪块代码导致时,可以使用traceView工具进行定位;traceView工具可以展示代码执行的耗时、次数
该工具位于android sdk的tools/monitor目录下

先来看一下traceView工具界面
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进行输入过滤,能快速找到低效率函数,然后我们去查看逻辑进行优化即可


优化demo

下面,我将使用上述方法来优化一段打开相机扫一扫的逻辑,进行优化:

  1. 首先从主观感受上,我打开相机感觉比较慢,我需要定位trace代码的启动和终点
  2. 起点我使用Debug.startMethodTracing(“xxx.trace”)进行开启trace起点,假设扫一扫页面为ScanActivity;那我这个起点在打开这个页面的前一句代码
  3. 终点我设置在ScanActivity的onWindowFocusChanged方法,该方法表明activity真正可见,不要设置到onResume;使用Debug.stopMethodTracing();停止trace
  4. 最后运行app完成后,会在/sdcard/Android/data/packageName/files/xxx.trace路径下,adb pull出来
  5. 使用monitor打开这个文件;以下是我对trace文件的分析:
    注意,定位trace代码块除了上面的Debug插入代码这种方法外;还可以直接使用studio的Profile功能,在CPU性能图下面,直接点击trace start和stop
    优化点1,相机参数getParameters
    耗时函数
    通过CPU Time/Calls排序后,加上书图com过滤后发现:
    早SurfaceCreated函数内部耗时的原因是initCamera和isFlashing这两个函数;进入这两个子函数查看:
    在这里插入图片描述
    在这里插入图片描述
    如上两图,在isFlashlightAvailable和initCamera两个方法内主要的耗时都在Camera.getParameters这个函数,进一步查看函数:
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耗时减少:
在这里插入图片描述

优化总结

  • 复杂耗时逻辑放入子线程、进程或者服务端
  • 一些网络请求使用缓存
  • 大量数据分批操作,如在屏幕内时才加载 屏幕外不加载
  • 使用Service后台进行

内存优化Profile

移动端设备内存有限,需要合理使用内存资源,及时释放不需要的内存,减少内存泄漏;这里用到的工具是Android Studio的Profile,点击Run下面的Profile功能,进入MEMORY内存下面,大致如下图所示:
memory
简单介绍下上述界面各个区域:
C区域:内存时间线,内存是实时的,可以查看过去到现在任意时刻的内存状况,内存增加时,图形会上升,内存释放或者垃圾回收时,图形会下降,如C区域右下角的垃圾桶符号就是一个垃圾回收标志。

B区域:垃圾桶标志,点击后就会主动进行垃圾回收;下载符号,是对app内存进行dump标志,后面的allocation Tracking菜单选项,是内存app dump选项,有sample和full选项,sample方式dump内存是对app影响较小,而full较大;dump内存后,内存明细就时上图下班部分的样子

A区域:实时展示app内各个模块内存大小;

  • java 从java或kotlin代码分配的对象内存
  • native 从C/C++代码分配的内存
  • Graphics 从图像缓冲队列输出到屏幕的像素(如GL表面/GL纹理)所占用的内存
  • Stack 应用中原生堆栈和java堆栈所用的内存
  • Code 代码资源(dex字节码/dex优化字节码/so库和字体等)
  • others 应用占用的内存系统无法归类
  • Allocated java和kotlin代码的对象个数

D区域:dump的内存实例详细展示,左上方方框内是查看方式选择;

  • default heap:当系统未指定堆时。
  • image heap:系统启动映像,包含启动期间预加载的类。此处的分配保证绝不会移动或消失。
  • zygote heap:写时复制堆,其中的应用进程是从 Android 系统中派生的。
  • app heap:您的应用在其中分配内存的主堆。
  • JNI heap:显示 Java 原生接口 (JNI) 引用被分配和释放到什么位置的堆。

右侧的菜单中,选择如何安排分配:

  • Arrange by class:根据类名称对所有分配进行分组。这是默认选项。
  • Arrange by package:根据软件包名称对所有分配进行分组。
  • Arrange by callstack:将所有分配分组到其对应的调用堆栈。

D区域右侧还有许多菜单:
Allocations:java内存实例数量
native size:此java对象使用的native层的内存量,字节单位
shallow size:此java对象使用java内存总量
Retained Size:为此类的所有实例而保留的内存总大小

当我们选择D区域某个对象实例时,会在右侧展示EF区域
E区域:为D区域该实例拥有的所有成员变量

  • depth 对象实例到GC根节点的跳数
  • Native Size:java对象所有所拥有native层的占用的内存
  • shallow size和Retained Size同上

F区域:对象实例被别的对象持有的引用
memory的功能介绍完了,那这个工具是如何进行内存优化呢?
首先,需要给app一些压力,极限测试下,实时查看内存分配请看,有没有内存激增的异常状况;其次,主动垃圾回收后,dump内存下来,查看内存实例,主要看内存泄漏,一些该释放而没有被释放的实例。这里我们也可以配合monkey测试框架自动完成内存优化

profile memory例子

我所采用的调试方法:
我先使用monkey自动测试,随机操作app,最后然其停留在主页面HomePage,手动点击垃圾回收,dump内存查看检查,和主页面不相关的实例都不应该存在,需要检查实例的对象数量,占用内存是否太大不合理等;app的其他页面也类似,都可以按照此操作进行,以下时我dump内存发现的异常问题:

实例数量过多

too much
ServiceCreator这个类时我底层网络框架的基础类,网络访问接口都是由这个类生成的;我代码设计的本意ServiceCreator时唯一的,不会存在多个实例,那这里时不对的,查看源码如下:

//网络底层服务
@Binds
abstract IServiceCreator serviceCreator(ServiceCreator serviceCreator);

我使用的时dagger2框架,这里采用的时@Binds接口和实例绑定,而问题出在没有给他设置单例唯一,解决的办法加个注解@Singleton解决问题;不清楚dagger2的可以参考这篇文章

下面截图也是实例过多,同一个实例高达6个
too much
查看这段源码:

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,直接设置为一个监听


Profile里面的Network和Energy调试

这两块主要网络模块和能耗模块,查看网络流以及一些服务能耗。
关于Network模块,查看一些网络访问数据和频率,减少不必要的发送数据,以及减小发送频率;
Energy模块,主要是一些服务耗电,如定位等,不使用定位时,关闭定位服务。

以上就是性能调试的记录!如有不正或更好的办法,望不吝赐教!

猜你喜欢

转载自blog.csdn.net/jackzhouyu/article/details/104219693