Android 优化系列总结

507 阅读9分钟

App稳定性优化

稳定性优化主要包括这些方面:启动速度,内存优化,绘制优化

启动优化

TraceView

  • 使用方式

1、代码中添加:Debug.startMethodTracing()、检测方法、Debug.stopMethodTracing()。(需要使用adb pull将生成的**.trace文件导出到电脑,然后使用Android Studio的Profiler加载)

2、打开Profiler -> CPU -> 点击 Record -> 点击 Stop -> 查看Profiler下方Top Down/Bottom Up 区域找出耗时的热点方法

  • TraceView特点

1、图形的形式展示执行时间、调用栈等。

2、信息全面,包含所有线程。

3、运行时开销严重,整体都会变慢,得出的结果并不真实。

4、找到最耗费时间的路径:Flame Chart、Top Down。

5、找到最耗费时间的节点:Bottom Up。

Systrace

  • 使用方式 1 定义Trace静态工厂类,将Trace.begainSection(),Trace.endSection()封装成i、o方法,然后再在想要分析的方法前后进行插桩即可 2 在命令行下执行systrace.py脚本,生成分析报告文件(html格式) 3 在UIThread一栏可以看到核心的系统方法时间区域和我们自己使用代码插桩捕获的方法时间区域。
  • 原理 1 在系统的一些关键链路(如SystemServcie、虚拟机、Binder驱动)插入一些信息(Label); 2 通过Label的开始和结束来确定某个核心过程的执行时间;把这些Label信息收集起来得到系统关键路径的运行时间信息,最后得到整个系统的运行性能信息; 3 Android Framework里面一些重要的模块都插入了label信息,用户App中可以添加自定义的Lable
  • 特点 1 结合Android内核的数据,生成Html报告。 2 系统版本越高,Android Framework中添加的系统可用Label就越多,能够支持和分析的系统模块也就越多。 3 必须手动缩小范围,会帮助你加速收敛问题的分析过程,进而快速地定位和解决问题

优化方案

  • 通过设置windowBackground属性预先显示一个启动图片避免启动白屏问题(用户体验上的方案)
  • 按需加载第三方库(懒加载)
  • 线程优化: 1 尽量用rxjava或线程池的方式而不是new Thread 2 线程收敛:提供统一线程池而不是每个业务自己维护一个,并注意io,cpu密集型任务区分。 3 根据业务情况设置线程优先级
  • 异步优化:通过子线程分担主线程任务
  • 延迟初始化:延时任务,界面展示后调用(postDelayed);采用IdleHandler,在CPU空闲时进行延时任务处理
  • 页面数据本地化预加载

内存优化

内存问题包括内存泄漏,内存抖动等

Memory Profiler+MAT

可以使用Android Studio的Profiler工具(例子:Bitmap的使用)查看内存抖动。并可以借助MAT工具查找内存泄漏,在主动gc几次后,在profile的memory视图导出dump文件(hprof),最后放到MAT工具中分析,可以看到内存中实例对象个数和大小,大对象的引用关系。

LeakCanary+MAT

先通过LeakCanary检查,然后再用MAT分析,用于内存泄漏检测

内存泄漏原因

  • 资源对象未关闭
  • 对象未取消注册
  • 静态变量持有对象
  • 非静态内部类的静态实例
  • Handler临时性内存泄漏
  • 容器中的对象未清理
  • WebView内存泄漏(只要使用一次,内存就不会释放掉,一般开启一个独立进程)

优化方法

  • 通过内存复用避免内存抖动:避免循环体内创建对象,注意View的绘制方法会被频繁调用,不能创建太多对象;bitmap要注意缓存复用;使用对象池技术缓存大对象
  • 合理使用对象引用避免内存泄漏
  • 使用最优数据类型:多使用ArrayMap替换HashMap,使用注解替代枚举类型
  • LruCache
  • 图片优化:合理使用图片规格(RGB_365);位图缩放;

绘制优化

  • Android的显示过程:应用程序将Measure,Layout,Draw等绘制后的suface数据交给SurfaceFlinger渲染到显示屏上,Android系统每隔16ms发出VSYNC信号,收到信号后,通过Choreographer调度更新界面。
  • 主动调用invalidate刷新:最终调用schedualTraversal,但不会立即执行,而是在Choregrapher的队列里添加一个重绘Runnable,同时向系统申请VSYNC,并添加同步栅栏直到VSYNC到来解除栅栏,保证VSYNC到来后能及时执行重绘Runnable。
  • Android的绘制是通过CPU和GPU以及刷新机制共同完成,CPU负责计算显示的内容,如Measure,Layout等操作然后转成纹理,最后传给GPU,GPU负责栅格化,即将UI元素拆分显示到不同像素上。
  • 绘制优化可以分成两个部分,布局优化和卡顿优化。

