在 Add Metrics 中我们可以增加更多的测量项。
CPU running

如果一直处于 running,则表明电量消耗比较高。
JobScheduler

选中 Job Scheduler 的某一个工作时间片,我们可以查看具体的 发生的时间、耗时以及次数,最重要的是它统计出来了是哪一个进程在使用这个 JobScheduler。
App Selection

- 1)、选择要分析电量的指定 App。
- 2)、点击右边区域的 System Stats 一栏可以在下方查看各个系统组件的电量百分比消耗详情,例如 Userspace Wakelocks。
主入口处的 Switch to Bugreport Comparison

选择多个文件进行上传对比。
5、电量专项测试
1)、耗电场景测试
- 复杂计算。
- 音视频播放。
2)、传感器相关
- 使用时长
- 耗电量
- 发热
3)、后台静默测试
四、耗电优化
===============================================================
1、耗电优化的难点
- 1)、「缺乏现场,无法复现」。
- 2)、「信息不全,难以定位」。
- 3)、「无法评估结果」。
在 App 开发中,经常会由于某个需求场景或 代码 bug 而导致大量耗电。
2、后台调度任务省电
思考步骤
- 需要后台运行
- 长时间下载:DownloadManager
- 数据同步:SyncAdapter
- 本地任务:JobScheduler
- 特定时间执行:AlarmManager
- 实时通信:推送服务
- 立刻执行:Foreground Service
对于耗电优化中,我们最常用的就是 JobScheduler,下面👇,我们来实战一下。
Job Scheduler 实战
/
- 开启 JobScheduler
*/
private void startJobScheduler() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), JobSchedulerService.class.getName()));
// 设置仅在 充电和WIFI 下才使用 JobScheduler 进行批量任务处理
builder.setRequiresCharging(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
jobScheduler.schedule(builder.build());
}
}
其中,「JobSchedulerService 就是用于进行批量任务处理的服务」,示例代码如下所示:
/
- 用于进行批量任务处理的 JobSchedulerService
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class JobSchedulerService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
// 此处执行在主线程
// 模拟一些处理:批量网络请求,APM日志上报
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}
特点
- 1)、「仅支持 API 21 及之上」。
- 2)、「在符合某些条件时创建执行在后台的任务」。
- 3)、「把不紧急的任务放到更合适的时机批量处理」。
符合 Android 规则,手机在充电状态才去做耗电工作。示例代码如下所示:
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);
//获取用户是否在充电的状态或者已经充满电了
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;
3、电量优化套路总结
1、优化应用的后台耗电
避免后台长时间获取 WakeLock、WIFI 和蓝牙的扫描等。
2、符合系统的耗电规则
Android P 使用了 Android Vitals 监控后台耗电,其规则如下所示:
- 1)、Alarm Manager wakeup 唤醒过多:当手机不在充电状态,每小时 wakeup 唤醒次数大于 10 次。
- 2)、频繁使用局部唤醒锁:当手机不在充电状态,partial wake lock 持有超过1小时。
- 3)、后台网络使用量过高:当手机不在充电状态而且应用在后台,每小时网络使用量超过 50MB。
- 4)、后台 WiFi scans 过多:当手机不在充电状态而且应用在后台,每小时大于4次 WiFi scans。
3、CPU 时间片
「Android 手机保护 AP 和 BP 两个 CPU。AP 即 Application Processor,所有的用户界面以及 App 都是运行在 AP 上的。BP 即 Baseband Processor,手机射频都是运行在这个 CPU 上的。而一般我们所说的耗电,PowerProfile 文件里面的 CPU,指的是 AP」。
CPU 耗电通常有两种情况:
- 1)、「长期频繁唤醒:原本可以仅仅在 BP 上运行,消耗 5mA 左右,但是因为唤醒,AP 就会运作,不同手机情况不一样,至少会导致 20~30 mA 左右的耗电」。
- 2)、「CPU 长期高负荷:例如 App 退到后台的时候没有停止动画,或者程序有不退出的死循环等等,导致 CPU 满频、满核地跑」。
常用优化 CPU 时间片的方式有:
- 1)、「获取运行过程线程 CPU 消耗,定位 CPU 占用率异常方法」。
- 2)、「减少后台应用的主动运行」。
4、网络相关
通常情况下,使用 WIFI 连接网络时的功耗要低于使用移动网络的功耗。而使用移动网络传输数据,电量的消耗有以下3种状态:
- 「Full power:高功率状态,移动网络连接被激活,允许设备以最大的传输速率进行操作」。
- 「Low power:低功耗状态,对电量的消耗差不多是 Full power 状态下的 50%」。
- 「Standby:空闲态,没有数据连接需要传输,电量消耗最少」。
因此,为了避免网络连接所带来的电量消耗,我们可以采用如下几种方案:
- 1)、尽量在 WIFI 环境下进行数据传输,在使用 WIFI 传输数据时,应该尽可能增大每个包的大小(不超过 MTU),并降低发包的频率。
- 2)、在蜂窝移动网络下需要对请求时机及次数控制:可以延迟执行的网络请求稍后一起发送,最好做到批量执行,尽量避免频繁的间隔网络请求,以尽量多地保持在 Radio Standby 状态。
- 3)、使用 JSON 和 Protobuf 进行数据压缩,减少时间。
- 4)、禁止使用轮询功能:轮询会导致网络请求一直处于被激活的状态,耗电过高。
5、定位相关
- 1)、「根据场景谨慎选择定位模式:对定位准确度没那么高的场景可以选择低精度模式」。
- 2)、「可以考虑网络定位代替 GPS」。
- 3)、「使用后务必及时关闭,减少更新频率,例如定位开启一定时间后超过某个阈值可以执行一个兜底策略:强制关闭 GPS」。
6、界面相关
- 1)、「离开界面后停止相关活动,例如关闭动画」。
- 2)、「耗电操作判断前后台,如果是后台则不执行相关操作」。
7、WakeLock 相关
WakeLock 常用于后台播放音视频、录制音视频、下载文件的情况。如果没有合理使用 WakeLock,则会造成严重的耗电问题,为了避免该问题,「我们应该定期针对使用了 WakeLock 的模块进行重点排查」。
我们可以使用 adb shell dumpsys power 命令查看系统当前的耗电信息,其中我们可以看到 WakeLock 列表,它通常会以「”mLocks.size“ 或者 ”Wake Locks:size“」 开头。关于 WakeLock 的使用我们要着重注意以下几点:
- 1)、「注意成对使用 acquire、release」。
- 2)、「建议使用带参数的 acquire,避免没有及时释放而导致电量消耗过大」。
- 3)、「使用 finally 确保 release 一定会被调用」。
- 4)、「常亮场景使用 keepScreenOn 即可」。
- 5)、「WakeLock 有一个接口 setReferenceCounted,用来设置 WakeLock 的技术机制,官方默认为计数。true 为计数,false 为不计数。所谓计数即每一个 acquire 必须对应一个 release;不计数则是无论有多少个 acquire,一个 release 就可以释放。但是问题是有的第三方 ROM 它将默认设置为了不计数,以为我们需要在调用 newWakeLock 之后再调用 setReferenceCounted 为 false」。
8、计算优化
「浮点运算比整数运算更消耗 CPU 时间片,因此耗电也会增加」。避开浮点运算的优化方法如下所示:
- 1)、「除法变乘法」。
- 2)、「充分利用移位」。
- 3)、「在 native 层开发时,可以利用 ARM neon 指令集做并行运算,注意需要 ARM V7 及以上架构 CPU 才能支持」。
9、灭屏时停止动画
「我们可以监听灭屏以及亮屏的广播,在灭屏的时候停止 surfaceView 的动画绘制。在亮屏的时候,恢复动画的绘制」。
五、耗电监控
===============================================================
以后台耗电监控为主,必须监控的模块有:
- 1)、「Alarm wakeup」
- 2)、「WakeLock」
- 3)、「WiFi scans」
- 4)、「Network」
「必须监控的现场信息有」 :
- 1)、「堆栈信息」
- 2)、「是否充电」
- 3)、「电量水平」
- 4)、「应用前后台时间」
- 5)、「CPU 状态信息」
最后,我们需要 「提炼规则,将监控内容 => 抽象成规则」。
1、Java Hook
我们可以通过代理对应的 Service 实现,完成收集 Wakelock、Alarm、GPS 的申请堆栈、释放信息、手机充电状态等等。
❝
示例项目
❞
2、电量辅助监控实战
1)、获取运行时能耗文件
- 1)、adb pull /system/framework/framework-res.apk
- 2)、反编译,xml—》power_profile
2)、电量辅助监控
线下使用 epic 进行 AOP 电量辅助统计
这里我们就以 WakeLock 的监控为例,切面代码如下所示:
public static long sStartTime = 0;
@Insert(value = “acquire”)
@TargetClass(value = “com.optimize.performance.wakelock.WakeLockUtils”,scope = Scope.SELF)
public static void acquire(Context context){
trace = Log.getStackTraceString(new Throwable());
sStartTime = System.currentTimeMillis();
Origin.callVoid();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
WakeLockUtils.release();
}
},1000);
}
@Insert(value = “release”)
@TargetClass(value = “com.optimize.performance.wakelock.WakeLockUtils”,scope = Scope.SELF)
public static void release(){
LogUtils.i(“PowerManager “+(System.currentTimeMillis() - sStartTime)+”/n”+trace);

此外,我们也可以利用 epic 来监控每个线程的执行时间,超过阈值则警告,示例代码如下所示:
public static long runTime = 0;
@Insert(value = “run”)
@TargetClass(value = “java.lang.Runnable”,scope = Scope.ALL)
public void run(){
runTime = System.currentTimeMillis();
Origin.callVoid();
LogUtils.i("runTime "+(System.currentTimeMillis() - runTime));
}
3、编译插桩
「写一个基础类,然后在统一的调用接口中添加监控逻辑」。这里我们可以参考 Facebook Battery-Metrics 获取、监控数据的方式。其代码如下所示:
public class WakelockMetrics {
/
- 获取 WakeLock
- @param wakeLock WakeLock
- @param timeout 超时时间
*/
public static void acquire(PowerManager.WakeLock wakeLock, long timeout) {
wakeLock.acquire(timeout);
// 监控 wakelock 相关信息
Log.e(“HOOOOOOOOK”, “–acquireWakeLock–”);
Log.e(“HOOOOOOOOK”, Utils.getStackTrace());
// 使用 Battery-Metrics 库统计其它维度的电量信息
}
/
- 释放 WakeLock
- @param wakeLock WakeLock
*/
public static void release(PowerManager.WakeLock wakeLock) {
wakeLock.release();
Log.e(“HOOOOOOOOK”, “–releaseWakeLock–”);
Log.e(“HOOOOOOOOK”, Utils.getStackTrace());
// 使用 Battery-Metrics 库统计其它维度的电量信息
}
}
Gradle 耗电量统计插件中 BatteryCreateMethodVisitor 的核心实现代码如下所示:
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
// 监控 Wakelock
String monitorClass = “com/ss/android/ugc/bytex/example/battery_monitor/WakelockMetrics”;
if (!monitorClass.equals(className)
&& “android/os/PowerManager$WakeLock”.equals(owner)
&& opcode == Opcodes.INVOKEVIRTUAL
&& “acquire”.equals(name)) {
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
monitorClass,
name,
“(Landroid/os/PowerManager$WakeLock;J)V”,
isInterface
);
return;
}
if (!monitorClass.equals(className)
&& “android/os/PowerManager$WakeLock”.equals(owner)
&& opcode == Opcodes.INVOKEVIRTUAL
&& “release”.equals(name)) {
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
monitorClass,
name,
“(Landroid/os/PowerManager$WakeLock;)V”,
isInterface
);
return;
}
// 监控 Gps
monitorClass = “com/pingan/bank/customplugin/GpsMetrics”;
if (!monitorClass.equals(className)
&& “android/location/LocationManager”.equals(owner)
&& opcode == Opcodes.INVOKEVIRTUAL
&& “requestLocationUpdates”.equals(name)) {
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
monitorClass,
name,
“(Landroid/location/LocationManager;Ljava/lang/String;JFLandroid/location/LocationListener;)V”,
isInterface
);
return;
}
if (!monitorClass.equals(className)
&& “android/location/LocationManager”.equals(owner)
&& opcode == Opcodes.INVOKEVIRTUAL
&& “removeUpdates”.equals(name)) {
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
monitorClass,
name,
“(Landroid/location/LocationManager;Landroid/location/LocationListener;)V”,
isInterface
);
return;
}
// 监控 Alarm Service
monitorClass = “com/pingan/bank/customplugin/AlarmMetrics”;
if (!monitorClass.equals(className)
&& “android/app/AlarmManager”.equals(owner)
&& opcode == Opcodes.INVOKEVIRTUAL
&& “set”.equals(name)) {
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
monitorClass,
name,
“(Landroid/app/AlarmManager;IJLandroid/app/PendingIntent;)V”,
isInterface
);
return;
}
if (!monitorClass.equals(className)
&& “android/app/AlarmManager”.equals(owner)
&& opcode == Opcodes.INVOKEVIRTUAL
&& “cancel”.equals(name)) {
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
monitorClass,
name,
“(Landroid/app/AlarmManager;Landroid/app/PendingIntent;)V”,
isInterface
);
return;
}
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
缺点
系统的代码插桩方案无法替换。
六、电量优化常见问题
===================================================================
1、怎么做电量测试?
电量相关的测试相对来说难度较大,因为 App 在具体手机上的耗电量无法准确统计,每一个手机所使用的硬件不一样,那么它相应的功耗就不一样。而且这个功耗值我们只能在线下通过导出手机的 power_profile.xml 文件拿到。
由于我们无法获取准确的耗电量,所以我们只能增加多个维度来辅助判断 App 是否耗电。
最后,我们可以分场景各个突破。
关于电量测试,我们可以针对各个功能场景进行针对性的专项测试。操作一段时间后,我们可以在手机设置—电量消耗里面,利用其数据作为判断依据。这样虽然直观,但精确度不行。
介绍 Battery Historian:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。





既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
学习交流
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。*
[外链图片转存中…(img-0UnNx5fI-38)]
[外链图片转存中…(img-MM1RdWph-38)]
[外链图片转存中…(img-8y2g9Rtf-38)]
[外链图片转存中…(img-sbd30IUb-38)]
[外链图片转存中…(img-UqGBvO2f-39)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
学习交流
[外链图片转存中…(img-KpIZEl1J-39)]
[外链图片转存中…(img-OxBhiPV1-39)]
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。



版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/46543.html