Android App性能相关

作为以移动设备为主的操作系统,安卓硬件配置是有一定的限制的,虽然配置现在越来越高级,但仍然无法与PC相比,在CPU和内存上使用不合理或者耗费资源多时,就会碰到内存不足导致的稳定性问题、CPU 消耗太多导致的卡顿问题等。

1. 性能问题

我们可以把用户体验的性能问题主要总结为4个类别:

  • 流畅

  • 稳定

  • 省电、省流量

  • 安装包小

 性能相关及对应的问题归纳为如下表格:

流畅的操作体验卡顿
稳定内存泄露、崩溃
省电、省流量代码质量和逻辑
安装包安装包过大

因此打造一个高质量的应用应该以4个方向为目标:快、稳、省、小。

  • 快:使用时避免出现卡顿,响应速度快,减少用户等待的时间,满足用户期望。

  • 稳:减低 crash 率和 ANR 率,不要在用户使用过程中崩溃和无响应。

  • 省:节省流量和耗电,减少用户使用成本,避免使用时导致手机发烫。

  • 小:安装包小可以降低用户的安装成本。

 2. 卡顿优化

卡顿问题大体可以分为UI 绘制、应用启动、页面跳转、事件响应4类,如下:

UI绘制、刷新
启动安装启动、冷启动、热启动
跳转页面间切换、前后台切换
响应按键、系统事件、滑动

这4种卡顿场景的根本原因可以分为两大类:

  • 界面绘制。主要原因是绘制的层级深、页面复杂、刷新不合理,由于这些原因导致卡顿的场景更多出现在UI和启动后的初始界面以及跳转到页面的绘制上。

  • 数据处理。导致这种卡顿场景的原因是数据处理量太大,一般分为三种情况,一是数据在处理UI线程,二是数据处理占用CPU高,导致主线程拿不到时间片,三是内存增加导致GC频繁,从而引起卡顿。

2.1   Android系统显示原理

Android显示过程可以简单概括为:应用程序把经过测量、布局、绘制后的Surface缓存数据,通过SurfaceFlinger把数据渲染到显示屏幕上, 通过Android的刷新机制来刷新数据。也就是说应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要绘制的数据传递到系统层服务,系统层服务通过刷新机制把数据更新到屏幕上。

Android的每个View绘制中有三个核心步骤:Measure、Layout、Draw。具体实现是从 ViewRootImp类的performTraversals() 方法开始执行,Measure和Layout都是通过递归来获取View的大小和位置,并且以深度作为优先级,可以看出层级越深、元素越多、耗时也就越长。

而真正的要把数据显示到屏幕上是通过系统级进程中的SurfaceFlinger服务来实现的,既然SurfaceFlinger是系统级进程,则应用在两个不同的进程,那么肯定是需要一个跨进程的通信机制来实现数据传递,在Android显示系统中,使用了Android的匿名共享内存:SharedClient,每一个应用和SurfaceFlinger之间都会创建一个SharedClient ,最多可以创建31个 SharedBufferStack,每个Surface都对应一个SharedBufferStack,一个Android应用程序可能包含多个窗口Surface,也就是说SharedClient包含的是SharedBufferStack的集合。

总结起来,显示流程分为三个模块:应用层View绘制,数据通信传递到缓存区,SurfaceFlinger把缓存区数据渲染到屏幕,使用Android的匿名共享内存SharedClient缓存需要显示的数据来达到目的。

FPS:每秒传递的帧数,理想状态下达到60fps就不会感觉卡,意味着每个绘制时长应控制在16.6ms以内,Android系统每隔16.6ms发出VSYNC信号,触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需的60FPS。如果某个操作花费的时间是24ms ,系统在得到VSYNC信号时就无法正常进行正常渲染,这样就发生了丢帧现象,用户在两个渲染周期看到的将是同一个画面,会有卡顿感觉。

2.2 卡顿根本原因

根据上面显示原理可以总结出影响绘制的根本原因有两个:

  • 绘制任务太重,绘制一帧内容耗时太长。

  • 主线程太忙,根据系统传递过来的VSYNC信号来时还没准备好数据导致丢帧。

