一、启动优化
1、启动优化介绍
app启动超过8s留存率就会流失70%。
启动分为冷启动(Application及Activity生命周期都重启)、热启动(从后台变到前台)、温启动(不走Application生命周期,只是重新启动Activity的生命周期)
需要优化的是Application和Activity的生命周期。
2、启动测量方式
测量应用启动开始到第一帧界面显示时的时间。
比如在recycleview 的onBindView方法中添加监听,当第一帧界面显示的时候统计界面显示结束
3、启动工具选择1
①TraceView,通过代码进行埋点,将数据保存到data目录下,双击该文件后studio会图形化显示。
可以查看线程运行信息、调用堆栈、方法执行时间
Wall Clock Time:该线程真正执行了多少时间
ThreadTime: CPU执行的时间,少于 Wall Clock Time
selfTime:可以计算该方法的执行时间
childrenTime :计算调用其它方法的时间
通过两个版本这两个时间可以比较可以得出新增的哪个方法耗时增加了,并且逆推出谁改动了代码
缺点:
运行时开销严重,整体都会变慢
可能带偏优化方向
4、启动工具选择2
systrace:结合Android内核的数据,生成Html报告。API 18以上,推荐TraceCompat
5、优雅获取方法耗时
通过AspectJ的AOP方式获取方法耗时,可以指定某个类、某个方法
6、异步优化
①将所有的task放到线程池的run方法中
②如果有需要先初始化完才能执行后续操作的task,则添加countdownlatch
7、异步初始化最优解--启动器
痛点:
代码不优雅
场景不好处理(依赖关系)
维护成本高
核心思想:
充分利用CPU多核,自动梳理任务顺序
流程:
代码Task化,启动逻辑抽象为Task
根据所有任务依赖关系排序生成一个有向无环图
多线程按照排序后的优先级依次执行
8、更优的延迟初始化方案
核心思想:对延迟任务进行分批初始化
利用IdleHandler特性,空闲执行
优点:
执行时机明确
缓解Feed卡顿
二、内存优化
1、内存优化工具选择
Profiler
MAT (Memory Analyzer)
LeakCanary
2、Android内存管理机制
Java内存分配:
方法区
虚拟机栈
本地方法栈
堆
程序计数器
Java内存回收算法:
标记-清除算法
复制算法
标记整理算法
分代收集算法
Android内存管理机制:
内存弹性分配,分配值最大值受具体设备影响
OOM场景:内存真正不足、可用内存不足
Dalvik与Art区别:
Dalvik仅固定一种回收算法
Art回收算法可运行期选择
Art具备内存整理能力,减少内存空洞
Low Memory Killer:
进程分类:前台进程、可见进程、服务进程、后台进程、空进程
回收收益
3、内存抖动:
定义:内存频繁分配和回收导致内存不稳定
表现:频繁GC、内存曲线呈锯齿状
使用Memory Profiler初步排查
使用Memory Profiler或CPU Profiler结合代码排查
内存抖动解决技巧:
找循环或者频繁调用的地方
4、内存泄漏
定义:内存中存在已经没有用的对象
表现:内存抖动、可用内存逐渐变少
危害:内存不足、GC频繁、OOM
内存泄漏解决技巧:
使用Memory Profiler初步观察
通过Memory Analyzer结合代码确认
5、Bitmap内存模型
API10之前Bitmap自身在Dalvik Heap中,像素在Native中
API10之后像素也被放在Dalvik Heap中
API26之后像素在Native
获取Bitmap占用内存:
getByteCount
宽*高*一像素占用内存
ARTHook介绍:
挂钩,将额外的代码钩住原有方法,修改执行逻辑
--运行时插桩
--性能分析
Epic简介:
Epic是一个虚拟机层面、以Java Method为粒度的运行时Hook框架
支持Android 4.0--9.0
ARTHOOK实现:
无侵入性
通用性强
兼容问题大,开源方案不能带到线上环境
6、线上内存监控方案
常规方式
LeakCanary定制
线上监控完整方案
常规实现一:
设定场景线上Dump:Debug.dumpHprofData()
超过最大内存80% --> 内存Dump --> 回传文件 --> MAT手动分析
Dump文件太大,和对象数正相关,可裁剪
上传失败率高、分析困难
配合一定策略,有一定效果
常规实现二:
LeakCannar带到线上
预设泄漏怀疑点
发现泄漏回传
总结:
不适合所有情况,必须预设怀疑点
分析比较耗时、也容易OOM
LeakCanary原理:
监控生命周期,onDestroy添加RefWatcher检测
二次确认断定发生内存泄漏
分析泄漏,找引用链
监控组件 + 分析组件
LeakCanary定制:
预设怀疑点 ----》 自动找怀疑点
分析泄漏链路慢 ----》 分析Retain size大的对象
分析 OOM ----》 对象裁剪,不全部加载到内存
线上监控完整方案:
待机内存、重点模块内存、OOM率
整体及重点模块GC次数、GC时间
增强的LeakCanary自动化内存泄漏分析
7、总结
优化大方向:
内存泄漏
内存抖动
Bitmap
优化细节:
LargeHeap属性
onTrimMemory
使用优化过的集合:SparseArray
谨慎使用SharedPreference
谨慎使用外部库
业务架构设计合理
三、App布局优化
1、
绘制原理
CPU负责计算线上内容
GPU负责栅格化(UI元素绘制到屏幕上)
16ms发出VSync信号触发UI渲染
大多数的Android设备屏幕刷新频率:60Hz
2、优化工具:
Systrace:
--关注Frames
--正常:绿色原点,丢帧:黄色或红色
LayoutInspector:
AndroidStudio自带工具
查看视图层次结构
Choreographer:
获取FPS,线上使用,具备实时性
--Api 16之后
--Choreographer.getInstance().postFrameCallback
3、布局加载流程
setContentView --> LayoutInflater --> inflate --> getLayout --> createViewFromTag --> Factory --> createView -->反射
布局文件解析: IO过程
创建View对象:反射
LayoutInflater.Factory
LayoutInflater创建View的一个Hook
定制创建View的过程:全局替换自定义TextView等
Factory与Factory2
Factory2继承于Factory
多了一个参数:parent
4、优雅获取界面布局耗时
常规方式
AOP/ArtHook
获取任一控件耗时
常规方式:
背景:获取每个界面加载耗时
实现:覆写方法、手动埋点
AOP实现:
切Activity的setContentView
@Around("execution(*android.app.Activity.setContentView(..))")
获取每一个控件加载耗时:
低侵入性
LayoutInflater.Factory
5、异步Inflate实战
布局文件读取慢:IO过程
创建View慢:反射(比new慢3倍)
思路介绍:
根本性的解决
侧面缓解
AsyncLayoutInflater
简称异步Inflate
--WorkThread加载布局
--回调主线程
--节约主线程时间
AsyncLayoutInflater:
不能设置LayoutInflater.Factory(自定义解决)
注意View中不能有依赖主线程的操作
6、布局加载优化
Java代码写布局:
本质上解决了性能问题
引入新的问题:不便于开发、可维护性差
X2C介绍:
保留XML优点,解决其性能问题
--开发人员写XML,加载Java代码
--原理:APT编译器翻译XML为Java代码
X2C问题:
部分属性Java不支持
失去了系统的兼容(AppCompat)
7、视图绘制优化
优化布局层级及复杂度
避免过度绘制
视图绘制回顾:
测量:确定大小
布局:确定位置
绘制:绘制视图
性能瓶颈:
每个阶段耗时
自顶而下的遍历
触发多次
布局层级及复杂度:
准则
--减少View树层级
--宽而浅,避免窄而深
ConstraintLayout:
实现几乎完全扁平化布局
构建复杂布局性能更高
具有RelativeLayout和LinearLayout特性
布局层级及复杂度:
不嵌套使用RelativeLayout
不在嵌套LinearLayout中使用weight
merge标签:减少一个层级,只能用于根View
过度绘制:
一个像素最好只被绘制一次
调试GPU过度绘制
蓝色可接受
避免过度绘制方法:
去掉多余背景色,减少复杂shape使用
避免层级叠加
自定义View使用clipRect屏蔽被遮盖View绘制
其它技巧:
Viewstub:高效占位符、延迟初始化
onDraw中避免:创建大对象、耗时操作
TextView优化
做完布局优化有哪些成果产出:
体系化监控手段:线下+线上
指标:FPS、加载时间、布局层级
核心路径保障
四、App卡顿优化
1、卡顿介绍及优化工具选择
很多性能问题不易被发现,但是卡顿很容易被直观感受
卡顿问题难以定位
卡顿问题难在哪里?
产生原因错综复杂:代码、内存、绘制、IO
不易复现:当时场景强相关
CPU Profiler:
图形的形式展示执行时间、调用栈等
信息全面,包含所有线程
运行时开销严重,整体都会变慢
使用方式:
Debug.startMethodTracing("");
Debug.stopMethodTracing("");
生成文件在sd卡:Android/data/packagename/files
Systrace:
监控和跟踪Api调用、线程运行情况,生成Html报告
API 18以上使用,推荐TraceCompat
使用方式:
python systrace.py -t 10 [other-options][categories]
优点:
轻量级,开销小
直观反映CPU利用率
给出建议
StrictMode
严苛模式,Android提供的一种运行时检测机制
方便强大,容易被忽视
包含:线程策略和虚拟机策略检测
线程策略:
自定义的耗时调用,detectCustomSlowCalls()
磁盘读取操作,detectDiskReads
网络操作,detectNetwork
虚拟机策略:
Activity泄漏,detectActivityLeaks()
sqlite对象泄漏,detectLeakedSqlLiteObjects
检测实例数量,setClassInstanceLimit()
2、自动化卡顿检测方案
为什么需要自动化检测方案:
系统工具适合线下针对性分析
线上及测试环节需要自动化检测方案
方案原理:
消息处理机制,一个线程只有一个Looper
mLogging对象在每个message处理前后被调用
主线程发生卡顿,是在dispatchMessage执行耗时操作
具体实现:
Looper.getMainLooper().setMessageLogging();
匹配>>>>>Dispatching,阈值时间后执行任务(获取堆栈)
匹配<<<<
AndroidPerformanceMonitor:
非侵入式的性能监控组件,通知形式弹出卡顿信息
方案总结:
非侵入式
方便精确,定位到代码某一行
自动检测方案问题:
确实卡顿了,但卡顿堆栈可能不准确
和OOM一样,最后的堆栈只是表象,不是真正问题
解决:
获取监控周期内的多个堆栈,而不仅是最后一个
startMonitor --> 高频采集堆栈 -->endMonitor
记录多个堆栈 --> 上报
海量卡顿堆栈处理:
高频卡顿上报量太大,服务端有压力
--分析:一个卡顿下多个堆栈大概率有重复
--解决:对一个卡顿下堆栈进行hash排重,找出重复的堆栈
--效果:极大的减少展示量同时更高效找到卡顿堆栈
3、ANR介绍
KeyDispatchTimeout,5s
BroadcastTimeout, 前台10s,后台60s
ServiceTimeout,前台20s,后台200s
ANR执行流程
发生ANR
进程接收异常终止信号,开始写入进程
弹出ANR提示框(Rom表现不一)
ANR解决套路
adb pull data/anr/traces.txt 存储路径
详细分析:CPU、IO、锁
线上ANR监控方案:
通过FileObserver监控文件变化,高版本权限问题
ANR-WatchDog
--非侵入式的ANR监控组件
ANR-WatchDog原理
start -> post消息改值 -> sleep
检测是否修改 -> 判断ANR发生
方案总结:
非侵入式
弥补高版本无权限问题
结合使用
区别:
AndroidPerformanceMonitor: 监控Msg
ANR-WatchDog : 看最终结果
前者适合监控卡顿,后者适合补充ANR监控
4、卡顿单点问题检测方案
背景介绍:
自动卡顿监测方案并不够
体系化解决方案务必尽早暴漏
单点问题:主线程IPC、DB
IPC问题监测:
监测指标:
--IPC调用类型
--调用耗时、次数
--调用堆栈、发生线程
常规方案:
--IPC前后加埋点
--不优雅、容易忘记
--维护成本大
IPC问题监测技巧:
adb命令:
--adb shell am trace-ipc start
--adb shell am trace-ipc stop --dump-file /data/local/tmp/ipc-trace.txt
--adb pull /data/local/tmp/ipc-trace.txt
IPC问题监测:
优雅方案:
--ARTHook还是AspectJ
--ARTHook:可以Hook系统方法
--AspectJ:非系统方法
卡顿问题监测方案:
--利用ARTHook完善线下工具
--开发阶段Hook相关操作,暴漏、分析问题
监控纬度:
IPC
IO、DB
View绘制
5、界面秒开
界面秒开就是一个小的启动优化
可以借鉴启动优化及布局优化章节
--SysTrace,优雅异步+优雅延迟初始化
--异步Inflate、X2C、绘制优化
--提前获取页面数据
界面秒开率统计:
onCreate到onWindowFocusChanged
特定接口
Lancet:
轻量级 Android AOP框架:
--编译速度快,支持增量编译
--API简单,没有任何多余代码插入apk
--@Proxy 通常用于对系统API调用的Hook
--@Insert 常用于操作App与Library的类
界面秒开监控纬度:
总体耗时
生命周期耗时
生命周期间隔耗时
6、耗时盲区监控背景
生命周期的间隔
onResume到Feed展示的间隔
举例:postMessage,很可能在Feed之前执行
耗时盲区监控难点:
只知道盲区时间,不清楚具体在做什么
线上盲区无从追查
耗时盲区监控线下方案:
TraceView
--特别适合一段时间内的盲区监控
--线程具体时间做了什么,一目了然
思考分析:
所有方法都是Msg,mLogging?没有Msg具体堆栈
AOP切Handler方法?不清楚准确执行时间
耗时盲区监控线上方案:
使用统一的Handler:定制具体方法
定制gradle插件,编译期动态替换
总结:
卡顿监控重要的一环,全面性保障
TraceView适合线下,可以监控系统Msg
动态替换适合线上,只有应用自身的Msg
7、卡顿优化技巧总结初步
耗时操作:异步、延迟
布局优化:异步Inflate、X2C、重绘解决
内存:降低内存占用,减少GC时间
系统工具认识、使用
SysTrace
TraceView
StrictMode
自动化监控及优化:
AndroidPerformanceMonitor、ANR-WatchDog
高频采集,找出重复率高的堆栈
卡顿监控工具:
单点问题:AOP、Hook
盲区监控:gradle编译期替换
卡顿监控指标:
卡顿率、ANR率、界面秒开率
交互时间、生命周期时间
上报环境、场景信息
五、App线程优化
1、
线程调度原理:
任何时刻,只有一个线程占用CPU,处于运行状态
多线程并发:轮流获取CPU使用权
JVM负责线程调度:按照特定机制分配CPU使用权
线程调度模型:
分时调度模型:轮流获取、均分CPU时间
抢占式调度模型:优先级高的获取,JVM采用
Android线程调度:
nice值:
--Process中定义
--值越小,优先级越高
--默认是THREAD_PRIORITY_DEFAULT,0
cgroup:
更严格的群组调度策略
保证前台线程可以获取到更多的CPU
注意点:
线程过度会导致CPU频繁切换,降低线程运行效率
正确认识任务重要性决定哪种优先级
优先级具有继承性
2、Android异步方式汇总
Thread:
最简单、常见的异步方式
--不易复用,频繁创建及销毁开销大
--复杂场景不易使用
HandlerThread:
自带消息循环的线程
--串行执行
--长时间运行,不断从队列中获取任务
IntentService:
继承自Service在内部创建HandlerThread
--异步,不占用主线程
优先级较高,不易被系统Kill
AsyncTask:
Android提供的工具类
--无需自己处理线程切换
需注意版本不一致问题
线程池
Java提供的线程池:
--易复用,减少频繁创建、销毁的时间
功能强大:定时、任务队列、并发数控制等
RxJava
由强大的Scheduler集合提供
--不同类型的区分:IO、Computation
总结:
推荐度:从前往后排列
正确场景选择正确的方式
2、Android线程优化
线程使用准则:
严禁直接new Thread
提供基础线程池供各个业务线使用
--避免各个业务线各自维护一套线程池,导致线程数过多
根据任务类型选择合适的异步方式
--优先级低,长时间执行,HandlerThread
创建线程必须命名
--方便定位线程归属
--运行期Thread.currentThread().setName修改名字
关键异步任务监控
--异步不等于不耗时
--AOP的方式来做监控
重视优先级设置
--Process.setThreadPriority();
--可以设置多次
锁定线程创建背景:
项目变大之后收敛线程
项目原码、三方库、aar中都有线程的创建
避免恶化的一种监控预防手段
分析:
创建线程的位置获取堆栈
所有的异步方式,都会走到new Thread
特别适合Hook手段
找Hook点:构造函数或者特定方法
Thread的构造函数
3、
线程收敛常规方案:
根据线程创建堆栈考量合理性,使用统一线程库
各业务线下掉自己的线程库
基础库怎么使用线程:
直接依赖线程库
--缺点:线程库更新可能会导致基础库更新
基础库内部暴漏API:setExecutor
初始化的时候注入统一的线程库
统一线程库:
区分任务类型:IO、CPU密集型
IO密集型任务不消耗CPU,核心池可以很大
CPU密集型任务:核心池大小和CPU核心数相关
六、网络优化纬度
1、网络优化纬度
一段时间流量消耗的精准度量,网络类型、前后台
监控相关:用户流量消耗均值、异常率(消耗多、次数多)
完整链路全部监控(Request、Response),主动上报
网络请求质量:
用户体验:请求速度、成功率
监控相关:请求时长、业务成功率、失败率、Top失败接口
其它:
公司成本:带宽、服务器数、CDN
耗电
优化误区:
只关注流量消耗,忽视其它纬度
只关注均值、整体,忽视个体
2、工具选择
Network Profiler:
显示实时网络活动:发送、接收数据及连接数
需要启动高级分析
只支持HttpURLConnection和OkHttp网络库
抓包工具:
Charles
Fiddler
Wireshark
TcpDump
3、精准获取流量消耗实战
TrafficStats:API8以上重启以来的流量数据统计
getUidRxBytes(int uid)指定Uid的接收流量
getTotalTxBytes()总发送流量
缺点:
无法获取某个时间段内的流量消耗
NetworkStatsManager: API23之后流量统计
可获取指定时间间隔内的流量信息
可获取不同网络类型下的消耗
前后台流量获取方案:
难题:线上反馈App后台跑流量
只获取一个时间段的值不够全面
后台定时任务-->获取间隔内流量-->记录前后台--> 分别计算 --> 上报APM后台 --> 流量治理依据
4、网络请求流量优化
数据缓存:
服务端返回加上过期时间,避免每次重新获取
节约流量且大幅提高数据访问速度,更好的用户体验
OkHttp、Volley都有较好的实践
增量数据更新:
加上版本的概念,只传输有变化的数据
配置信息、省市区县等更新
数据压缩:
Post请求Body使用GZip压缩
请求头压缩
图片上传之前必须压缩
优化发送频率和时机:
合并网络请求、减少请求次数
性能日志上报:批量+特定场景上报
图片相关:
图片使用策略细化:优先缩略图
使用WebP格式化图片
5、网络请求质量优化
质量指标:
网络请求成功率
网络请求速度
Http请求过程:
请求到达运营商的Dns服务器并解析成对应的IP地址
创建连接,根据IP地址找到相应的服务器,发起一个请求
服务器找到对应的资源原路返回访问的用户
DNS相关:
问题:DNS被劫持、DNS解析慢
方案:使用HttpDNS,绕过运营商域名解析过程
优势:降低平均访问时长、提高连接成功率
协议版本升级:
1.0:版本TCP连接不复用
1.1:引入持久连接,但数据通讯按次序进行
2:多工,客户端、服务器双向实时通信
网络请求质量监控:
接口请求耗时、成功率、错误码
图片加载的每一步耗时
网络容灾机制:
备用服务器分流
多次失败后一定时间内不进行请求,避免雪崩效应
其它:
CDN加速、提高带宽、动静资源分离(更新后清理缓存)
减少传输量,注意请求时机及频率
OkHttp的请求池
6、网络体系化方案
线下测试:
方案:只抓单独App
侧重点:请求有误、多余,网络切换、弱网、无网测试
线下测试:
方案:只抓单独App
侧重点:请求有误、多余,网络切换、弱网、无网测试
线上监控:
服务端监控:
--请求耗时(区分地域、时间段、版本、机型)
--失败率(业务失败与请求失败)
--Top失败接口、异常接口
客户端监控:
--接口的每一步详细信息(DNS、连接、请求等)
请求次数、网络包大小、失败原因
图片监控
异常监控体系:
--服务器防刷:超限拒绝访问
客户端:大文件预警、异常兜底策略
单点问题追查