硬件加速

  • 软件绘制和硬件绘制的区别:软件绘制最终使用Skia库(2D图像框架)绘制;硬件绘制使用OpenGL(2D、3D) androd_rendering_process.png
  • 硬件加速原理就是将CPU不擅长的图形计算转换成GPU专用指令
  • 硬件加速是把View的绘制函数转化为使用OpenGL的函数来进完成实际的绘制的,存在OpenGL中不支持原始回执函数的情况,对于这些绘制函数,就会失效
  • 需要把系统中OpenGL加载到内存中,OpenGL API调用就会占用8MB,而实际上会占用更多内存,并且使用了硬件必然增加耗电量
  • 硬件加速优势在于:不需要每次重绘都执行大量的代码,基于软件的绘制模式会重绘脏区域内的所有控件,而硬件加速只会更新列表,然后绘制列表内的控件
  • 硬件加速问题:内存消耗较大;某些APi存在Android版本上的兼容问题;耗电
  • CPU更擅长复杂逻辑控制,而GPU得益于大量ALU和并行结构设计,更擅长数学运算

工具

  • Android 自带的卡顿检测工具Profile GPU Rendering;调试GPU过度绘制选项
  • LayoutInspector:可以很方便地看到每一个界面的布局层级,帮助我们对层级进行优化。
  • Android Studio的Profile工具
  • TraceView:可以分析函数调用过程,方法调用次数,递归次数,方法耗时
  • Systrace:可以到每帧的具体耗时以及这一帧在布局当中它真正做了什么。得到分析报告(黄色或红色的渲染时间超过了16ms)

布局导致卡顿的原因

16ms发出Vsync信号触发UI渲染,如果16ms内不能完成渲染过程,就会造成掉帧现象,即卡顿。根本原因有两个: 1 绘制任务太重、绘制一帧内容耗时太长 2 主线程繁忙,Vsync到来时还没准备好数据 具体案例有:

  • xml是通过IO的方式加载到内存中
  • 布局加载是通过反射过程
  • 层级比较深则遍历时耗时大
  • 不合理的嵌套RelativeLayout导致重绘次数增多

优化方式

  • 异步加载xml(AsyncLayoutInflater):在子线程中对我们的Layout进行加载,而加载完成之后会将View通过Handler发送到主线程来使用
  • 在编译时APT的方式将XML布局转换为Java的方式进行布局。省去了使用IO的方式去加载XML布局的耗时过程。采用Java代码直接new的方式去创建控件对象,所以它也没有反射带来的性能损耗。这样就从根本上解决了布局加载过程中带来的问题
  • 容器布局选择,如使用ConstraintLayout去减少我们界面布局的嵌套层级
  • 合理使用XML标签 Merge: 减少视图层级,可以删除多余的层级。 ViewStub: 按需加载,减少内存使用量、加快渲染速度、不支持 merge 标签。原理:获取到真正的布局文件渲染成view后替换到viewstub标签所在的位置,因此只能inflate一次。
  • 避免过度绘制:包括xml是否重复设置背景;View的onDraw同一个区域绘制多次
  • 合理使用刷新机制:减少刷新次数和刷新区域
  • 提升动画性能:减小运算量;少用帧动画
  • 注意硬件加速:原理是打开硬件加速渲染View时,会将最后的所有绘制命令放入View的DisplayList,并由OpenGLRender负责将其渲染到屏幕上。如果只使用了标准View和Drawable,可以全局开启,需要注意开启硬件加速可能需要额外内存,若存在过度绘制,硬件加速比较容易发生问题

卡顿监控方案

参考BlockCanary,利用Looper中的Printer来实现监控 blockcanary的核心原理是通过自定义一个Printer,设置到主线程ActivityThread的MainLooper中。MainLooper在dispatch消息前后都会调用Printer进行打印。从而获取前后执行的时间差值,判断是否超过设置的阈值。如果超过,则会将记录的栈信息及cpu信息发通知到前台

App瘦身(包体积优化)

  • 通过Lint工具扫描工程资源,删除无用资源,压缩资源
  • 使用一套图片资源,一般采用xhdpi下的图片
  • 避免使用帧动画,可使用Lottle等动画库(最终是以json文件形式导入)
  • 动态下载资源,字体,图片等
  • 统一应用颜色风格
  • 图片优化压缩,使用svg,xml,drawable等
  • 开启混淆(proguard作用:压缩(删除没有使用的类,字段,方法);优化(分析和优化Java字节码),混淆(使用简短无意义的名称对类,方法,字段重命名))
  • lib目录优化,设置对主流架构的支持,比如armabi-v7a,其他架构不提供lib包

网络优化

优化方法

  • 请求合并
  • 减小请求数据大小:如gzip压缩
  • 根据网络质量下载对应图片

网络安全

  • 加密算法:对称加密,非对称加密
  • 电子签名
  • 消息摘要算法:MD5,SHA

电量优化

  • 使用TraceView定位CPU占用异常的问题
  • 网络传输优化:无网络避免网络请求,研所数据
  • 后台监听取消

自定义View优化

  • onDraw方法里避免做内存分配,容易导致gc,从而导致重绘,应该在初始化做
  • 动画执行时也要避免做内存分配
  • 减少onDraw调用次数,减少调用invaildate()的次数。如果可能的话,尽量调用含有4个参数的invalidate()方法而不是没有参数的invalidate()。没有参数的invalidate会强制重绘整个view
  • 保持View层级扁平化,requestLayout(),会使得Android UI系统去遍历整个View的层级来计算出每一个view的大小。如果找到有冲突的值,它会需要重新计算好几次。
  • 复杂的UI可以考虑采用自定义ViewGroup执行layout操作,只测量一部分,避免遍历整个View层级结构