Android 功耗测试
阅读原文时间:2023年07月11日阅读:1


Evernote Export






电池
  • 电池容量,电池容量越大手机越笨重
  • 充电时间。低电压高电流,高电压低电流
  • 寿命
  • 安全性
硬件
  • 处理器芯片
    • CPUGPU,NPU
  • 基带芯片
    • 蜂窝网WIFINFC
  • 传感器
    • 加速度传感器,陀螺仪,气压计,温度传感器
  • 外设
    • 相机,麦克风,扬声器
  • 其他
    • GPS,内存,显示屏闪存
资源调度机制是厂商功耗优化的最重要的手段,手机基带,GPS,这些模块在不使用时也会进入低功耗或休眠模式,达到降低功耗的目的。
手机厂商为了保证头部应用能有更好的体验,厂商愿意给他们分配更多的资源

软件
    如何评估软件的耗电情况: 电能 = 电压 \* 电流 \* 时间
        手机的电压一般恒定,Adnroid 系统要求厂商必须在/frameworks/base/core/res/xml/power\_profile.xml提供组件的电源配置文件
        如何在不同厂商中获取耗电流
  1. 从手机中导出 /system/framework/framework-res.apk 文件
  2. 使用反编译工具对导出文件framework-res.apk 进行反编译
  3. 查看power\_profile.xml 文件在frame-res 反编译目录路径: /res/xml/power\_profile.xml
对于系统的电量消耗情况,可以通过dumpsys   batterystatus 导出

adb shell dumpsys batterystats > battery.txt
// 各个Uid的总耗电量,而且是粗略的电量计算估计。
Estimated power use (mAh):
    Capacity: 3450, Computed drain: 501, actual drain: 552-587
    ...
    Idle: 41.8
    Uid 0: 135 ( cpu=103 wake=31.5 wifi=0.346 )
    Uid u0a208: 17.8 ( cpu=17.7 wake=0.00460 wifi=0.0901 )
    Uid u0a65: 17.5 ( cpu=12.7 wake=4.11 wifi=0.436 gps=0.309 )
    ...
// reset电量统计
adb shell dumpsys batterystats --reset


编号
测试方法
适用场景
优点
缺点
1
稳压电源+ 电流仪
整机电流
可以测试整机电流,而且数据精确
需要准确硬件工具,测试操作复杂,而且不能准确测试APP消耗电量
2
dumpsys batterystats
App电量
有耗电量的详细数据
结果可读行比较差
3
系统“耗电排行”
APP电量
直观,跟用户看到的一致
没有详细的数据
4
Battery History
App电量
结果直观,有耗电量的详细数据
适用与Android 5.0及以上系统

//7.0和7.0以后
$ adb bugreport bugreport.zip
//6.0和6.0之前:
$ adb bugreport > bugreport.txt
//通过historian图形化展示结果
python historian.py -a bugreport.txt > battery.html
Android 耗电优化历史
  1.     野蛮生长: Pre   Android 5.0
    1.  耗电与安装应用程序的数量有关,用户安装越多的应用程序,无论是否打开它们,手机耗电都会很快。
    2. App耗电量与APP使用时间无关,用户希望 App 的耗电量应该与它的使用时间相关,但是有些应用即使常年不打开,依然非常耗电
    3. 电量问题排查复杂。无论是电量的测量,还是耗电问题的排查都异常艰难。   
逐步收紧:Android 5.0 ~ Android 8.0
Android 6.0 开始~, Google开始着手清理后台应用和广播来进一步省电
  • 省电模式不够省电
  • 用户对应用控制力度不够
  •  Target API 开发者响应不积极
最严格:Android 9.0


耗电优化与线上监控

耗电优化
  • 后台耗电(厂商预装项目要求最严格的正是应用后台待机耗电)
    • 用户认为应用的耗电量应该是和使用时间有关的
    • Camera, Audio, Video,Bluetooth, Network, Sensor. Radio, Screen,WiFi, CPU, GPS
  • 符合系统规范,让系统认为你耗电是正常的
    • Android Vitals
耗电优化难点
  • 缺乏现场,无法复现
  • 信息不全,难以定位
  • 无法评估结果
耗电优化的方法
  • 常见的耗电方式
    • 某个需求场景
    • 代码的Bug
  • 优化方法
    • 利用厂商通道或定时拉去最新消息
耗电监控
  1. Android  Vistals
  2. 耗电监控应该监控什么
    1. 监控信息---> 系统关心什么,我们就监控什么(后台耗电监控  )   Alarm wakeup、WakeLock、WiFi scans、Network
    2. 现场信息---> 完整的对栈信息
    3. 提炼规则---->将监控的内容抽象成规则



前台规则
后台规则
Alarm
单个Alarm每小时不能启动超过20次
单个Alarm 每小时不能启动超过10次
WakeLock
单个WakeLock每小时不能超过30分钟或者超过20次
  1. WakeLock每 小时不能超过30分钟;
  2. 单个WakeLock每小时不能超过10分钟或12次 
Sensor
不监控前台sensor使用
每小时使用不能超过30分钟
WIFI scans
每小时扫描不允许超过10次
每小时扫描不允许超过4次
Bluetooth scans
每小时扫描不能超过10次
每小时扫描不允许超过4次
GPS
每小时使用不能超过30分钟
每小时使用不能超过15分钟
Camera
不监控前台相机使用
后台不允许使用相机
Network
不监控前台网络耗电
每小时后台网络数据量不能超过10MB
CPU
不监控前台CPU
后台CPU持续超过30分钟

 


如何监控耗电
 
Java Hook

    WakeLock
// 代理PowerManagerService
ProxyHook().proxyHook(context.getSystemService(Context.POWER\_SERVICE), "mService", this);
@Override
public void beforeInvoke(Method method, Object\[\] args) {
    // 申请Wakelock
    if (method.getName().equals("acquireWakeLock")) {
        if (isAppBackground()) {
            // 应用后台逻辑,获取应用堆栈等等     
         } else {
            // 应用前台逻辑,获取应用堆栈等等
         }
    // 释放Wakelock
    } else if (method.getName().equals("releaseWakeLock")) {
       // 释放的逻辑    
    }
}
Alarm : 用来执行定时的重复任务



// 代理AlarmManagerService
new ProxyHook().proxyHook(context.getSystemService
(Context.ALARM\_SERVICE), "mService", this);


public void beforeInvoke(Method method, Object\[\] args) {
    // 设置Alarm
    if (method.getName().equals("set")) {
        // 不同版本参数类型的适配,获取应用堆栈等等
    // 清除Alarm
    } else if (method.getName().equals("remove")) {
        // 清除的逻辑
    }
}

后台CPU
对于 GPS 监控,我们可以通过 Hook 代理LOCATION\_SERVICE。对于 Sensor,我们通过 Hook SENSOR\_SERVICE中的“mSensorListeners”

通过 Hook,我们可以在申请资源的时候将堆栈信息保存起来。当我们触发某个规则上报问题的时候,可以将收集到的堆栈信息、电池是否充电、CPU 信息、应用前后台时间等辅助信息也一起带上








插桩

写一个基础类,然后在统一的调用接口中增加监控逻辑。以 WakeLock 为例:


public class WakelockMetrics {
    // Wakelock 申请
    public void acquire(PowerManager.WakeLock wakelock) {
        wakeLock.acquire();
        // 在这里增加Wakelock 申请监控逻辑
    }
    // Wakelock 释放
    public void release(PowerManager.WakeLock wakelock, int flags) {
        wakelock.release();
        // 在这里增加Wakelock 释放监控逻辑
    }
}