大致章节如下:
(1)为什么会耗电?
(2)学会使用电量分析工具Battery Historian
(3)低耗电模式(Doze)和应用待机模式(App Standby)
(4)监控电池电量和充电状态
(5)确定和监控插接状态和基座类型
一、为什么会耗电?
电量的消耗是一种必然现象,主要是由于移动设备的硬件在工作导致耗电;
硬件的过度工作,导致耗电增加,这种情况属于不正常现象,所以需要电量优化;
耗电比较严重的硬件主要有:CPU、wakelock、数据传输(流量和wifi)、wifi运行、gps、other senior等。
二、学会使用电量分析工具Battery Historian
Battery Historian 是谷歌推出的系统电量分析工具,仅支持5.0(API21)及以上系统的电量分析。
Battery Historian 对应的Github地址是:
https://github.com/google/battery-historian
在线电量分析工具地址是:https://bathist.ef.lc/,可以将bugreport
文件上传到在线网站上,从而进行数据分析。但是,它的响应速度太慢,甚至无法传递数据,估计是需要翻墙才可以正常访问吧。
Battery Historian可以配置本地环境,在Battery Historian的github官网上可以找到本地环境配置方法,步骤如下:
【1】
安装Docker(https://docs.docker.com/engine/install/)
需要注意的是:
现在官网提供的最新版本的Docker只支持win10以上的操作系统或者mac系统,作者本人特地将win7升级到win11系统;
如果Docker启动失败,报`WSL 2 installation is incomplete.`错误,那么需要安装`wsl_update_x64.msi`才可以,下载地址是:
https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi
Docker安装完之后,启动即可。
如果Docker中没有正在执行的镜像,那么主页面会提示没有容器在运行,如图:
image.pngdocker run -d -p:运行镜像的命令
80:端口号
docker/getting-started:镜像名称
可以在命令提示符或者git bash中执行命令(需要保证Docker已启动):
docker run -d -p 80:80 docker/getting-started
如果本地没有docker/getting-started
镜像,那么直接重远程下载:
最终成功启动镜像:
image.png输入命令:
docker ps -a
可以看到当前正在执行的镜像:
image.png打开浏览器,输入:
http://localhost:80
即可打开docker/getting-started镜像的主页面。
【2】
安装电池分析相关镜像
github上提供了电量分析相关的镜像:gcr.io/android-battery-historian/stable:3.0
输入以下命令下载并启动镜像(为了防止端口冲突,就不要设置80或者8080端口了):
docker run -d -p 9999:9999 gcr.io/android-battery-historian/stable:3.0 --port 9999
结果超时了:
image.png之所以超时,可能是防火墙的原因,或者需要翻墙。
如果您真的超时了,也无需烦恼,只需要使用Docker自带的搜索命令,输入命令:
docker search battery
即,搜索关键字"battery"找到所有与battery相关的镜像,如图:
image.pngNAME为镜像名称,经过一波尝试,最终确认,最好用的镜像是:runcare/battery-historian
,因为他可以做到秒开。
接下来就用runcare/battery-historian
镜像来演示。
输入命令下载并启动镜像:
docker run -d -p 9999:9999 runcare/battery-historian --port 9999
等待下载完成,runcare/battery-historian
镜像下载完成之后,会自动启动,如果已经启动,请先关闭。
接下来,点击下图的RUN按钮:
启动之前,需要配置下端口号:
image.pngLocal Host对应的是localhost后面的端口号;
Container Port是容器的端口号;
需要注意的是, Local Host和Container Port不能相同,否则上传电池信息的Submit
按钮无法显示。
在浏览器上输入(localhost后面的8888就是启动镜像之前配置的端口号):
http://localhost:8888/
打开后的页面如下:
image.png【3】
导出电池信息日志
重置电量信息:adb shell dumpsys batterystats --reset
获取完整的wakelock信息:adb shell dumpsys batterystats --enable full-wake-history
拔掉USB,让设备不处于充电状态,等待一段时间....
获得报告:
>7.0
adb bugreport bugreport.zip
<=6.0
adb bugreport > bugreport.txt
【4】
上传bugreport文件并分析
在浏览器上输入:
http://localhost:8888/
点击“Browse”按钮,选择bugreport文件,之后页面的右侧会出现一个提交按钮
image.png点击提交:
image.png那么,怎么分析这个数据呢?
主要从两个维度上来分析:
(1)查看耗电应用排行
点击左侧的“Tables - Device's Power Estimates”,
image.png耗电排名第一的非系统应用为高德地图,这个数据理所当然,因为这段时间,我只打开了高德地图应用。
(2)查看具体应用的耗电情况
找到左侧的”App Selection“,选择对应耗电应用的包名:
image.png查看耗电信息:
image.png
三、低耗电模式(Doze)和应用待机模式(App Standby)
为了延长电池寿命,谷歌在Android 6.0(API 23)引入了低耗电模式(Doze)和应用待机模式(App Standby)。
低电耗模式:如果用户未插接设备的电源,在屏幕关闭的情况下,让设备在一段时间内保持不活动状态,那么设备就会进入低电耗模式。
在低电耗模式下,系统会尝试通过限制应用访问占用大量网络和 CPU 资源的服务来节省电量。
它还会阻止应用访问网络,并延迟其作业、同步和标准闹钟。
系统会定期退出低电耗模式一小段时间,让应用完成其延迟的活动。
在此维护期内,系统会运行所有待处理的同步、作业和闹钟,并允许应用访问网络。
在每个维护期结束时,系统会再次进入低电耗模式,暂停网络访问并推迟作业、同步和闹钟。
随着时间的推移,系统安排维护期的次数越来越少,这有助于在设备未连接至充电器的情况下长期处于不活动状态时降低耗电量。
一旦用户通过移动设备、打开屏幕或连接至充电器唤醒设备,系统就会立即退出低电耗模式,并且所有应用都会恢复正常活动。
在低耗电模式下,您的应用会收到如下限制:
(1)暂停访问网络;
(2)系统忽略唤醒锁定(PowerManager.WakeLock);
(3)标准 AlarmManager 的 setExact() 和 setWindow() 推迟到下个维护期;
如果需要在低耗电模式下触发闹钟使用 `setAndAllowWhileIdle()` 和 `setExactAndAllowWhileIdle()` 即可;
使用 `setAlarmClock()` 设置的闹钟将继续正常触发,系统会在这些闹钟触发之前不久退出低耗电模式;
(4)系统不执行WLAN扫描;
(5)系统不允许运行同步适配器;
(6)系统不允许运行JobScheduler;
低耗电模式下,您的应用可能存在的影响?
低电耗模式可能会对应用产生不同的影响,具体取决于应用提供的功能和使用的服务。
许多应用无需修改即可在低电耗模式周期内正常运行。
在某些情况下,您必须优化应用管理网络、闹钟、作业和同步的方式。
应用应该能够在每个维护期内高效地管理活动。
低电耗模式尤其可能会影响 `AlarmManager` 闹钟和定时器管理的活动,因为当系统处于低电耗模式时,不会触发 Android 5.1(API 级别 22)或更低版本中的闹钟。
为了帮助安排闹钟,Android 6.0(API 级别 23)引入了两种新的 `AlarmManager` 方法:`setAndAllowWhileIdle()` 和 `setExactAndAllowWhileIdle()`。
通过这些方法,您可以设置即使设备处于低电耗模式也会触发的闹钟。
低电耗模式对网络访问的限制也有可能影响应用,特别是当应用依赖于操作消息或通知等实时消息时更是如此。
系统提供白名单,可以部分免除低耗电模式和应用待机模式给应用带来的影响?
通过妥善管理网络连接、闹钟、作业和同步,几乎所有应用都应该能够支持低电耗模式。
系统提供了一个可配置的白名单,将部分免除低电耗模式和应用待机模式优化的应用列入其中。
在低电耗模式和应用待机模式期间,列入白名单的应用可以使用网络并保留部分唤醒锁定。不过,列入白名单的应用仍会受到其他限制,就像其他应用一样。
例如,列入白名单的应用的作业和同步会延迟(在搭载 API 级别 23 及更低级别的设备上),并且其常规 `AlarmManager` 闹钟不会触发。
应用可以调用 `isIgnoringBatteryOptimizations()` 来检查它当前是否在豁免白名单中。
用户可以依次转到`设置 > 电池 > 电池优化`来手动配置该白名单。另外,系统也提供了一些方法,让应用要求用户将其列入白名单。
`设置 > 电池 > 电池优化`只是部分手机的路径,但是有些手机,电池优化的路径并未暴露出来,可以通过以下代码直接跳转到`电池优化`界面。
Intent intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
startActivity(intent);
类似这样的界面:
image.png image.pngAndroid 6.0 之后,PowerManager 新增了isIgnoringBatteryOptimizations方法,来确定应用是电池优化的情况。
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 是否忽视电池优化
boolean isIgnoringBatteryOptimizations = powerManager.isIgnoringBatteryOptimizations(getPackageName());
}
如果应用的电池优化选择了`不优化`,那么`isIgnoringBatteryOptimizations`的值为true,
此时就相当于已经加入了白名单,电池电量肯能会更快耗尽,系统将不再限制应用在后台使用电量;
具有 `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` 权限的应用可以触发一个系统对话框,让用户直接将该应用添加到白名单,而无需转到“设置”。
此类应用将通过触发 `ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` Intent 来触发该对话框。
代码如下:
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 是否忽视电池优化
boolean isIgnoringBatteryOptimizations = powerManager.isIgnoringBatteryOptimizations(getPackageName());
if (!isIgnoringBatteryOptimizations) {
Intent intent = new Intent(ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
startActivity(intent);
}
}
`ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS`的目的是为了弹出系统对话框,但是,部分手机并没有这个对话框,可以使用try...catch来保护。
另外,用户可以根据需要从白名单中手动移除应用。
如何在低电耗模式下进行测试?
为确保用户获得良好的体验,您应在低电耗模式和应用待机模式下全面测试您的应用。
您可以按以下步骤在低电耗模式下测试您的应用:
(1)使用 Android 6.0(API 级别 23)或更高版本的系统映像配置硬件设备或虚拟设备。
(2)将设备连接到开发计算机并安装您的应用。
(3)运行您的应用并使其保持活动状态。
(4)运行以下命令,强制系统进入闲置模式:
adb shell dumpsys deviceidle force-idle
(5)准备就绪后,运行以下命令,使系统退出闲置模式:
adb shell dumpsys deviceidle unforce
(6)执行以下命令,重新激活设备:
adb shell dumpsys battery reset
(7)在重新激活设备后观察应用的行为。确保应用在设备退出低电耗模式时正常恢复。
如何在应用待机模式下进行测试?
如需在应用待机模式下测试您的应用,请执行以下操作:
(1)使用 Android 6.0(API 级别 23)或更高版本的系统映像配置硬件设备或虚拟设备。
(2)将设备连接到开发计算机并安装您的应用。
(3)运行您的应用并使其保持活动状态。
(4)运行以下命令,强制应用进入应用待机模式:
adb shell dumpsys battery unplug
adb shell am set-inactive <packageName> true
(5)使用以下命令模拟唤醒您的应用:
adb shell am set-inactive <packageName> false
adb shell am get-inactive <packageName>
(6)在唤醒应用后观察它的行为。确保应用从待机模式正常恢复。您应特别检查应用的通知和后台作业是否继续按预期运行。
四、监控电池电量和充电状态
如果您要通过改变后台更新的频率降低这些更新对电池续航时间的影响,最好先从检查当前电池电量和充电状态入手。
执行应用更新对电池续航时间的影响取决于设备的电池电量和充电状态。设备通过交流电源充电时执行更新的影响可以忽略不计,因此在大多数情况下,
只要设备连接到壁式充电器,您就可以最大限度地提高更新频率。相反,如果设备正在放电,降低更新频率有助于延长电池续航时间。
同理,您也可以检查电池充电电量,并在电池电量近乎耗尽时降低更新频率,甚至停止更新。
如何确认当前充电状态?
首先,确定当前充电状态。`BatteryManager` 会在一个包含充电状态的粘性 `Intent` 中广播所有电池和充电详情。
由于它是一个粘性 Intent,因此您并不需要如下一代码段中所示的那样通过简单地调用 `registerReceiver`
传入 `null` 作为接收器来注册 `BroadcastReceive`,便可返回当前电池状态 Intent。
您可以在此处传入实际 `BroadcastReceive` 对象,但由于稍后我们将会处理更新,因此并不需要这样做。
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);
获取电池的充电状态?
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
电池的充电状态有:
BatteryManager.BATTERY_STATUS_UNKNOWN:没有安装电池
BatteryManager.BATTERY_STATUS_CHARGING:正在充电
BatteryManager.BATTERY_STATUS_DISCHARGING:放电中
BatteryManager.BATTERY_STATUS_NOT_CHARGING:未充电
BatteryManager.BATTERY_STATUS_FULL:已经充满(电量100%不代表一定是BATTERY_STATUS_FULL状态,如果电量100%并且正在充电才是BATTERY_STATUS_FULL状态)
// 是否正在充电
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;
您可以提取当前充电状态,并且如果设备正在充电,则还可以提取设备是通过 USB 还是交流充电器进行充电。
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
电池的电源类型有:
BatteryManager.BATTERY_PLUGGED_USB:电源为USB端口
BatteryManager.BATTERY_PLUGGED_AC:电源为交流充电器
BatteryManager.BATTERY_PLUGGED_WIRELESS:电源为无线
// 电源是否是USB端口
boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
// 电源是否是交流充电器
boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;
// 电源是否是无线的
boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS;
您可以获取电池的健康状态
// 电池的健康状况
int chargeHealth = batteryStatus.getIntExtra(BatteryManager.EXTRA_HEALTH, -1);
电池的健康状况有:
BatteryManager.BATTERY_HEALTH_UNKNOWN:未知
BatteryManager.BATTERY_HEALTH_GOOD:良好
BatteryManager.BATTERY_HEALTH_OVERHEAT:过热
BatteryManager.BATTERY_HEALTH_DEAD:没电
BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:过电压
BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:未知故障
BatteryManager.BATTERY_HEALTH_COLD:冷却
// 是否为Good状态
boolean chargeGood = chargeHealth == BatteryManager.BATTERY_HEALTH_GOOD;
如何监控充电状态变化?
就像设备可以轻松地插入电源,充电状态也很容易发生变化,因此必须监控充电状态的变化并相应地改变刷新频率。
每当设备连接或断开电源时,`BatteryManager` 都会广播一项操作。请务必接收这些事件,即便您的应用并未运行,
尤其要考虑到这些事件可能会影响您启动应用以便发起后台更新的频率,因此您应在清单中注册一个 `BroadcastReceiver`,
通过在一个 Intent 过滤器内定义 `ACTION_POWER_CONNECTED` 和 `ACTION_POWER_DISCONNECTED` 来同时监听这两种事件。
代码如下:
<receiver android:name=".PowerConnectionReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
</intent-filter>
</receiver>
public class PowerConnectionReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;
if (isCharging) {
Log.d("TAG", "正在充电...");
} else {
Log.d("TAG", "放电中...");
}
}
}
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
this.registerReceiver(new PowerConnectionReceiver(), ifilter);
当充电器插拔时,充电状态改变,PowerConnectionReceiver
广播总是被触发。
如何确定当前电池电量?
在某些情况下,确定当前电池电量也很有用处。您可以选择在电池电量低于某一水平时降低后台更新的频率。
您可以通过从电池状态 intent 提取当前电池电量和刻度来了解当前电池电量,如下所示:
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
float batteryPct = level * 100 / (float)scale;
如何监控电池电量变化?
您无法轻松地持续监控电池状态,您也不必如此。
一般而言,持续监控电池电量对电池的影响大于应用正常行为造成的影响,因此最好只监控显著的电池电量变化,特别是在设备进入或退出电量不足状态时。
Android给我们提供了两个给力的Actiton,分别是:BATTERY_LOW 和 BATTERY_OKAY
BATTERY_LOW:设备进入电量不足状态
BATTERY_OKAY:设备退出电量不足状态
<receiver android:name=".BatteryLevelReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_LOW"/>
<action android:name="android.intent.action.BATTERY_OKAY"/>
</intent-filter>
</receiver>
一般而言,建议您在电量极低时停用所有后台更新。如果手机自行关机,您就无法利用相关数据,数据的新鲜度也就无关紧要。
五、确定和监控插接状态和基座类型
Android 设备可插接到几个不同种类的基座,其中包括车载或家用基座以及数字和模拟基座。
插接状态通常与充电状态联系密切,因为许多基座都为插接的设备供电。
手机插接状态对更新频率的影响取决于您的应用。
您可以选择在手机插入桌面基座时提高体育中心应用的更新频率,或者在设备插入车载基座时完全停用更新。
相反,如果您的后台服务正在更新路况信息,则您可以选择在已插接车载基座的情况下最大限度地提高更新频率。
插接状态也会以粘性 `Intent` 形式广播,以便您查询设备是否已插接以及已插接情况下的基座类型。
如何确定当前插接状态?
插接状态详情以 extra 形式包含在 `ACTION_DOCK_EVENT` 操作的粘性广播中。由于它是粘性广播,因此您只需调用 `registerReceiver()`,将 `null` 作为广播接收器传入。
以下代码段展示了如何完成此流程:
IntentFilter ifilter = new IntentFilter(Intent.ACTION_DOCK_EVENT);
Intent dockStatus = context.registerReceiver(null, ifilter);
您可以从 EXTRA_DOCK_STATE extra 中提取当前插接状态:
int dockState = dockStatus.getIntExtra(EXTRA_DOCK_STATE, -1);
boolean isDocked = dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
基座类型有:
Intent.EXTRA_DOCK_STATE_UNDOCKED:无基座
Intent.EXTRA_DOCK_STATE_DESK:桌面基座
Intent.EXTRA_DOCK_STATE_CAR:车载基座
Intent.EXTRA_DOCK_STATE_LE_DESK:低端(模拟)桌面基座
Intent.EXTRA_DOCK_STATE_HE_DESK:高端(数字)桌面基座
请注意,后两种类型从 Android 的 API 级别 11 才开始引入,因此如果您只关注基座类型而不关心其具体为数字还是模拟形式,则最好检查所有三种类型:
boolean isCar = dockState == EXTRA_DOCK_STATE_CAR;
boolean isDesk = dockState == EXTRA_DOCK_STATE_DESK ||
dockState == EXTRA_DOCK_STATE_LE_DESK ||
dockState == EXTRA_DOCK_STATE_HE_DESK;
[本章完...]
网友评论