性能优化
一个高质量的应用应该以4个方向为目标:快、稳、省、小。
快:使用时避免出现卡顿,响应速度快,减少用户等待的时间。
稳:减低 crash 率和 ANR 率,不要在用户使用过程中崩溃和无响应。
省:节省流量和耗电,减少用户使用成本,避免使用时导致手机发烫。
小:安装包小可以降低用户的安装成本。
卡顿优化
Android 应用启动慢,使用时经常卡顿,是非常影响用户体验的,应该尽量避免出现。卡顿的场景有很多,按场景可以分为4类:UI 绘制、应用启动、页面跳转、事件响应。
这4种卡顿场景的根本原因可以分为两大类:
1.界面绘制。主要原因是绘制的层级深、页面复杂、刷新不合理,由于这些原因导致卡顿的场景更多出现在 UI 和启动后的初始界面以及跳转到页面的绘制上。
2.数据处理。导致这种卡顿场景的原因是数据处理量太大,一般分为三种情况,一是数据在处理 UI 线程,二是数据处理占用 CPU 高,导致主线程拿不到时间片,三是内存增加导致GC 频繁,从而引起卡顿。
影响绘制的根本原因有以下两个方面:
1.绘制任务太重,绘制一帧内容耗时太长。
2.主线程太忙,根据系统传递过来的VSYNC 信号来时还没准备好数据导致丢帧。
性能问题并不容易复现,也不好定位,但是真的碰到问题还是需要去解决的,那么分析问题和确认问题是否解决,就需要借助相应的的调试工具,比如查看 Layout 层次的 Hierarchy View、Android 系统上带的 GPU Profile 工具和静态代码检查工具 Lint 等,这些工具对性能优化起到非常重要的作用,所以要熟悉,知道在什么场景用什么工具来分析。
优化建议:
1.布局优化:
减少层级。合理使用 RelativeLayout 和 LinerLayout,合理使用Merge。
提高显示速度。使用 ViewStub,它是一个看不见的、不占布局位置、占用资源非常小的视图对象。
布局复用。可以通过 标签来提高复用。
尽可能少用wrap_content。wrap_content 会增加布局 measure 时计算成本,在已知宽高为固定值时,不用wrap_content 。
删除控件中无用的属性
2,避免过度绘制
过度绘制是指在屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次重叠的 UI结构中,如果不可见的 UI 也在做绘制的操作,就会导致某些像素区域被绘制了多次,从而浪费了多余的 CPU 以及 GPU 资源。
如何避免过度绘制呢:
布局上的优化。移除 XML 中非必须的背景,移除 Window 默认的背景、按需显示占位背景图片
自定义View优化。使用canvas.clipRect()来帮助系统识别那些可见的区域,只有在这个区域内才会被绘制。
3,启动优化
通过对启动速度的监控,发现影响启动速度的问题所在,优化启动逻辑,提高应用的启动速度。启动主要完成三件事:UI 布局、绘制和数据准备。因此启动速度优化就是需要优化这三个过程:
UI 布局。应用一般都有闪屏页,优化闪屏页的 UI 布局,可以通过 Profile GPU Rendering 检测丢帧情况。
启动加载逻辑优化。可以采用分布加载、异步加载、延期加载策略来提高应用启动速度。
数据准备。数据初始化分析,加载数据可以考虑用线程初始化等策略。
4,合理的刷新机制
在应用开发过程中,因为数据的变化,需要刷新页面来展示新的数据,但频繁刷新会增加资源开销,并且可能导致卡顿发生,因此,需要一个合理的刷新机制来提高整体的 UI 流畅度。合理的刷新需要注意以下几点:
尽量减少刷新次数。
尽量避免后台有高的 CPU 线程运行。
缩小刷新区域。
内存优化
在 Android 系统中有个垃圾内存回收机制,在虚拟机层自动分配和释放内存,因此不需要在代码中分配和释放某一块内存,从应用层面上不容易出现内存泄漏和内存溢出等问题,但是需要内存管理。
Android 系统在内存管理上有一个 Generational Heap Memory 模型,内存回收的大部分压力不需要应用层关心, Generational Heap Memory 有自己一套管理机制,当内存达到一个阈值时,系统会根据不同的规则自动释放系统认为可以释放的内存,也正是因为 Android 程序把内存控制的权力交给了 Generational Heap
Memory,一旦出现内存泄漏和溢出方面的问题,排查错误将会成为一项异常艰难的工作。
除此之外,部分 Android 应用开发人员在开发过程中并没有特别关注内存的合理使用,也没有在内存方面做太多的优化,当应用程序同时运行越来越多的任务,加上越来越复杂的业务需求时,完全依赖 Android 的内存管理机制就会导致一系列性能问题逐渐呈现,对应用的稳定性和性能带来不可忽视的影响,因此,解决内存问题和合理优化内存是非常有必要的。
常见内存泄漏场景
handler耗时引发的内存泄漏
当activity当中存在handler接收耗时的消息时,比如我们一般在网络请求切换线程时,经常使用到handler,假设消息还没有发送完成,但是页面已经被关闭,也就说activity已经执行了ondestroy方法。当gc回收时,会出现改activity不能被回收的情况,导致内存泄漏。
解决办法:当activity销毁的时候,调用handler的removeCallbacksAndMessages方法,移除消息任务,然后将handler对象及线程置空。
内部类引发的内存泄漏(当然handler或子线程一般也作为内部类使用)
因为java当中,内部类默认持有外部类的引用,当外部类销毁后,一旦gc回收该实例,发现内部类持有他的引用而导致不能回收该实例,出现内存泄漏的情况。
解决方法:将内部类改为静态内部类,因为静态内部类生命周期和应用一样长,所以当退出程序的时候会一同回收该实例,并不会影响外部类的回收。
单例导致的内存泄漏
因为在使用单例的时候,经常会传入一个本类的上下文对象,而单例是静态的,生命周期和application一样长,当activity销毁的时候,该单例持有activity的引用导致其不能被回收,出现内存泄漏。
解决方法:在使用上下文的时候,传全局上下文。
资源未关闭
Cursor,stream,database,Butterknife,broadcastreciver,bindservice,eventBus
比如这些东西在使用完成后,需要进行close或者Unbind处理,以节省内存
Bitmap对象不在使用时调用recycle()释放内存
Timer计时器、动画,因为这些涉及耗时问题,如果activity销毁,而该任务并未执行完成,会导致内存泄漏,所以一般在activity中如果使用到这些耗时任务,需要在activity销毁时,做对应处理,比如调用timer的cancel方法,或者动画的cancel方法并将对象置空
一些监听器的内存泄漏,比如说我们给edittext设置输入文字监听时,当监听到文字发生变化,我们通过获取变化后的文字执行了耗时任务(比如获取到edittext里的内容上传服务器),当耗时任务未执行完成activity销毁了,会引发内存泄漏,所以在onDestory时,取消注册,比如说editText调用removeTextChangedListener方法
Rxjava的内存泄漏:因为rxjava采用的是观察者模式,当请求到数据后会根据订阅关系将数据发送个订阅者,而如果这时订阅者已经销毁,就会出现引用该对象导致其不能被回收的情况,出现内存泄漏,rxjava2发布的时候也发现了这个问题,所以在回调当中,新增加了onSubcribe回调,同时返回了一个disposable对象,可以通过判断disposable里的isDisposed来确定当前的订阅关系,如果订阅关系中的订阅者已经不存在且当前订阅关系存在,解除订阅关系,并终止数据的发送。
webView引发的内存泄漏:因为webview在使用的时候一般持有activity的引用,我们一般在activity的onDestroy方法中调用mWebView.destroy();来释放webview。如果在onDetachedFromWindow之前调用了destroy那就肯定会无法正常反注册了,也就会导致内存泄漏。所以在销毁webview前一定要先在onDetachedFromWindow中将webview从它的父view中移除,再调用destroy方法中调用webview的destroy,我开发的时候在5.1以上的手机上发现这种问题比较多,因为现在5.1以下适配的比较少了,基本没咋注意。
线程导致的内存泄漏:一般使用子线程都会创建一个内部类对象,而创建线程一般执行耗时任务,所以这个内部类默认持有外部类的引用,如果耗时任务在activity销毁的时候未执行完成,会因为持有外部类引用导致外部类不能被回收。
优化内存空间
没有内存泄漏,并不意味着内存就不需要优化,在移动设备上,由于物理设备的存储空间有限,Android 系统对每个应用进程也都分配了有限的堆内存,因此使用最小内存对象或者资源可以减小内存开销,同时让GC 能更高效地回收不再需要使用的对象,让应用堆内存保持充足的可用内存,使应用更稳定高效地运行。常见做法如下:
1.对象引用。强引用、软引用、弱引用、虚引用四种引用类型,根据业务需求合理使用不同,选择不同的引用类型。
2.减少不必要的内存开销。比如有效利用系统自带的资源、视图复用、对象池、Bitmap对象的复用。
3.使用最优的数据类型。比如针对数据类容器结构,可以使用ArrayMap数据结构,避免使用枚举类型,使用缓存Lrucache等等。
4.图片内存优化。可以设置位图规格,根据采样因子做压缩,用一些图片缓存方式对图片进行管理等等。
稳定性优化
Android 应用的稳定性定义很宽泛,影响稳定性的原因很多,比如内存使用不合理、代码异常场景考虑不周全、代码逻辑不合理等,都会对应用的稳定性造成影响。其中最常见的两个场景是:Crash和 ANR,这两个错误将会使得程序无法使用,比较常用的解决方式如下:
提高代码质量。比如开发期间的代码审核,看些代码设计逻辑,业务合理性等。
代码静态扫描工具。常见工具有Android,Lint、Findbugs、Checkstyle、PMD等等。
Crash监控。把一些崩溃的信息,异常信息及时地记录下来,以便后续分析解决。
Crash上传机制。在Crash后,尽量先保存日志到本地,然后等下一次网络正常时再上传日志信息。
耗电优化
在移动设备中,电池的重要性不言而喻,没有电什么都干不成。对于操作系统和设备开发商来说,耗电优化一致没有停止,去追求更长的待机时间,而对于一款应用来说,并不是可以忽略电量使用问题,特别是那些被归为“电池杀手”的应用,最终的结果是被卸载。因此,应用开发者在实现需求的同时,需要尽量减少电量的消耗。
在Android5.0 以前,在应用中测试电量消耗比较麻烦,也不准确,5.0之后专门引入了一个获取设备上电量消耗信息的API:Battery Historian。Battery
Historian 是一款由 Google 提供的 Android 系统电量分析工具,和Systrace 一样,是一款图形化数据分析工具,直观地展示出手机的电量消耗过程,通过输入电量分析文件,显示消耗情况,最后提供一些可供参考电量优化的方法。
除此之外,还有一些常用方案可提供:
计算优化,避开浮点运算等。
避免 WaleLock使用不当。
使用 Job Scheduler。
安装包大小优化
应用安装包大小对应用使用没有影响,但应用的安装包越大,用户下载的门槛越高,特别是在移动网络情况下,用户在下载应用时,对安装包大小的要求更高,因此,减小安装包大小可以让更多用户愿意下载和体验产品。
减少安装包大小的常用方案
代码混淆。使用proGuard 代码混淆器工具,它包括压缩、优化、混淆等功能。
资源优化。比如使用 Android Lint 删除冗余资源,资源文件最少化等。
图片优化。比如利用 AAPT 工具对 PNG 格式的图片做压缩处理,降低图片色彩位数等。
避免重复功能的库,使用 WebP图片格式等。
插件化。比如功能模块放在服务器上,按需下载,可以减少安装包大小。