说到 Android 系统手机,大部分人的印象是用了一段时间就变得有点卡顿,有些程序在运行期间莫名其妙的出现崩溃,打开系统文件夹一看,发现多了不少文件,而后用手机管家 APP 不断地进行清理优化 ,才感受运行速度稍微提升了点,就算手机在各类性能跑分软件面前分数遥遥领先,仍是感受不管有多大的内存空间都远远不够用。相信每一个使用 Android 系统的用户都有过以上相似经历,确实,Android 系统在流畅性方面不如 IOS 系统,为什么呢,明明在看手机硬件配置上时,Android 设备都不会输于 IOS 设备,甚至都强于它,关键是在于软件上。形成这种现象的缘由是多方面的,简单罗列几点以下:程序员
今天想说的重点是Android APP 性能优化,也就是在开发应用程序时应该注意的点有哪些,如何更好地提升用户体验。一个好的应用,除了要有吸引人的功能和交互以外,在性能上也应该有高的要求,即时应用很是具备特点,在产品前期可能吸引了部分用户,可是用户体验很差的话,也会给产品带来很差的口碑。那么一个好的应用应该如何定义呢?主要有如下三方面:canvas
众所周知,Android 系统做为以移动设备为主的操做系统,硬件配置是有必定的限制的,虽然配置如今愈来愈高级,但仍然没法与 PC 相比,在 CPU 和内存上使用不合理或者耗费资源多时,就会碰到内存不足致使的稳定性问题、CPU 消耗太多致使的卡顿问题等。缓存
面对问题时,你们想到的都是联系用户,而后查看日志,但却不知有关性能类问题的反馈,缘由也很是难找,日志大多用处不大,为什么呢?由于性能问题大部分是非必现的问题,问题定位很难复现,而又没有关键的日志,固然就没法找到缘由了。这些问题很是影响用户体验和功能使用,因此了解一些性能优化的一些解决方案就显得很重要了,并在实际的项目中优化咱们的应用,进而提升用户体验。性能优化
能够把用户体验的性能问题主要总结为4个类别:服务器
性能问题的主要缘由是什么,缘由有相同的,也有不一样的,但归根到底,不外乎内存使用、代码效率、合适的策略逻辑、代码质量、安装包体积这一类问题,整理归类以下:网络
从图中能够看到,打造一个高质量的应用应该以4个方向为目标:快、稳、省、小。数据结构
快:使用时避免出现卡顿,响应速度快,减小用户等待的时间,知足用户指望。架构
稳:减低 crash 率和 ANR 率,不要在用户使用过程当中崩溃和无响应。框架
省:节省流量和耗电,减小用户使用成本,避免使用时致使手机发烫。异步
小:安装包小能够下降用户的安装成本。
要想达到这4个目标,具体实现是在右边框里的问题:卡顿、内存使用不合理、代码质量差、代码逻辑乱、安装包过大,这些问题也是在开发过程当中碰到最多的问题,在实现业务需求同时,也须要考虑到这点,多花时间去思考,如何避免功能完成后再来作优化,否则的话等功能实现后带来的维护成本会增长。
Android 应用启动慢,使用时常常卡顿,是很是影响用户体验的,应该尽可能避免出现。卡顿的场景有不少,按场景能够分为4类:UI 绘制、应用启动、页面跳转、事件响应,如图:
这4种卡顿场景的根本缘由能够分为两大类:
引发卡顿的缘由不少,但无论怎么样的缘由和场景,最终都是经过设备屏幕上显示来达到用户,归根到底就是显示有问题,因此,要解决卡顿,就要先了解 Android 系统的显示原理。
Android 显示过程能够简单归纳为:Android 应用程序把通过测量、布局、绘制后的 surface 缓存数据,经过 SurfaceFlinger 把数据渲染到显示屏幕上, 经过 Android 的刷新机制来刷新数据。也就是说应用层负责绘制,系统层负责渲染,经过进程间通讯把应用层须要绘制的数据传递到系统层服务,系统层服务经过刷新机制把数据更新到屏幕上。
咱们都知道在 Android 的每一个 View 绘制中有三个核心步骤:Measure、Layout、Draw。具体实现是从 ViewRootImp 类的performTraversals() 方法开始执行,Measure 和 Layout都是经过递归来获取 View 的大小和位置,而且以深度做为优先级,能够看出层级越深、元素越多、耗时也就越长。
真正把须要显示的数据渲染到屏幕上,是经过系统级进程中的 SurfaceFlinger 服务来实现的,那么这个SurfaceFlinger 服务主要作了哪些工做呢?以下:
既然是两个不一样的进程,那么确定是须要一个跨进程的通讯机制来实现数据传递,在 Android 显示系统中,使用了 Android 的匿名共享内存:SharedClient,每个应用和 SurfaceFlinger 之间都会建立一个SharedClient ,而后在每一个 SharedClient 中,最多能够建立 31 个 SharedBufferStack,每一个 Surface 都对应一个 SharedBufferStack,也就是一个 Window。
一个 SharedClient 对应一个Android 应用程序,而一个 Android 应用程序可能包含多个窗口,即 Surface 。也就是说 SharedClient 包含的是 SharedBufferStack的集合,其中在显示刷新机制中用到了双缓冲和三重缓冲技术。最后总结起来显示总体流程分为三个模块:应用层绘制到缓存区,SurfaceFlinger 把缓存区数据渲染到屏幕,因为是不一样的进程,因此使用 Android 的匿名共享内存 SharedClient 缓存须要显示的数据来达到目的。
除此以外,咱们还须要一个名词:FPS。FPS 表示每秒传递的帧数。在理想状况下,60 FPS 就感受不到卡,这意味着每一个绘制时长应该在16 ms 之内。可是 Android 系统颇有可能没法及时完成那些复杂的页面渲染操做。Android 系统每隔 16ms 发出 VSYNC 信号,触发对 UI 进行渲染,若是每次渲染都成功,这样就可以达到流畅的画面所需的 60FPS。若是某个操做花费的时间是 24ms ,系统在获得 VSYNC 信号时就没法正常进行正常渲染,这样就发生了丢帧现象。那么用户在 32ms 内看到的会是同一帧画面,这种现象在执行动画或滑动列表比较常见,还有多是你的 Layout 太过复杂,层叠太多的绘制单元,没法在 16ms 完成渲染,最终引发刷新不及时。
根据Android 系统显示原理能够看到,影响绘制的根本缘由有如下两个方面:
绘制耗时太长,有一些工具能够帮助咱们定位问题。主线程太忙则须要注意了,主线程关键职责是处理用户交互,在屏幕上绘制像素,并进行加载显示相关的数据,因此特别须要避免任何主线程的事情,这样应用程序才能保持对用户操做的即时响应。总结起来,主线程主要作如下几个方面工做:
除此以外,应该尽可能避免将其余处理放在主线程中,特别复杂的数据计算和网络请求等。
性能问题并不容易复现,也很差定位,可是真的碰到问题仍是须要去解决的,那么分析问题和确认问题是否解决,就须要借助相应的的调试工具,好比查看 Layout 层次的 Hierarchy View、Android 系统上带的 GPU Profile 工具和静态代码检查工具 Lint 等,这些工具对性能优化起到很是重要的做用,因此要熟悉,知道在什么场景用什么工具来分析。
1,Profile GPU Rendering
在手机开发者模式下,有一个卡顿检测工具叫作:Profile GPU Rendering,如图:
它的功能特色以下:
2,TraceView
TraceView 是 Android SDK 自带的工具,用来分析函数调用过程,能够对 Android 的应用程序以及 Framework 层的代码进行性能分析。它是一个图形化的工具,最终会产生一个图表,用于对性能分析进行说明,能够分析到每个方法的执行时间,其中能够统计出该方法调用次数和递归次数,实际时长等参数维度,使用很是直观,分析性能很是方便。
3,Systrace UI 性能分析
Systrace 是 Android 4.1及以上版本提供的性能数据采样和分析工具,它是经过系统的角度来返回一些信息。它能够帮助开发者收集 Android 关键子系统,如 surfaceflinger、WindowManagerService 等 Framework 部分关键模块、服务、View系统等运行信息,从而帮助开发者更直观地分析系统瓶颈,改进性能。Systrace 的功能包括跟踪系统的 I/O 操做、内核工做队列、CPU 负载等,在 UI 显示性能分析上提供很好的数据,特别是在动画播放不流畅、渲染卡等问题上。
1,布局优化
布局是否合理主要影响的是页面测量时间的多少,咱们知道一个页面的显示测量和绘制过程都是经过递归来完成的,多叉树遍历的时间与树的高度h有关,其时间复杂度 O(h),若是层级太深,每增长一层则会增长更多的页面显示时间,因此布局的合理性就显得很重要。
那布局优化有哪些方法呢,主要经过减小层级、减小测量和绘制时间、提升复用性三个方面入手。总结以下:
2,避免过分绘制
过分绘制是指在屏幕上的某个像素在同一帧的时间内被绘制了屡次。在多层次重叠的 UI 结构中,若是不可见的 UI 也在作绘制的操做,就会致使某些像素区域被绘制了屡次,从而浪费了多余的 CPU 以及 GPU 资源。
如何避免过分绘制呢,以下:
3,启动优化
经过对启动速度的监控,发现影响启动速度的问题所在,优化启动逻辑,提升应用的启动速度。启动主要完成三件事:UI 布局、绘制和数据准备。所以启动速度优化就是须要优化这三个过程:
4,合理的刷新机制
在应用开发过程当中,由于数据的变化,须要刷新页面来展现新的数据,但频繁刷新会增长资源开销,而且可能致使卡顿发生,所以,须要一个合理的刷新机制来提升总体的 UI 流畅度。合理的刷新须要注意如下几点:
5,其余
在实现动画效果时,须要根据不一样场景选择合适的动画框架来实现。有些状况下,能够用硬件加速方式来提供流畅度。
在 Android 系统中有个垃圾内存回收机制,在虚拟机层自动分配和释放内存,所以不须要在代码中分配和释放某一块内存,从应用层面上不容易出现内存泄漏和内存溢出等问题,可是须要内存管理。Android 系统在内存管理上有一个 Generational Heap Memory 模型,内存回收的大部分压力不须要应用层关心, Generational Heap Memory 有本身一套管理机制,当内存达到一个阈值时,系统会根据不一样的规则自动释放系统认为能够释放的内存,也正是由于 Android 程序把内存控制的权力交给了 Generational Heap Memory,一旦出现内存泄漏和溢出方面的问题,排查错误将会成为一项异常艰难的工做。除此以外,部分 Android 应用开发人员在开发过程当中并无特别关注内存的合理使用,也没有在内存方面作太多的优化,当应用程序同时运行愈来愈多的任务,加上愈来愈复杂的业务需求时,彻底依赖 Android 的内存管理机制就会致使一系列性能问题逐渐呈现,对应用的稳定性和性能带来不可忽视的影响,所以,解决内存问题和合理优化内存是很是有必要的。
Android 应用都是在 Android 的虚拟机上运行,应用 程序的内存分配与垃圾回收都是由虚拟机完成的。在 Android 系统,虚拟机有两种运行模式:Dalvik 和 ART。
1,Java对象生命周期
通常Java对象在虚拟机上有7个运行阶段:
建立阶段->应用阶段->不可见阶段->不可达阶段->收集阶段->终结阶段->对象空间从新分配阶段
2,内存分配
在 Android 系统中,内存分配其实是对堆的分配和释放。当一个 Android 程序启动,应用进程都是从一个叫作 Zygote 的进程衍生出来,系统启动 Zygote 进程后,为了启动一个新的应用程序进程,系统会衍生 Zygote 进程生成一个新的进程,而后在新的进程中加载并运行应用程序的代码。其中,大多数的 RAM pages 被用来分配给Framework 代码,同时促使 RAM 资源可以在应用全部进程之间共享。
可是为了整个系统的内存控制须要,Android 系统会为每个应用程序都设置一个硬性的 Dalvik Heap Size 最大限制阈值,整个阈值在不一样设备上会由于 RAM 大小不一样而有所差别。若是应用占用内存空间已经接近整个阈值时,再尝试分配内存的话,就很容易引发内存溢出的错误。
3,内存回收机制
咱们须要知道的是,在 Java 中内存被分为三个区域:Young Generation(年轻代)、Old Generation(年老代)、Permanent Generation(持久代)。最近分配的对象会存放在 Young Generation 区域。对象在某个时机触发 GC 回收垃圾,而没有回收的就根据不一样规则,有可能被移动到 Old Generation,最后累积必定时间在移动到 Permanent Generation 区域。系统会根据内存中不一样的内存数据类型分别执行不一样的 GC 操做。GC 经过肯定对象是否被活动对象引用来肯定是否收集对象,进而动态回收无任何引用的对象占据的内存空间。但须要注意的是频繁的 GC 会增长应用的卡顿状况,影响应用的流畅性,所以须要尽可能减小系统 GC 行为,以便提升应用的流畅度,减少卡顿发生的几率。
作内存优化前,须要了解当前应用的内存使用现状,经过现状去分析哪些数据类型有问题,各类类型的分布状况如何,以及在发现问题后如何发现是哪些具体对象致使的,这就须要相关工具来帮助咱们。
1,Memory Monitor
Memory Monitor 是一款使用很是简单的图形化工具,能够很好地监控系统或应用的内存使用状况,主要有如下功能:
2,Heap Viewer
Heap Viewer 的主要功能是查看不一样数据类型在内存中的使用状况,能够看到当前进程中的 Heap Size 的状况,分别有哪些类型的数据,以及各类类型数据占比状况。经过分析这些数据来找到大的内存对象,再进一步分析这些大对象,进而经过优化减小内存开销,也能够经过数据的变化发现内存泄漏。
3,Allocation Tracker
Memory Monitor 和 Heap Viewer 均可以很直观且实时地监控内存使用状况,还能发现内存问题,但发现内存问题后不能再进一步找到缘由,或者发现一块异常内存,但不能区别是否正常,同时在发现问题后,也不能定位到具体的类和方法。这时就须要使用另外一个内存分析工具 Allocation Tracker,进行更详细的分析, Allocation Tracker 能够分配跟踪记录应用程序的内存分配,并列出了它们的调用堆栈,能够查看全部对象内存分配的周期。
4,Memory Analyzer Tool(MAT)
MAT 是一个快速,功能丰富的 Java Heap 分析工具,经过分析 Java 进程的内存快照 HPROF 分析,从众多的对象中分析,快速计算出在内存中对象占用的大小,查看哪些对象不能被垃圾收集器回收,并能够经过视图直观地查看可能形成这种结果的对象。
若是在内存泄漏发生后再去找缘由并修复会增长开发的成本,最好在编写代码时就可以很好地考虑内存问题,写出更高质量的代码,这里列出一些常见的内存泄漏场景,在之后的开发过程当中须要避免这类问题。
除此以外,内存泄漏可监控,常见的就是用LeakCanary 第三方库,这是一个检测内存泄漏的开源库,使用很是简单,能够在发生内存泄漏时告警,而且生成 leak tarce 分析泄漏位置,同时能够提供 Dump 文件进行分析。
没有内存泄漏,并不意味着内存就不须要优化,在移动设备上,因为物理设备的存储空间有限,Android 系统对每一个应用进程也都分配了有限的堆内存,所以使用最小内存对象或者资源能够减少内存开销,同时让GC 能更高效地回收再也不须要使用的对象,让应用堆内存保持充足的可用内存,使应用更稳定高效地运行。常见作法以下:
Android 应用的稳定性定义很宽泛,影响稳定性的缘由不少,好比内存使用不合理、代码异常场景考虑不周全、代码逻辑不合理等,都会对应用的稳定性形成影响。其中最多见的两个场景是:Crash 和 ANR,这两个错误将会使得程序没法使用,比较经常使用的解决方式以下:
在移动设备中,电池的重要性不言而喻,没有电什么都干不成。对于操做系统和设备开发商来讲,耗电优化一致没有中止,去追求更长的待机时间,而对于一款应用来讲,并非能够忽略电量使用问题,特别是那些被归为“电池杀手”的应用,最终的结果是被卸载。所以,应用开发者在实现需求的同时,须要尽可能减小电量的消耗。
在 Android5.0 之前,在应用中测试电量消耗比较麻烦,也不许确,5.0 以后专门引入了一个获取设备上电量消耗信息的 API:Battery Historian。Battery Historian 是一款由 Google 提供的 Android 系统电量分析工具,和Systrace 同样,是一款图形化数据分析工具,直观地展现出手机的电量消耗过程,经过输入电量分析文件,显示消耗状况,最后提供一些可供参考电量优化的方法。
除此以外,还有一些经常使用方案可提供:
应用安装包大小对应用使用没有影响,但应用的安装包越大,用户下载的门槛越高,特别是在移动网络状况下,用户在下载应用时,对安装包大小的要求更高,所以,减少安装包大小可让更多用户愿意下载和体验产品。
经常使用应用安装包的构成,如图所示:
从图中咱们能够看到:
assets文件夹。存放一些配置文件、资源文件,assets不会自动生成对应的 ID,而是经过 AssetManager 类的接口获取。
res。res 是 resource 的缩写,这个目录存放资源文件,会自动生成对应的 ID 并映射到 .R 文件中,访问直接使用资源 ID。
META-INF。保存应用的签名信息,签名信息能够验证 APK 文件的完整性。
AndroidManifest.xml。这个文件用来描述 Android 应用的配置信息,一些组件的注册信息、可以使用权限等。
classes.dex。Dalvik 字节码程序,让 Dalvik 虚拟机可执行,通常状况下,Android 应用在打包时经过 Android SDK 中的 dx 工具将 Java 字节码转换为 Dalvik 字节码。
resources.arsc。记录着资源文件和资源 ID 之间的映射关系,用来根据资源 ID 寻找资源。
减小安装包大小的经常使用方案
性能优化不是更新一两个版本就能够解决的,是持续性的需求,持续集成迭代反馈。在实际的项目中,在项目刚开始的时候,因为人力和项目完成时间限制,性能优化的优先级比较低,等进入项目投入使用阶段,就须要把优先级提升,但在项目初期,在设计架构方案时,性能优化的点也须要提前考虑进去,这就体现出一个程序员的技术功底了。
何时开始有性能优化的需求,每每都是从发现问题开始,而后分析问题缘由及背景,进而寻找最优解决方案,最终解决问题,这也是平常工做中常会用到的处理方式。