Android原生的HighCPU使用率查杀机制
摘要
原生的HighCPU使用率查杀机制是基于读取/proc/pid/stat中的utime + stime后,根据CPU使用率= (utime + stime / totalTime)*100%进行实现,当检测后台进程的CPU使用率超过阈值时,执行查杀和统计到电池数据中。
细节点:
1. 原生根据不同的后台运行时间,制定不同的查杀阈值,这点不错哈;
2. 如果对超级应用或核心应用有保活的定制需求,需要进行在原生的CPU高负载策略进行规避哈;
CPU高负载检查主要是在AMS中进行实现,具体关注如下4个函数,就可以大概清楚原生的CPU高负载查杀机制了
1.checkExcessivePowerUsageLPr()函数
1.更新cpu统计信息 updateCpuStatsNow()
2.遍历所有进程 forEachLruProcessesLOSP
3.计算进程变成非重要进程的时长
4.不同app根据非重要状态时长设定不同CPU阈值
若非重要状态的持续时长5分钟内,则CPU使用率阈值25%
若非重要状态的持续时长10分钟内,则CPU使用率阈值25%
若非重要状态的持续时长15分钟内,则CPU使用率阈值10%
若非重要状态的持续时长大于15分钟,则CPU使用率阈值2%
2.updateAppProcessCpuTimeLPr()函数
主要通过PhantomProcessRecord获取进程CPU时间
1.获取app当前CPU使用时间
2.获取app上次CPU使用时间
3.CPU使用时间=当前-上次
4.检查进程CPU使用时间是否超过阈值
如果超过阈值,则进行查杀处理
@GuardedBy("mProcLock")
private void updateAppProcessCpuTimeLPr() {
...
// CPU使用率换算和阈值超过判断
if (checkExcessivePowerUsageLPr(uptimeSince, doCpuKills, cpuTimeUsed,
app.processName, app.toShortString(), cpuLimit, app)) {
...
if (app.getThread() == null
|| 如果有保活的需求,可以新增到该处
|| app.mState.getSetProcState() < ActivityManager.PROCESS_STATE_HOME) {
return;
}
// 超过阈值,执行查杀
app.killLocked("excessive cpu " + cpuTimeUsed + " during "
+ uptimeSince + " dur=" + checkDur + " limit=" + cpuLimit,
ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
true);
...
}
3.checkExcessivePowerUsageLPr()函数
1.将CPU使用时间转化为CPU使用率
CPU使用率=(CPU运行时间 * 100) / uptimeSince
2. 如果超过阈值,则上报到batteryStats统计,并最终返回true让策略进行查杀处理
4.getCpuTimeForPid函数
读文件节点/proc/pid/stat获取utime和stime,其中utime数据第14位,stime数据第15位。CPU使用率= (utime + stime / totalTime)*100%
public long getCpuTimeForPid(int pid) {
final String statFile = "/proc/" + pid + "/stat";
...
}
其他CPU负载值的获取方式介绍
cpu负载值获取方式 | 计算公式 | 优点 | 缺点 |
adb shell top | 直接可查看进程级的cpu负载百分百值 | 获取方便且准确度高 | 本身top命令会存在高cpu负载的占用 |
adb shell top -H | 直接可查看线程级的cpu负载百分百值 | ||
adb shell cat /proc/pid/stat | cpuload = (utime + stime / totalTime)*100% utime数据第14位,stime数据第15位 | 读文件节点获取,方便代码或脚本实现 | 批量读取大量文件节点 |
原生 框架读文件节点/proc/pid/stat | |||
内核层 | 中task_struc接口获取utime + stime,cpuload = (utime + stime / totalTime)*100% | 内存方式读取,性能效率最高,且本身cpu占用率及其低,0.3%以内 | 内核层到框架层通信和策略联动,虽然麻烦,从性能角度来说我觉得是最佳方案 |
Perfetto或trace | 线程的cpu负载值 = 该线程运行总时长 / 总时长 = WallDuration / totalTime | 直观准确 | 需要抓trace哈 |
adb shell dumpsys cpuinfo | 直接查看进程及对应线程的cpu负载百分百值 | 获取方便又详细且准确率高 | dump命令本身也会存在高cpu负载占用,即性能耗时 |
utime: 线程或进程在用户模式下花费的时间,单位是 jiffies。
stime: 线程或进程在内核模式下花费的时间,单位是 jiffies。
我认为的最佳方案是:内核层通过内存方式读取线程或进程用户态CPU时间(utime)和内核态CPU时间(stime)并换算为cpu负载值+ 框架层场景策略进行cpu高负载管控