绘制耗时太长,可以借助一些工具帮助定位问题。主线程太忙则需要注意了,主线程关键职责是处理用户交互,在屏幕上绘制像素,并进行加载显示相关的数据,所以特别需要避免任何主线程的事情,这样应用程序才能保持对用户操作的即时响应。总结起来,主线程主要做以下几个方面工作:

  • UI生命周期控制
  • 系统事件处理
  • 消息处理
  • 界面布局
  • 界面绘制
  • 界面刷新

因此,应该尽量避免将其他处理放在主线程中,如:复杂的数据计算、较大数据库访问、网络请求等。

2.3 性能分析工具

性能问题不太好复现和定位,但出现了我们还是得想办法去解决,常用的工具有:查看Layout层次的Hierarchy View,Android系统上带的GPU Profile工具,静态代码检查工具Lint等。

(1) Profile GPU Rendering 在手机开发者模式下,有一个卡顿检测工具叫做:Profile GPU Rendering,它是一个图形监测工具,能实时反应当前绘制的耗时,横轴表示时间,纵轴表示每一帧的耗时,随着时间推移,从左到右的刷新呈现,其提供一个标准的耗时,如果高于标准耗时,就表示当前这一帧丢失。

(2) TraceView Android SDK自带的工具,用来分析函数调用过程,可以对Android的应用程序以及Framework层的代码进行性能分析。它是一个图形化的工具,最终会产生一个图表,用于对性能分析进行说明,可以分析到每一个方法的执行时间,其中可以统计出该方法调用次数和递归次数,实际时长等参数维度,使用非常直观,分析性能非常方便。

(3) Systrace UI 性能分析,Android 4.1及以上版本提供的性能数据采样和分析工具,它是通过系统的角度来返回一些信息。它可以帮助开发者收集Android关键子系统,如Surfaceflinger、WindowManagerService等Framework部分关键模块、服务、View系统等运行信息,从而帮助开发者更直观地分析系统瓶颈,改进性能。Systrace的功能包括跟踪系统的I/O操作、内核工作队列、CPU负载等,在UI显示性能分析上提供很好的数据,特别是在动画播放不流畅、渲染卡等问题上。

2.4 优化建议

(1)布局优化,页面的显示测量和绘制过程都是通过递归来完成的,如果层级太深,每增加一层则会增加更多的页面显示时间

  • 减少层级。合理使用RelativeLayout和LinerLayout,合理使用Merge。

  • 提高显示速度。使用ViewStub,它是一个看不见的、不占布局位置、占用资源非常小的视图对象。

  • 布局复用。可以通过标签来提高复用。

  • 尽可能少用wrap_content。wrap_content 会增加布局measure时计算成本,在已知宽高为固定值时,不用wrap_content 。

  • 删除控件中无用的属性。

(2)避免过度绘制 过度绘制是指在屏幕上的某个像素在同一帧的时间内被绘制了多次

  • 布局上的优化。移除XML中非必须的背景,移除Window默认的背景、按需显示占位背景图片

  • 自定义View优化。使用 canvas.clipRect()来帮助系统识别那些可见的区域,只有在这个区域内才会被绘制。

(3)启动优化

  • UI布局。应用一般都有闪屏页,优化闪屏页的UI布局,可以通过Profile GPU Rendering检测丢帧情况。

  • 启动加载逻辑优化。可以采用分布加载、异步加载、延期加载策略来提高应用启动速度。

  • 数据准备。数据初始化分析,加载数据可以考虑用线程初始化等策略。

3. 内存优化

3.1 因其他地方已写过Android内存机制,这里写点内存相关的工具。

(1)Heap Viewer

Heap Viewer的主要功能是查看不同数据类型在内存中的使用情况,可以看到当前进程中的Heap Size的情况,分别有哪些类型的数据,以及各种类型数据占比情况。通过分析这些数据来找到大的内存对象,再进一步分析这些大对象,进而通过优化减少内存开销,也可以通过数据的变化发现内存泄漏。

(2)Allocation Tracker

