>>分享Android开发相关的技术 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 19705 个阅读者 刷新本主题
 * 贴子主题:  android使用工具性能优化 回复文章 点赞(0)  收藏  
作者:Jacky    发表时间:2020-03-08 20:46:02     消息  查看  搜索  好友  邮件  复制  引用

                                                                                                

android使用工具性能优化

简介

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

本文记录使用工具来对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工具界面

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

如上图:

区域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

    点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

    通过CPU Time/Calls排序后,加上书图com过滤后发现:

    早SurfaceCreated函数内部耗时的原因是initCamera和isFlashing这两个函数;进入这两个子函数查看:

    点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

    点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

    如上两图,在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结果如下图:

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

          优化点2,setContentView布局

从traceView文件中发现setContentView也比较耗时:

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

setContentView时间的长短取决于布局文件xml的层叠嵌套,查看布局发现,根部据里面嵌套了一层ToolBar,在ToolBar里面有我们的左右标题TextView;我修改为去掉ToolBar布局,把左右布局提出来在根布局下面,这样整个布局文件就只有一层布局了

最后,优化后trace的结果,setContentView耗时减少:

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

优化总结

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

内存优化Profile

移动端设备内存有限,需要合理使用内存资源,及时释放不需要的内存,减少内存泄漏;这里用到的工具是Android Studio的Profile,点击Run下面的Profile功能,进入MEMORY内存下面,大致如下图所示:

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

简单介绍下上述界面各个区域:

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内存发现的异常问题:
实例数量过多
点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

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

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

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

     下面截图也是实例过多,同一个实例高达6个

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

查看这段源码:    

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模块,主要是一些服务耗电,如定位等,不使用定位时,关闭定位服务。

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

        
----------------------------
原文链接:https://blog.csdn.net/jackzhouyu/article/details/104219693

程序猿的技术大观园:www.javathinker.net



[这个贴子最后由 flybird 在 2020-03-09 22:11:29 重新编辑]
  Java面向对象编程-->多线程(上)
  JavaWeb开发-->使用Session(Ⅱ)
  JSP与Hibernate开发-->域对象在持久化层的四种状态
  Java网络编程-->Java网络编程入门
  精通Spring-->Vue组件开发高级技术
  Vue3开发-->Vue CLI脚手架工具
  Android开发教程之Java开发环境配置和运行第一个程序
  Android安卓面试复盘
  Android Application Theme的实现及管理
  Android SearchView 搜索框
  Android UI学习 - Tab的学习和使用
  众多Android 开源项目推荐
  Android忽略HTTPS安全验证的解决
  Android多线程断点续传
  Android UI优化—从Android渲染原理理解UI卡顿
  android 系统自带的卡帧警告
  Android仿Volley手写属于自己的万能网络访问框架
  Matrix源码分析
  Android开发学习笔记:浅谈WebView-IT的点点滴滴
  Android开发学习笔记:Intent的简介以及属性的详解-IT的点点...
  下拉框+Switch开关控件
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。