性能优化的目的
在不断地迭代开发过程中,我们的应用功能会越来越复杂,代码量也会不断增加。再加上偶尔的重构、人员的变更等等原因,我们曾经那个如丝顺滑的项目也会渐渐变得卡顿。
那么这个时候,就不得不提性能优化这个话题了。正好这段时间有空,就整理了一下常见的性能优化的几个方面以及各个方面的注意事项。一来是给自己脑中的知识做个梳理,加深下记忆,二来也能给一些萌新提供点思路。
内存优化
内存优化,可以说是性能优化中最重要的一部分内容了。如果应用占用内存过大,轻则应用卡顿降低用户体验,重则内存溢出(OOM),程序崩溃。所以内存优化很重要,接下来我们从一下几个方面来进行讲解
android的内存管理
垃圾回收
android的垃圾回收和java 的垃圾回收一直,一旦确定程序不再使用内存,便将其释放,而无需人为干预。
垃圾回收有两个步骤:在程序中产兆将来无法访问的数据对象;将那些对象的资源释放并回收。
内存管理
android作为一个多任务的操作系统,为了维持系统功能正常,会对每个应用程序的内存大小做出硬性限制。具体的数值和机型以及内存有关。如果应用内存打到限制后还要申请内存,就会引发OutOfMemoryError。
如果在开发中有获取剩余内存的需求,可以调用getMemoryClass()方法。用来在内存快满时及时回收一些不必要的东西。
android内存监测
LeakCanary 详情
一个可以检测程序在运行过程中发生的内存泄漏问题,通过简单的代码配置,可以方便的找出我们应用中的内存问题
Memory Monitor
android studio自带的实时内存分析工具,我们可以通过实时的内存、CPU等的波动来分析问题,如果某个页面反复进入后内存持续增长,我们就要注意了。
[图片上传失败...(image-211493-1570784432908)]
Heap Viewer 详情
也是android studio中可以直接使用的内存分析工具,需要android系统在5.0以上并保持开发者选项可用。具体使用情况请点击详情。
Allocation Tracker 详情
可以追踪内存分配信息,按顺序排列,这样我们就可以清晰看出某一个操作的内存是如何一步步分配出来的,从而进一步找出发生问题的代码。
更多性能测试工具看这里
内存优化方案
界面不可见时及时回收部分内存
当用户打开了另外一个程序,我们的程序界面已经不再可见的时候,我们应当将所有和界面相关的资源进行释放。在这种场景下释放资源可以让系统缓存后台进程的能力显著增加,因此也会让用户体验变得更好。
检测界面是否可见我们可以重写如下方法:
@OverridepublicvoidonTrimMemory(intlevel){super.onTrimMemory(level);switch(level){caseTRIM_MEMORY_UI_HIDDEN:// 进行资源释放操作 break;}}
onTrimMemory方法只有当一个Activity完全不可见时候才会调用,这和onStop()方法还是有很大区别的,因为onStop()方法只是当一个Activity完全不可见的时候就会调用,比如说用户打开了我们程序中的另一个Activity。
因此,我们可以在onStop()方法中去释放一些Activity相关的资源,比如说取消网络连接或者注销广播接收器等,但是像UI相关的资源应该一直要等到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)这个回调之后才去释放,这样可以保证如果用户只是从我们程序的一个Activity回到了另外一个Activity,界面相关的资源都不需要重新加载,从而提升响应速度。
使用Handler时尽量弱引用
我们经常会在handler中进行一些延时任务,这些延时任务会导致Activity被引用,从而发生内存泄漏,为了避免这类事情发生,我们可以对handler使用弱引用。
publicclassMainActivityextendsAppCompatActivity{publicstaticfinalStringTAG="carson:";privateHandlershowhandler;// 主线程创建时便自动创建Looper & 对应的MessageQueue// 之后执行Loop()进入消息循环@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//1. 实例化自定义的Handler类对象->>分析1//注:// a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue;// b. 定义时需传入持有的Activity实例(弱引用)showhandler=newFHandler(this);// 2. 启动子线程1newThread(){@Overridepublicvoidrun(){try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}// a. 定义要发送的消息Messagemsg=Message.obtain();msg.what=1;// 消息标识msg.obj="AA";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();// 3. 启动子线程2newThread(){@Overridepublicvoidrun(){try{Thread.sleep(5000);}catch(InterruptedExceptione){e.printStackTrace();}// a. 定义要发送的消息Messagemsg=Message.obtain();msg.what=2;// 消息标识msg.obj="BB";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();}// 分析1:自定义Handler子类// 设置为:静态内部类privatestaticclassFHandlerextendsHandler{// 定义 弱引用实例privateWeakReference
这样一来,handler中发送延时消息便不会发生内存泄漏了。
当然避免handler内存泄漏还可以采取当Activity结束使用时候,清空消息队列的操作,如下:
@OverrideprotectedvoidonDestroy(){super.onDestroy();mHandler.removeCallbacksAndMessages(null);// 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期}
加载图片的注意事项 详情
不在小控件上显示大图
列表类图片,仅加载当前页面可见的图片
有显示原图需求时,要判断可用内存,内存不够时要压缩图片
避免内存抖动 详情
尽量避免在循环体内创建对象,应该把对象创建移到循环体外。
注意自定义View的onDraw()方法会被频繁调用,所以在这里面不应该频繁的创建对象。
当需要大量使用Bitmap的时候,试着把它们缓存在数组中实现复用。
对于能够复用的对象,同理可以使用对象池将它们缓存起来。
布局优化 详情
避免过度绘制
什么是过度绘制
过度绘制就是在同一个位置,有多次的颜色绘制过程。常见的情况就是在同一个位置堆叠了许多控件,这会造成一些性能问题,严重的情况会造成卡顿。
如何检测过度绘制
开发者选项->调试GPU过度绘制->显示过度绘制区域
过度绘制优化
移除控件中不需要的背景
将layout层级扁平化
减少透明度的使用
自定义View中减少重复绘制区域
布局优化技巧
简单布局优先使用LinearLayout或FragmentLayout
复杂布局优先使用constrainLayout
使用include标签提高复用性
使用ViewStub标签延迟加载
onDraw()中不要创建新的局部变量以及不要做耗时操作
网络优化 详情
为什么要网络优化
过多的无用网络请求,会消耗用户的网络流量。流量消耗过大会流失用户
频繁的网络操作会导致设备用电量提升
网络弹框的频繁出现会降低用户体验
应用更新、大文件下载等场景,更优的网络传输速度可提升用户体验
网络优化的方式
使用GZip压缩,数据压缩后可以减少流量的消耗,减少传输的时间
使用IP直连,DNS域名解析是一个较为耗时操作,可以直连IP减少解析时间
图片加载
使用WebP格式可以大幅节省流量
图片按需加载,列表中图片只加载缩略图
大图上传时,采用分片传输,失败只传对应片段
用户体验影响不大时,手机原图压缩后再传输
减少接口数量,同一个页面尽量只使用一个接口,数据可以放到后台去拼凑
利用缓存,对数据设定有效期,有效期内数据不重复请求
检测网络状态,不同网络转态执行不同策略,例如移动网络不加载图片,2G网络只加载标题等。
文件上传、下载采用断点续传,不浪费已传输完成部分流量
利用抓包工具模拟多种情况,在实践中调整不断优化用户体验
启动优化 详情
闪屏页优化
主流APP是在应用启动时候会加载一个默认的主题,用来去掉应用启动时候的黑/白屏的情况
应用主题到Application或Activity
其实就是个障眼法而已,提前让你看到了假的界面。也算是一种不错的方法,但是治标不治本。
第三方库懒加载
在开发中会用到很多的三方库,如友盟、百度、bugly、图片库、网络库等。
这些都是必须的,不能去掉,那么办法就是异步加载了,可以有以下几种思路
像友盟,bugly这样的业务非必要的可以的异步加载。
比如地图,推送等,非第一时间需要的可以在主线程做延时启动。当程序已经启动起来之后,在进行初始化。
对于图片,网络请求框架必须在主线程里初始化了。
我们一般会有闪屏页面,也可以把延时启动的地图、推送的启动放到这个页面
按照以上方式处理后,还可以进一步降低应用启动时间。
WebView启动优化
WebView第一次创建比较耗时,可以预先创建WebView,提前将其内核初始化。
使用WebView缓存池,用到WebView的地方都从缓存池取,缓存池中没有缓存再创建,注意内存泄漏问题。
本地预置html和css,WebView创建的时候先预加载本地html,之后通过js脚本填充内容部分。
数据项预加载
主页数据变化不大时候,可以再第一次启动后,缓存主页数据到本地,下次启动先读取本地数据,页面完全显示后再去请求新数据进行增量更新。
安装包体积优化
体积优化的必要性
安装包体积是用户搜索应用后能第一眼看到的数据,虽然现在的应用体积越来越大,但小体积的App依旧是很多存储空间紧张用户的痛点。所以减少安装包体积是性能优化方面必不可少的一步。
减少应用体积的N种办法
使用lint工具删除无用的资源
简单的切图尽量替换为shape类型的xml文件
形状一致的图片只使用一个切图,比如方向不同的箭头、图像相同着色不同的切图等
对图片进行压缩,优先使用WebP格式图像
使用矢量图(.9)图来实现大小可变的背景图
代码混淆,使用proGuard代码混淆器工具,它包括压缩、优化、混淆等功能。
插件化,不需要的部分可以存在服务器,当用到时候动态下载。
电量优化
电量优化我放到最后说,是因为这个优先级比较低,因为一般APP在使用过程中,很难造成电量的明显下降,除非是游戏、相机或者视频类APP
电量优化相对来说比较简单,在开发中注意一下几点就可以了:
数据备份、日志报告等后台活动,可以放到电量充足或者正在充电时候执行
除视屏播放外,一直避免一直亮屏。
录音、GPS、相机等耗电操作,在执行完成后及时释放对应资源
后台不必要的service记得及时关闭
总结
Android的性能优化是一个长期且漫长的过程。一般企业在开发中都是先实现功能再去管性能,这样做会导致后期优化起来麻烦且耗时。建议有可能的话尽量保持一个好的开发习惯,在项目初期就注意性能方面的事情,不要引入无用的内容、保持代码整洁、及时删除已废弃模块等,这样开发的项目才回高效且易维护。