概述
总结性的内容,总结了一些优化可以注意的点。
APP启动优化
APP内存优化
- 内存检测 (查找内存泄漏和对象数量分配)
- Android Profile (直观,新版本可以直接查看最近一次GC Route路线,对查找内存泄漏问题很好用)
- Memory Analyzer(MAT):查找引用和内存泄漏比较方便
- LeakCanary:直观分析GC路径,可以带到线上(但是得优化,因为dump的文件比较大)
- android Studio最新版本的内存检测可以实时查看内存对象变化情况,这个非常好!!!类似于以前Java监控对象变化的功能
- 图片优化
- 图片大小计算方式:图片: 宽 x 高 x 一个像素占用内存 x(如果放在drawable下的压缩比率)
- 获取图片大小使用 options.inJustDecodeBounds = true 可以不加在图片到内存
- 图片压缩:使用 BitmapFactory.Options() 的 inSampleSize 属性,建议采用和加载图片的 view 大小动态计算 inSampleSize 大小,防止图片失真。
- 采用不用的图片格式 比如:PNG是无损格式占用内存较大,JPEG不支持透明通道为有损格式,WEBP 支持有损和 无损压缩 支持透明通道 支持多帧动画 — 首选(替代PNG)。还有 GIF格式等。可以根据不用需要选择
- 对图片进行 Options 的 inPreferredConfig 压缩 ,比如 ARGB_8888 改为 ARGB_565,取消透过名通道,并且 ARGB_8888 每个像素占用4字节 ARGB_565占用2字节。其他还有:ARGB_4444 ,ALPHA_8 等。根据需要进行选择。
超大图片加载:在压缩之外,如果需求允许,可以加载部分图片,根据手势或者其他控制展示其他部分。可以通过 BitmapRegionDecoder 加载指定区域图片。- 加载 Bitmap 时,可以通过 InBitmap 优化,复用上一次创建Bitmap的内存空间,可以防止重新分配空间。限制:需要新bitmap需要的空间 < 复用空间。
- 优化图片加载:
1:可以通过自定义ImageVIew onDraw的时候动态加载 (侵入性强 但是兼容性好)
2:可以在 onDrawListener 回调里面处理 (侵入性强 但是兼容性好
3:ARTHook方式优化 (因为兼容性不好 所以 可以通过某些版本开启hook 某些版本不开启hook) (EPIC框架 最小力度为Java方法 支持Android4.0-9.0)
(感觉如果配合Kotlin 可以做成扩展函数 让开发人员选择调用)- Bitmap.recycle 新版API可以不用主动调用。因为图片位信息等存储位置改变了。
- Low Memory Kller
内存不足时回掉 onTrimMemory ;基本上此方法回掉以后就快oom了。可以自己处理是主动关闭重新打开,还是释放资源。
- 使用Android优化过的一些容器比如:android.util.Sparserray 等
- 优点:
避免了基本数据类型的装箱操作
不需要额外的结构体,单个元素的存储成本更低
数据量小的情况下,随机访问的效率更高- 缺点:
插入操作需要复制数组,增删效率降低
数据量巨大时,复制数组成本巨大,gc()成本也巨大
数据量巨大时,查询效率也会明显下降
每个容器都有各自的特点,开发时需要根据情况选择是增删快还是查找快等特性的容器。
- SharedPreference 优化(用不好严重影响性能甚至 ANR)
- 避免大量频繁读取SharedPreference,占用内存,IO操作耗时。
- 使用SharedPreference时,可以合理利用多次添加一次写入等。合理利用 apply与commit
- 可以使用 MMKV替代(mmap原理,少一次copy操作,效率大大提高)
- 初始化可以利用启动时空闲CPU时间片进行初始化操作。
APP布局优化
- 通过分析布局源码分析一些可以考虑优化性能的地方。
- 文件解析,IO加载XML,
- 反射创建view,
- 层级越深,循环测量次数增长越大。
- 可以通过 LayoutInflate.Factory 和 Factory2 做一些事情
Factory实际上是创建 View 的一个Hook。可以做一些自己的操作,比如更换全部TextView。
- Activity加载xml优化
- AsyncLayoutInflate 异步加载view ,如果 xml特别复杂比较耗时,可酌情采用
有一些使用限制,并且也会占用主线程looper
-
由于加载xml是IO操作,为了避免IO操作,可以使用java代码创建布局,但是Java代码难维护。可以采用 X2C 框架编译期编译 xml 文件
-
X2C 框架 (XML文件是 IO操作 还会通过反射创建View 效率低)原理:编译时注解:编辑期换成java代码创建布局 避免了反射和IO操作。 缺点:部分属性不支持 失去了系统兼容AppCompat。
-
GPU过度绘制 (根据开发者模式中的绘制检测,通过颜色判断是否过度绘制。白色最好 蓝色可以接受,红色应该修改…等)
-
自定义view 只绘制指定区域
-
viewStub 延迟初始化(高效占位)
-
优化层级;避免过度绘制
-
检测工具
- Systrace (检测具体耗时)
关注 Frames;正常:绿色圆点;丢帧,黄色或红色
Alert 栏:自动分析标注出的一些问题条目。
- Layout Inspector (AndroidStudio自带工具,检测布局层级)
通过分析层级,优化布局结构,减少布局深度。
- Choreographer
获取 FPS,可以在线上使用,具备实时性。(> API 16)
- 获取加载布局耗时
- 普通记录…打点统计
- 通过 aop方式记录,低侵入(AspectJ等aop框架;代理等)
- 可以通过 LayoutInflate.Factory 获取每个view加载耗时。
- 监控:通过aop方式,监控执行时间
- 监控指标:FPS,加载时间,布局层级…
APP卡顿优化
- 产生的原因复杂:代码问题、IO、内存、绘制
- 不容易复现;可能和线上场景有关
- 工具
- CPU Profiler :图形的形式展示执行时间和调用栈等。信息全面,包含了所有线层。不过运行时容易卡死,开销严重。
- Systrace:(AndroidSdk tools下的monitor )监控和跟踪Api调用,线程运行情况,生成HTML报告;API18以上,建议使用TraceCompat:特点:轻量级,开销小。直观反应CPU利用率,给我建议。
- StrickMode :严苛模式,Android提供的一种运行时检测机制,方便强大;包含:线程策略和虚拟机策略检测。(线程策略:自定义的耗时调用;磁盘读取操作,网络请求等。。。)(虚拟机策略:Activity泄漏,Sqlite对象泄漏,检测实例数量。)
- 自动化检测方案
方案原理
- 消息处理机制,一个线程只有一个Looper
- mLogging 对象在每一个 message 处理前后被调用。
- 主线程发生卡顿,是在 dispatchMessage 执行耗时操作。
可以通过 Looper.getMainLooper.setMessageLogging();设置自己的Log
匹配>>> Dispatching,阀值时间后执行任务(获取堆栈等信息)
匹配 <<< Finished ,任务启动之前取消掉。
- AndroidPerformanceMonitor
- 非侵入式性能监控组件,通知形式弹出卡片信息。
- 原理就是上面方案原理
- 存在问题:可能抓取的堆栈不一定是卡顿的堆栈;解决:需要高频抓取这段时间多个堆栈信息。上报到后台。
- ANR 分析
发生条件:
- KeyDispatchTimeout :5s
- BroadcastTimeout:前台10s,后台60s。
- Service:前台20s,后台200s。
ANR解决套路
- 线下可以导出ANR文件进行分析(CPU,IO,锁等…)导出ANR文件跳转链接
- 线上:通过FileOberver,监控文件变化(高版本权限问题)
- ANR-WatchDog:非侵入式ANR监控组件(非侵入式,弥补高版本无权限问题。)
对比 AndroidPerformanceMonitor 和 ANR-WatchDog;前者更适合监控卡顿,后者补充ANR监控。
- 单点卡顿检测方案
例:IPC检测方案
- 常规检测方案:IPC前后加埋点。
- 不优雅容易忘记,维护成本大。
- 技巧:线下可以通过 adb 命令检测 adb shell am trace-ipc start
- 优雅的方案:ARTHook(可以检测系统方法) 和 AspectJ (不可监控系统方法,因为没法修改代码。)
监控维度:IPC,IO,DB,View绘制
注: AOP轻量级框架 Lancet
APP线程优化
- 线程调度
需要了解的知识点
- 线程优先级
- 线程切换对CPU的损耗
- Android线程调度中的:
nice值(Process中设置)值越大优先级越低 ;
cgroup:更严格的前后台优先级策略,保证前台线程获取更多CPU(如果只按照单独线程优先级,如果后台线程过多,也会影响到前台线程的调度)- 线程的继承性
- 异步执行方式
- 直接创建Thread
- HandlerThread:自带消息循环,串行执行,适合长时间执行,不断的从队列中获取任务。
- Intent Service:Service内部创建的 HandlerThread(因为是Service优先级比较高不容易被kill)
- AsyncTask:内部是线程池原理;不用自己处理线程切换
-
线程池
-
RxJava
-
线程使用
- 严禁使用 new Thread
- 提供基础线程池供各个任务使用
- 根据任务类型选择合适的线程池;
- 可以使用的时候设置当前线程的名字,方便查找错误
- 重视设置线程优先级,可以设置多次
- 收敛项目线程数量
- 创建线程时获取堆栈
- 无论什么框架都会走到 new Thread
- 通过 Hook (通过一些hook框架)方式查找 Hook点,记录创建线程信息,获取堆栈信息
- 线程池库设计注意点
- 区分任务类型(IO 密集型,CPU密集型)
- IO密集型:不消耗CPU,线程吃可以很大
- CPU密集型:核心线程大小和CPU核数相关
APP网络优化
- 网络优化维度
流量消耗
- 一定时间流量消耗精准度量,网络的类型(移动网络、WI-FI等),前台后台
- 监控相关:用户流量消耗均值,异常率(消耗多,次数多)
- 可以通过request 和 response 进行拦截,主动上报。
网络请求质量
- 用户体验:请求速度,成功率。
- 监控:请求时长,业务成功率,失败率,Top失败接口。
其他
- 成本:贷款,服务器数,CDN
- 耗电
- 工具选择
- Network Profiler:AndroidStudio自带的工具,显示了实时网络活动,发送,接受数据以及连接数。需要启用高级分析。只支持HttpUrlConnection 和 OkHttp两个网络库
- 抓包工具
- Charles (Mac用的比较多):断点功能,MapLocal,弱网环境模拟
- Fiddler(window用的比较多)
- Wireshark
- TcpDump
- Stetho:强大的应用调试桥,连接Android和Chrome ;网络监控,试图查看,数据库查看,命令行扩展等。(一般不会用它作为抓包工具等)
- 精准获取流量消耗
- 绝对值不能一定展现流量高低
- 对比竞品,进行对比
- 异常监控超过指标
线上流量获取方案
- TrafficStats:API18 获取流量(仅获取重起以后的流量)可以设置获取指定UID的流量,可以获取总发送流量。(无法获取某一个时间段内的流量消耗)
- NetworkStatsManager:API23以后的流量统计(可获取指定时间内的流量信息。可获取不同网络下的流量消耗)
前台后台流量获取
- 后台开启定时任务-> 获取一定时间内的流量消耗-> 记录是前台还是后台消耗 -> 分别计算,上报。
- 使用网络的场景
数据API;资源包(RN H5 热更新等);配置信息等
图片:上传下载
监控:需要网络上报信息。单点问题等
- 数据缓存
服务器请求的数据做数据缓存一段时间,避免重复获取。能提高访问速度,节约流量。okhttp volley等都有缓存策略。
- 增量数据更新
只传输有变化的数据;比如:省市区的更新
- 数据压缩
- Post 请求使用 GZIP压缩
- 请求头压缩
- 图片上传压缩(这个点对流量比较明显)
- 数据可以根据需求粒度压缩(我在项目中就对经纬度进行过采样压缩由于数据过大,由于计算量过大请求速度变慢,流量消耗也大。可以采用采样的方式,比如隔几个点取一个经纬度降低密度。)
- 合并网络请求,减少请求次数
- 一些特定信息等WI-FI再上传(比如性能日志等Log)、
- 质量指标
网络请求成功 && 网络请求速度
- 网络请求过程:请求达到运营商通过DNS解析成对应的IP,创建连接根据IP找到服务器,发起请求,服务器找到资源返回。所以对 DNS解析进行优化可以提高网络访问体验。使用HttpDNS 绕过解析过程。可以降低平均访问时长,提高连接成功率。(项目中可以通过 Okhttp + HttpDNS进行优化)
- 升级协议
比如Http协议 从开始到现在升级了 持久链接,网络复用等。
- 网络请求监控
- 请求接口耗时,成功率,失败率(业务失败和请求失败),错误吗
- 图片加载每一步的耗时
- 客户端监控
- 接口每一步的信息(DNS、链接、请求等)
- 请求次数、网络包大小、失败原因
- 图片监控
- 其他优化点
- 可以采用复用连接池
- 如果都是同一个域名的请求 采用 keep-alive 多路复用提高效率(okhttp默认开启)
- 线下测试
- 最好关闭其他应用的网络权限,只开启单独app,测试流量性能更准确
- 侧重点:请求有误,多余请求,网络切换,弱网,无网测试
APP电量优化
电量优化往往被忽略,而且线上不好量化和监测电量问题。
- 通过广播获取电池电量充电状态电池状态等(针对手机,不能针对特定单个 app)
ACTION_BATTERY_CHANGED 广播
- 可以通过 BatteryManager 查看具体可以获取到哪些参数
- Battery Historian : google 推出的Android系统电量分析工具 支持 API21以上版本
- 测试功能强大,推荐使用
- 可视化指标:耗电执行时间,次数等
- 适合线下测试使用
- Energy Profiler
- 监控CPU、网络和GPS使用情况
- 可以直接定位到代码位置
- 可以终点检测一下 heavy 级别的耗电的位置
- 测试场景
- 因为测试很难拿到精准的耗电量,所以测试要针对一些特殊模块,比如视频播放,导航,大运算等。
- 注意使用时长,耗电量,发热情况等
- 后台静默测试:切换到后台查看耗电情况
- 相关优化
cpu
- CPU频率越高会提高电量的消耗;所以多关注CPU消耗的时间片;减少CPU高负荷运作时间等。可以通过一些检测CPU的工具进行查看;如CPU profile等
- 减少切换到后台后,减少APP的操作。切换到前台以后再唤起等。包括动画UI等
网络
- 控制网络请求的时机和次数(比如一起请求,可以减少网络发起次数)
- 数据压缩,减少网络请求时间
- 网络尽量不使用轮询的方式做业务,可能会导致网络一直处于被激活的状态
定位
- 根据场景选择定位模式,高精度,低精度模式;网络定位替代GPS定位。使用后及时关闭GPS,减少更新频率。
其他- JobScheduler 根据特定场景发起任务
APP apk瘦身优化
-
影响APK下载转化率
-
可以通过反编译,或 AndroidStudio 查看APK哪部分占比比较大。可以考虑是否有优化空间
-
通过代码混淆:移除冗余代码,缩短元素名称。增加反编译难度。可以缩小包大小
-
三方库统一,选择更小的库
-
仅引入需要的代码,去掉不需要的文件。
-
移除无用代码
-
可以通过AS RemoveUnusedResource 移除(注意:如果是通过文件名称去匹配的资源文件不是通过R文件等方式。也会被移除)
-
图片压缩
-
SO文件移除:一般根据CPU架构来使用so库,一般选择armeabi就可以了,但是毕竟最匹配的性能最好,比如armeabi-v7 家在armeabi 就不如直接加载V7效率。所以这样会降低一些性能。
-
so更优方案:将需要的so文件全部放在armeabi下,通过代码去判断是加载armeabi-v7的还是armeabi的so文件
APP稳定性优化
- 大型的趋于稳定周期的app才会重视稳定性。稳定性是用户留存很重要的指标。
- Crash是最高优先级
- 稳定性维度
- Crash纬度:降低Crash率
- 性能纬度:提高性能
- 业务高可用
- 重在预防,监控必不可少
- Crash相关指标
- UV (方便用户影响范围)、PV(Crash影响严重程度)Crash 率
- Java Native Crash率
- 启动重点流程Crash率
- 增量存量Crash率
- 尽可能还原崩溃现场
- 堆栈、设备、os版本、进程、线程、logcat
- 前后台、使用时长、APP版本、小版本、升级渠道
- CPU架构、内存信息、线程数、资源包信息、行为日志
- 业务高可用方案 & 容灾方案
- 有时候项目并没有Crash,但是一样会给公司带来很大损失,比如点击了支付但是没有成功等。有时候需要把主流程打上点,看用户进行了哪些操作,出现了哪些异常情况,如走到了catch中或者走到了else中终断了操作。
- 如果发版本后不稳定,兜底方案可以用后台配置功能开关的方式,禁用功能。或者 A
B版本切换新旧老版本。- 可以通过类似于组件化路由框架中,根据线上问题动态更改路由跳转地址
- 热修复
- 根据Crash次数,如果次数过多,清除APP全部信息,防止脏数据导致用户崩溃
- 多次请求服务失败,可以主动拒绝请求服务,也给服务器降低压力
其他优化
-
StringBuilder 代替 String拼接
-
存储优化
- SharePreperences:加载慢,初始化需要加载整个文件;全量写入,单次改动都需要整体写入。卡顿,虽然apply切换到子线程,但是也会造成主线程等待结果。不适合存储大量数据,不支持跨进程通信。如果发生Crash不能保证数据完整。
- 使用MMKV替代SharePreperences,通过MMap和文件锁;增量写入,使用Protocol Buffer,支持从SharePreperences迁移。
- 项目日志存储优化:如果每次写磁盘会影响性能,如果写入缓存区最后直接写磁盘有可能会导致数据丢失。也可以用MMap方式,它是一种内存映射的方法,高性能不丢失。
- 常用数据缓存,避免多次读取。
- 采用适当的缓冲区Buffer大小 4-8k