Memory Monitor和Heap Viewer都可以很直观且实时地监控内存使用情况,还能发现内存问题,但发现内存问题后不能再进一步找到原因,或者发现一块异常内存,但不能区别是否正常,同时在发现问题后,也不能定位到具体的类和方法。这时就需要使用另一个内存分析工具Allocation Tracker,进行更详细的分析,Allocation Tracker可以分配跟踪记录应用程序的内存分配,并列出了它们的调用堆栈,可以查看所有对象内存分配的周期。

(3)Memory Analyzer Tool(MAT,推荐使用)

MAT是一个快速,功能丰富的Java Heap分析工具,通过分析Java进程的内存快照HPROF分析,从众多的对象中分析,快速计算出在内存中对象占用的大小,查看哪些对象不能被垃圾收集器回收,并可以通过视图直观地查看可能造成这种结果的对象。

3.2 常见内存泄漏场景

  • 资源性对象未关闭。比如Cursor、File文件等,往往都用了一些缓冲,在不使用时,应该及时关闭它们。

  • 注册对象未注销。比如事件注册后未注销,会导致观察者列表中维持着对象的引用。

  • 类的静态变量持有大数据对象。

  • 非静态内部类的静态实例。

  • Handler临时性内存泄漏。如果Handler是非静态的,容易导致Activity或Service不会被回收。

  • 容器中的对象没清理造成的内存泄漏。

  • WebView。WebView存在着内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。

3.3 优化内存空间

由于移动设备的存储空间有限,Android 系统对每个应用进程也都分配了有限的堆内存,因此使用最小内存对象或者资源可以减小内存开销,同时让GC 能更高效地回收不再需要使用的对象,让应用堆内存保持充足的可用内存,使应用更稳定高效地运行。常见做法如下:

  • 对象引用。强引用、软引用、弱引用、虚引用四种引用类型,根据业务需求合理使用不同,选择不同的引用类型。

  • 减少不必要的内存开销。注意自动装箱,增加内存复用,比如有效利用系统自带的资源、视图复用、对象池、Bitmap对象的复用。

  • 使用最优的数据类型。比如针对数据类容器结构,可以使用ArrayMap数据结构,避免使用枚举类型,使用缓存Lrucache等等。

  • 图片内存优化。可以设置位图规格,根据采样因子做压缩,用一些图片缓存方式对图片进行管理等等。

4. 稳定性优化

Android应用的稳定性定义很宽泛,影响稳定性的原因很多,比如内存使用不合理、代码异常场景考虑不周全、代码逻辑不合理等,都会对应用的稳定性造成影响。其中最常见的两个场景是:Crash和ANR,这两个错误将会使得程序无法使用,比较常用的解决方式如下:

  • 提高代码质量。比如开发期间的代码审核,看些代码设计逻辑,业务合理性等。

  • 代码静态扫描工具。常见工具有Android Lint、Findbugs、Checkstyle、PMD等等。

  • Crash监控。把一些崩溃的信息,异常信息及时地记录下来,以便后续分析解决。

  • Crash上传机制。在Crash后,尽量先保存日志到本地,然后等下一次网络正常时再上传日志信息。

5.耗电优化

Android 5.0之后专门引入了一个获取设备上电量消耗信息的API:Battery Historian。Battery Historian是一款由Google提供的Android系统电量分析工具,和Systrace一样,是一款图形化数据分析工具,直观地展示出手机的电量消耗过程,通过输入电量分析文件,显示消耗情况,最后提供一些可供参考电量优化的方法。除此之外,还有一些常用方案可提供:

  • 计算优化,避开浮点运算等。

  • 避免WaleLock使用不当。

  • 使用Job Scheduler。

6. 安装包大小优化

减少安装包大小的常用方案:

  • 代码混淆。使用ProGuard代码混淆器工具,它包括压缩、优化、混淆等功能。

  • 资源优化。比如使用Android Lint删除冗余资源,资源文件最少化等。

  • 图片优化。比如利用AAPT工具对PNG格式的图片做压缩处理,降低图片色彩位数等。

  • 避免重复功能的库,使用WebP图片格式等。

  • 插件化。比如功能模块放在服务器上,按需下载,可以减少安装包大小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值