文章大纲
- 引言
- 一、在低电耗模式和应用待机模式下进行测试
- 1、在低电耗模式下测试您的应用
- 2、在应用待机模式下测试您的应用
- 3、列入白名单的可接受用例
- 4、确定当前充电状态
- 5、监控充电状态变化
- 6、确定当前电池电量
- 7、监控显著的电池电量变化
- 二、Wakelock 机制
- 1、WakeLock分类
- 2、申请WakeLock锁
- 3、获取WakeLock对象
- 4、Wake Lock 申请流程
- 4.1、setWakeLockDisabledStateLocked()
- 4.2、applyWakeLockFlagsOnAcquireLocked()处理亮屏标记
- 4.3、updatePowerStateLocked()更新全局状态
- 4.4、updateSuspendBlockerLocked()更新SuspendBlocker
- 4.5、SuspendBlocker#acquire()申请SuspendBlocker
- 5、WakeLock的释放
- 6、小结
- 三、AlarmManager
- 四、采集电量相关日志报告
- 1、通过`adb dumpsys batterystats --reset`命令重置
- 2、再执行`adb dumpsys batterystats --enable full-wake-history` 打开完整唤醒日志
- 3、断开设备与电脑的连接,以便只从设备的电池中消耗电流,模拟场景并采集数据
- 4、不充电状态下获取完整的bugreport信息
- 五、Battery Historian
- 1、Battery Historian 常见条目解析
- 2、Battery Historian 主要功能介绍
- 2.1、Add Metrics
- 2.2、System Stats
- 2.3、App Stats
引言
电池续航时间是移动用户体验中最重要的一个方面。没电的设备完全无法使用。因此,对于应用来说,尽可能地考虑电池续航时间是至关重要的。为使应用保持节能,前一篇Android 进阶——性能优化之电量优化全攻略及实战小结(一)对于一些概念进行了总结。
一、在低电耗模式和应用待机模式下进行测试
为确保用户获得良好的体验,您应在低电耗模式和应用待机模式下全面测试您的应用。
1、在低电耗模式下测试您的应用
您可以按以下步骤在低电耗模式下测试您的应用:
-
使用 Android 6.0(API 级别 23)或更高版本的系统映像配置硬件设备或虚拟设备。
-
将设备连接到开发计算机并安装您的应用。
-
运行您的应用并使其保持活动状态。
-
运行以下命令,强制系统进入闲置模式:
$ adb shell dumpsys deviceidle force-idle
-
准备就绪后,运行以下命令,使系统退出闲置模式:
$ adb shell dumpsys deviceidle unforce
-
执行以下命令,重新激活设备:
$ adb shell dumpsys battery reset
-
在重新激活设备后观察应用的行为。确保应用在设备退出低电耗模式时正常恢复。
2、在应用待机模式下测试您的应用
如需在应用待机模式下测试您的应用,请执行以下操作:
-
使用 Android 6.0(API 级别 23)或更高版本的系统映像配置硬件设备或虚拟设备。
-
将设备连接到开发计算机并安装您的应用。
-
运行您的应用并使其保持活动状态。
-
运行以下命令,强制应用进入应用待机模式:
$ adb shell dumpsys battery unplug $ adb shell am set-inactive <packageName> true
-
使用以下命令模拟唤醒您的应用:
$ adb shell am set-inactive <packageName> false $ adb shell am get-inactive <packageName>
-
在唤醒应用后观察它的行为。确保应用从待机模式正常恢复。您应特别检查应用的通知和后台作业是否继续按预期运行。
3、列入白名单的可接受用例
下表重点介绍了请求将应用列入电池优化豁免白名单或应用目前在该白名单中的可接受用例。一般来说,除非低电耗模式或应用待机模式破坏了应用的核心功能,或者由于技术方面的原因而导致您的应用无法使用高优先级 FCM 消息,否则您的应用不应在白名单中。
4、确定当前充电状态
首先,确定当前充电状态。BatteryManager
会在一个包含充电状态的粘性 Intent
中广播所有电池和充电详情。
由于它是一个粘性 Intent,因此您并不需要如下一代码段中所示的那样通过简单地调用 registerReceiver
传入 null
作为接收器来注册 BroadcastReceiver
,便可返回当前电池状态 Intent。您可以在此处传入实际 BroadcastReceiver
对象,但由于稍后我们将会处理更新,因此并不需要这样做。
KotlinJava
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);
您可以提取当前充电状态,并且如果设备正在充电,则还可以提取设备是通过 USB 还是交流充电器进行充电。
KotlinJava
// Are we charging / charged?
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
// How are we charging?
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;
通常,如果设备连接到交流充电器,您应最大限度地提高后台更新的频率;如果设备是通过 USB 充电,则应降低更新频率;如果电池正在放电,则应进一步降低更新频率。
5、监控充电状态变化
就像设备可以轻松地插入电源,充电状态也很容易发生变化,因此必须监控充电状态的变化并相应地改变刷新频率。
每当设备连接或断开电源时,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>
6、确定当前电池电量
在某些情况下,确定当前电池电量也很有用处。您可以选择在电池电量低于某一水平时降低后台更新的频率。
您可以通过从电池状态 intent 提取当前电池电量和刻度来了解当前电池电量,如下所示:
KotlinJava
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
float batteryPct = level * 100 / (float)scale;
7、监控显著的电池电量变化
您无法轻松地持续监控电池状态,您也不必如此。
一般而言,持续监控电池电量对电池的影响大于应用正常行为造成的影响,因此最好只监控显著的电池电量变化 - 特别是在设备进入或退出电量不足状态时。
以下清单代码段摘自某个广播接收器内的 Intent 过滤器元素。通过监听 ACTION_BATTERY_LOW
和 ACTION_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>
一般而言,建议您在电量极低时停用所有后台更新。如果手机自行关机,您就无法利用相关数据,数据的新鲜度也就无关紧要。在许多情况下,为设备充电与将设备插入基座是同一操作。下一课将为您介绍如何确定当前基座状态以及如何监控设备插接状态的变化。
二、Wakelock 机制
Android为了确保应用程序中关键代码的正确执行,提供了Wake Lock(一种锁的机制, 只要有人拿着这个锁,系统就无法进入休眠)可以被用户态程序和内核获得。使得应用程序有权限通过代码阻止AP进入休眠状态。WakeLock是Android中为应用层及框架层提供的用来保证CPU处于唤醒状态的一种锁机制。PMS中为应用及框架层其他组件提供了接口,进行WakeLock的申请和释放。应用在申请WakeLock时,需要在清单文件中配置android.Manifest.permission.WAKE_LOCK
权限。如果没有锁了或者超时了, 内核就会启动休眠的那套机制来进入休眠。WakeLock阻止应用处理器(ApplicationProcessor)挂起,确保关键代码的运行,通过中断唤起应用处理器(ApplicationProcessor),可以阻止屏幕变暗。所有的WakeLock被释放后,系统会挂起。
1、WakeLock分类
根据作用时间,WakeLock可以分为永久锁和超时锁:
- 永久锁:只要获取了WakeLock锁,必须显式进行释放,否则系统会一直持有该锁;
- 超时锁:在到达给定时间后,自动释放WakeLock锁,其实现原理为方法内部维护了一个Handler进行。
根据释放原则,WakeLock可以分为计数锁和非计数锁:
- 计数锁:一次申请必须对应一次释放;
- 非计数锁:不管申请多少次,只需要一次就可以释放该WakeLock。
默认为计数锁。WakeLock机制从上到下架构如下:
WakeLock有三种表现形式:
- PowerManger.WakeLock:PMS暴露给应用层和其他组件用来申请WakeLock的接口;
- PowerManagerService.WakeLock: PowerManager.WakeLock在PMS中的表现形式;
- SuspendBlocker: PowerManagerService.WakeLock在向底层节点操作时的表现形式。
2、申请WakeLock锁
// 获取PowerManager对象
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
// 创建WakeLock锁实例
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
// 申请WakeLock
wl.acquire();
// 释放WakeLock
wl.release();
在PowerManager中,共定义了以下七种WakeLock:
WakeLock | 说明 |
---|---|
PARTIAL_WAKE_LOCK | 0x00000001,保证CPU处于唤醒状态,但屏幕和键盘灯有可能是关闭的。 |
PROXIMITY_SCREEN_OFF_WAKE_LOCK | 0x00000020,通过PSensor进行亮灭屏工作,PSensor检测到有物体靠近时关闭屏幕,远离时又亮屏 |
DOZE_WAKE_LOCK | 0x00000040,仅用于PMS唤醒状态为Doze时,进入Doze状态后,DreamMangerService会申请该锁,允许CPU挂起 |
DRAW_WAKE_LOCK | 0x00000080,仅用于PMS唤醒状态为Doze时,保证CPU处于运行状态,以进行Doze状态下屏幕的绘制,如AOD、防烧屏显示 |
0x0000001a,保证屏幕、键盘灯都保持常亮状态,按Power键灭屏后,会忽略该锁 | |
0x0000000a,保证屏幕一直保持常亮状态,按Power键灭屏后,会忽略该锁(不推荐) | |
0x00000006,保持CPU运转,保证屏幕一直保持Dim状态但有可能是灰的,允许关闭键盘灯,按Power键灭屏后,会忽略该锁(不推荐) |
还定义了三个Flag,可以在创建、申请及释放WakeLock时和以上几类搭配使用:
Flag | 说明 |
---|---|
ACQUIRE_CAUSES_WAKEUP | 不会唤醒设备,强制屏幕马上高亮显示,键盘灯开启。有一个例外,如果有notification弹出的话,会唤醒设备。,不能和PARTIAL_WAKE_LOCK一起使用 |
ON_AFTER_RELEASE | 在释放有该Flag的WakeLock时,会稍微延长自动休眠时间一小会儿,但不能和PARTIAL_WAKE_LOCK一起使用,WakeLock被释放后,维持屏幕亮度一小段时间,减少WakeLock循环时的闪烁情况。 |
PROXIMITY_SCREEN_OFF_WAKE_LOCK | 在释放PROXIMITY_SCREEN_OFF_WAKE_LOCK锁时,不会立即解除PSensor监听,而是在PSensor上报远离后,才会亮屏并解除Psensor监听,仅用于释放PROXIMITY_SCREEN_OFF_WAKE_LOCK锁 |
3、获取WakeLock对象
PowerManager中提供了接口newWakeLock()
来创建WakeLock对象:
// frameworks/base/core/java/android/os/PowerManager.java
public WakeLock newWakeLock(int levelAndFlags, String tag) {
validateWakeLockParameters(levelAndFlags, tag); // 校验Flag
// 创建WakeLock对象
return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName());
}
首先进行了参数的校验,然后调用WakeLock构造方法创建WakeLock对象:
// frameworks/base/core/java/android/os/PowerManager.java
WakeLock(int flags, String tag, String packageName) {
mFlags = flags; //表示wakelock类型
mTag = tag; //一个tag,一般为当前类名
mPackageName = packageName; //申请wakelock的应用包名
mToken = new Binder(); //一个Binder标记
mTraceName = "WakeLock (" + mTag + ")";
}
除以上几个属性之外,WakeLock中还有如下几个属性:
// frameworks/base/core/java/android/os/PowerManager.java
//表示内部计数
private int mInternalCount;
//表示外部计数
private int mExternalCount;
//表示是否是计数锁,默认true
private boolean mRefCounted = true;
//表示是否已经持有该锁
private boolean mHeld;
//表示和该wakelock相关联的工作源,这在一些服务获取wakelock时很有用,以便计算工作成本
private WorkSource mWorkSource;
//表示一个历史标签
private String mHistoryTag;
4、Wake Lock 申请流程
当创建好WakeLock对象以后,就可以申请WakeLock锁了。不管是永久锁还是超时锁,都是通过acquire()
方法来申请:
mWakeLock.acquire(); //申请一个永久锁
mWakeLock.acquire(int timeout); //申请一个超时锁,指定作用时间
复制代码
PowerManager#acquire()方法如下:
// frameworks/base/core/java/android/os/PowerManager.java
// 申请永久锁
public void acquire() {
synchronized (mToken) {
acquireLocked();
}
}
// 申请超时锁
public void acquire(long timeout) {
synchronized (mToken) {
acquireLocked();
//通过Handler设置一个延迟消息自动释放锁
mHandler.postDelayed(mReleaser, timeout);
}
}
复制代码
这两种申请方式完全一样,只不过如果是申请一个超时锁,会通过Handler发送一个延时消息,到达时间后去自动释放锁。继续看acquireLocked()
方法:
// frameworks/base/core/java/android/os/PowerManager.java
private void acquireLocked() {
// 计数器+1
mInternalCount++;
mExternalCount++;
//如果是非计数锁或者内部计数值为1,即第一次申请该锁,才会真正去申请
if (!mRefCounted || mInternalCount == 1) {
// 移除释放超时锁的Msg
mHandler.removeCallbacks(mReleaser);
try {
// 通过Binder进入PMS中
mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
mHistoryTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mHeld = true; // 表示已持有该锁,申请成功
}
}
复制代码
对同一个WakeLock每申请一次,属性值mInternalCount和mExternalCount都会+1,这两个值都用来表示引用计数,前者相对于PowerManager内部,后者则相对于用户操作,之所以有两个引用计数器,主要是为了针对超时锁的释放,如果一个超时锁在已自动释放的情况下,用户手动再释放一次,相当于释放两次。这种情况下由于mExternalCount的存在,就不会导致crash。
mRefCounted用来表示计数锁或非计数锁,默认为true(计数锁),可以通过setReferenceCount()来设置:
public void setReferenceCounted(boolean value) {
synchronized (mToken) {
mRefCounted = value;
}
}
复制代码
mHeld
表示是否已经持有锁,可以通过调用isHeld()
来判断是已申请WakeLock。
以上逻辑都是在APP进程执行的,接下来通过mService进入到system_server,开始执行了PMS中的流程。直接来看PMS#acquireWakeLockInternal()方法:
// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName,
WorkSource ws, String historyTag, int uid, int pid) {
synchronized (mLock) {
// 这是PMS中的WakeLock类
WakeLock wakeLock;
// 通过IBinder标记确认是否已申请该WakeLock
int index = findWakeLockIndexLocked(lock);
boolean notifyAcquire;
// 说明已申请过该WakeLock,则更新下该WakeLock即可
if (index >= 0) {
wakeLock = mWakeLocks.get(index);
if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) {
notifyWakeLockChangingLocked(wakeLock, flags, tag, packageName,
uid, pid, ws, historyTag);
wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid);
}
notifyAcquire = false;
} else { // 说明没有申请过该WakeLock
......
// 创建一个WakeLock
wakeLock = new WakeLock(lock, flags, tag, packageName, ws, historyTag, uid, pid,
state);
// .......
// 添加到保存系统所有WakeLock的list中
mWakeLocks.add(wakeLock);
// 对于PowerManager.PARTIAL_WAKE_LOCK类型锁,省电机制Doze模式会对其进行disable处理
setWakeLockDisabledStateLocked(wakeLock);
notifyAcquire = true;
}
// 处理PowerManager.ACQUIRE_CAUSES_WAKEUP标记,带有此标记,进行亮屏处理
applyWakeLockFlagsOnAcquireLocked(wakeLock, uid);
mDirty |= DIRTY_WAKE_LOCKS; // 设置DIRTY_WAKE_LOCKS标记位
updatePowerStateLocked(); // 更新状态
if (notifyAcquire) {
// 将申请WakeLock动作通知其他组件
notifyWakeLockAcquiredLocked(wakeLock);
}
}
}
复制代码
首先,通过传入的第一个参数IBinder进行查找WakeLock是否已经存在,若存在,则在原有的WakeLock上更新其属性值;若不存在,则创建一个WakeLock对象,同时将该WakeLock保存到List中,并将相关数据保存到UidState中。
4.1、setWakeLockDisabledStateLocked()
创建WakeLock实例后,接下来调用setWakeLockDisabledStateLocked()
方法,这个方法会对PARTIAL_WAKE_LOCK类型的WakeLock进行disable。系统如果持有该类型锁,会导致CPU一直保持唤醒状态而无法休眠,因此在省电策略DeviceIdle模块中,会在某些特定状态下将该类型的锁进行diable:
// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
private boolean setWakeLockDisabledStateLocked(WakeLock wakeLock) {
if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK)
== PowerManager.PARTIAL_WAKE_LOCK) {
boolean disabled = false;
final int appid = UserHandle.getAppId(wakeLock.mOwnerUid);
// 非系统进程
if (appid >= Process.FIRST_APPLICATION_UID) {
// Cached inactive processes are never allowed to hold wake locks.
if (mConstants.NO_CACHED_WAKE_LOCKS) {
// 强制进入suspend、对应uid进程没有处于active且进程adj大于PROCESS_STATE_RECEIVER
disabled = mForceSuspendActive // 强制进入suspend
|| (!wakeLock.mUidState.mActive && wakeLock.mUidState.mProcState
!= ActivityManager.PROCESS_STATE_NONEXISTENT &&
wakeLock.mUidState.mProcState > ActivityManager.PROCESS_STATE_RECEIVER);
}
if (mDeviceIdleMode) { //处于idle状态时,将非白名单应用wakeLock 禁用
final UidState state = wakeLock.mUidState;
if (Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0 &&
Arrays.binarySearch(mDeviceIdleTempWhitelist, appid) < 0 &&
state.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT &&
state.mProcState >
ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
disabled = true;
}
}
}
// 更新mDisabled属性
if (wakeLock.mDisabled != disabled) {
wakeLock.mDisabled = disabled;
return true;
}
}
return false;
}
复制代码
主要有三种情况下会禁用Partical WakeLock:
- 强制进入suspend;
- WakeLock所属进程不处于active状态,且进程adj大于PROCESS_STATE_RECEIVER;
- DeviceIdle处于IDLE状态,且所属进程不在doze白名单中;
4.2、applyWakeLockFlagsOnAcquireLocked()处理亮屏标记
接下来调用applyWakeLockFlagsOnAcquireLocked()
方法,对ACQUIRE_CAUSES_WAKEUP
标记进行处理。如果WakeLock带有标志,并且WakeLock类型为FULL_WAKE_LOCK、SCREEN_BRIGHT_WAKE_LOCK、SCREEN_DIM_WAKE_LOCK这三种其中之一,则会点亮屏幕:
// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
private void applyWakeLockFlagsOnAcquireLocked(WakeLock wakeLock, int uid) {
// 如果持有ACQUIRE_CAUSES_WAKEUP标记,且为亮屏相关三类锁之一
if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0
&& isScreenLock(wakeLock)) {
......
// 亮屏流程
wakeUpNoUpdateLocked(SystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_APPLICATION, wakeLock.mTag,
opUid, opPackageName, opUid);
}
}
复制代码
wakeUpNoUpdateLocked()
方法是点亮屏幕的主要方法,会在后面部分分析。
4.3、updatePowerStateLocked()更新全局状态
这个方法在PowerManagerService模块(一) 启动流程和核心方法中进行了分析,其中涉及到WakeLock流程的有两个方法:updateWakeLockSummaryLocked()和updateSuspendBlockerLocked()方法,前者已经分析过了,用来将所有的WakeLock统计到mWakeLockSummary全局变量中,这里对后一个方法进行分析。
4.4、updateSuspendBlockerLocked()更新SuspendBlocker
在这个方法中,将会根据系统所有WakeLock的状态,获得一个SuspendBlocker锁:
// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
private void updateSuspendBlockerLocked() {
// 是否因持有WakeLock锁而需要CPU保持唤醒
final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0);
// 是否因Display状态而需要CPU保持唤醒
final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked();
// 是否开启auto_suspend模式
final boolean autoSuspend = !needDisplaySuspendBlocker;
// 是否处于交互状态
final boolean interactive = mDisplayPowerRequest.isBrightOrDim();
// 如果持有Display SuspendBlocker,则关闭auto-suspend模式
if (!autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) {
setHalAutoSuspendModeLocked(false);
}
// 申请mWakeLockSuspendBlocker锁
if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) {
mWakeLockSuspendBlocker.acquire();
mHoldingWakeLockSuspendBlocker = true;
}
// 申请mDisplaySuspendBlocker锁
if (needDisplaySuspendBlocker && !mHoldingDisplaySuspendBlocker) {
mDisplaySuspendBlocker.acquire();
mHoldingDisplaySuspendBlocker = true;
}
// 设置交互状态
if (mDecoupleHalInteractiveModeFromDisplayConfig) {
if (interactive || mDisplayReady) {
setHalInteractiveModeLocked(interactive);
}
}
// 释放mWakeLockSuspendBlocker
if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) {
mWakeLockSuspendBlocker.release();
mHoldingWakeLockSuspendBlocker = false;
}
// 释放mDisplaySuspendBlocker
if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) {
mDisplaySuspendBlocker.release();
mHoldingDisplaySuspendBlocker = false;
}
// 开启auto_suspend模式
if (autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) {
setHalAutoSuspendModeLocked(true);
}
}
复制代码
在创建PMS实例时,创建了两个SuspendBlocker对象:mWakeLockSuspendBlocker和mDisplaySuspendBlocker,在这个方法中则体现出了他们的作用。当系统当前持有WakeLock锁,通过mWakeLockSuspendBlocker使CPU保持唤醒,当屏幕亮屏时,则通过mDisplaySuspendBlocker使CPU保持唤醒。
再看看needDisplaySuspendBlockerLocked()方法,如果该方法返回true,表示需要mDisplaySuspendBlocker锁:
private boolean needDisplaySuspendBlockerLocked() {
if (!mDisplayReady) {
return true;
}
// 当请求policy为bright或dim时,需要对psensor灭屏场景进行区分
if (mDisplayPowerRequest.isBrightOrDim()) {
// 如果在使用psensor且psensor上报靠近事件且psensor灭屏后允许CPU休眠,则表示不需要mDisplaySuspendBlocker
if (!mDisplayPowerRequest.useProximitySensor || !mProximityPositive
|| !mSuspendWhenScreenOffDueToProximityConfig) {
return true;
}
}
if (mScreenBrightnessBoostInProgress) {
return true;
}
return false;
}
复制代码
4.5、SuspendBlocker#acquire()申请SuspendBlocker
接下来将目光聚焦到SuspendBlocker上,它是一个接口,并且只有acquire()
和release()
两个方法,用来申请和释放SuspendBlocker。PMS.SuspendBlockerImpl实现了该接口,acquire()方法如下:
public void acquire() {
synchronized (this) {
// 引用计数+1
mReferenceCount += 1;
if (mReferenceCount == 1) {
// 进入native层
mNativeWrapper.nativeAcquireSuspendBlocker(mName);
}
}
}
复制代码
SuspendBlocker对象的申请操作也使用了引用计数方式,每申请一次,其引用计数+1,如果mReferenceCount > 1,不会进行锁的申请,只有当mReferenceCount为0时,才会真正执行申请锁操作,然后通过JNI进入了native层:
// frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp
static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) {
acquire_wake_lock(PARTIAL_WAKE_LOCK, name.c_str());
}
复制代码
最终,在SystemSuspend中,将会在/sys/power/wake_lock节点中,写入SuspendBlocker的name,完成申请过程:
// system/hardware/interfaces/suspend/1.0/default/SystemSuspend.cpp
void SystemSuspend::incSuspendCounter(const string& name) {
auto l = std::lock_guard(mCounterLock);
if (mUseSuspendCounter) {
mSuspendCounter++;
} else {
if (!WriteStringToFd(name, mWakeLockFd)) {
}
}
}
复制代码
这个name就是在创建SuspendBlocker对象时传入的String类型参数,可以通过adb shell看看这个节点内容:
$ adb shell cat /sys/power/wake_lock
PowerManager.SuspendLockout PowerManagerService.Display
复制代码
整个申请流程可以看出,WakeLock的申请和释放锁,对应着SuspendBlocker的申请和释放锁。也可以说SuspendBlocker类是WakeLock锁功能的具体实现者。只要持有SuspendBlocker锁,都会使得CPU处于唤醒状态。
5、WakeLock的释放
当申请了WakeLock锁(更准确地说是PARTIAL_WAKELOCK类型的WakeLock锁)执行完相应的任务后,要及时对其进行释放,否则会导致CPU无法进入休眠状态,导致电量消耗过快。在释放WakeLock时,也有两种释放形式:
- release():正常释放WakeLock;
- release(int flag):传入一个标志值,以达到修改释放行为的目的。目前只支持一个Flag——
RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY
,这个值前面已经说过,主要和PSensor灭屏后的动作有关。release()方法如下:
// frameworks/base/core/java/android/os/PowerManager.java
public void release(int flags) {
synchronized (mToken) {
if (mInternalCount > 0) {
// 计数-1
mInternalCount--;
}
// 如果非超时锁,外部计数-1
if ((flags & RELEASE_FLAG_TIMEOUT) == 0) {
mExternalCount--;
}
if (!mRefCounted || mInternalCount == 0) {
mHandler.removeCallbacks(mReleaser);
if (mHeld) {
try {
// 进入PMS中
mService.releaseWakeLock(mToken, flags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mHeld = false;
}
}
// 在没有申请锁时进行释放操作,直接抛出异常
if (mRefCounted && mExternalCount < 0) {
throw new RuntimeException("WakeLock under-locked " + mTag);
}
}
}
复制代码
对于计数锁的释放,每次都会对内部计数值mInternalCount减一,且当它减为0时,才会去执行PMS中的释放流程;对于非计数锁的释放,每次都会调用到PMS释放流程中。
进入PMS流程后,PMS#releaseWakeLock()在进行权限验证后,执行PMS#releaseWakeLockInternal()方法:
// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
private void releaseWakeLockInternal(IBinder lock, int flags) {
synchronized (mLock) {
//查找WakeLock是否存在,不存在直接返回
int index = findWakeLockIndexLocked(lock);
if (index < 0) {
return;
}
WakeLock wakeLock = mWakeLocks.get(index);
// 如果带有该Flag,它会在距离传感器上报远离事件后才会取消PSensor监听
if ((flags & PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY) != 0) {
//表示在点亮屏幕前需要等待PSensor返回远离值
mRequestWaitForNegativeProximity = true;
}
//释放锁
removeWakeLockLocked(wakeLock, index);
}
}
复制代码
这里对RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY标记进行了处理。这个标记和PSensor的行为有关。
当应用申请了PROXIMITY_SCREEN_OFF_WAKE_LOCK类型WakeLock锁,PMS模块会请求DMS模块注册一个PSensor监听,根据PSensor的事件状态来进行亮灭屏,通话时贴耳灭屏就是这样实现的。如果在PSensor灭屏的场景下,带这个flag去释放WakeLock,那么即使该WakeLock已经释放,依然会保持PSensor的监听状态,直到PSensor上报远离事件后,才会解除PSensor监听。
然后将执行removeWakeLockLocked()方法进行进一步操作:
// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
private void removeWakeLockLocked(WakeLock wakeLock, int index) {
mWakeLocks.remove(index); // 从保存系统WakeLock列表中移除该WakeLock
// 更新WakeLock所属的UidState中的相关wakeLock数据
......
// 对带有ON_AFTER_RELEASE标志的wakelock进行处理
applyWakeLockFlagsOnReleaseLocked(wakeLock);
mDirty |= DIRTY_WAKE_LOCKS; // 设置标记位
// 更新PMS全局状态
updatePowerStateLocked();
}
复制代码
首先,将该WakeLock从mWakeLocks列表中移除;
接下来,执行applyWakeLockFlagsOnReleaseLocked()方法,对带有ON_AFTER_RELEASE标志的WakeLock进行处理。在释放带有该标记的WakeLock时,会更新用户活动状态,从而当没有用户操作情况下,会延迟自动灭屏时间:
// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
private void applyWakeLockFlagsOnReleaseLocked(WakeLock wakeLock) {
if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0
&& isScreenLock(wakeLock)) {
// 更新用户活动时间,用于延长灭屏时间
userActivityNoUpdateLocked(SystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_OTHER,
PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
wakeLock.mOwnerUid);
}
}
复制代码
userActivityNoUpdateLocked()
方法会更新用户活动时间,该方法的详细分析见Android R PowerManagerService模块(4) 灭屏流程。
最后,执行updatePowerStateLocked()方法,该方法中和WakeLock相关的方法已经在申请流程中说过,这里只看看释放相关代码:
// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
private void updateSuspendBlockerLocked() {
......
// 释放mWakeLockSuspendBlocker
if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) {
mWakeLockSuspendBlocker.release();
mHoldingWakeLockSuspendBlocker = false;
}
// 释放mDisplaySuspendBlocker
if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) {
mDisplaySuspendBlocker.release();
mHoldingDisplaySuspendBlocker = false;
}
}
复制代码
和申请流程一样,还是对mWakeLockSuspendBlocker和mDisplaySuspendBlocker进行释放:
// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
public void release() {
synchronized (this) {
mReferenceCount -= 1; // 引用计数-1
if (mReferenceCount == 0) {
// 进入native层
mNativeWrapper.nativeReleaseSuspendBlocker(mName);
} else if (mReferenceCount < 0) {
// ......
}
}
}
复制代码
在释放时对引用计数-1,当mReferenceCount为0时,会进入native层进行最后的释放:
// hardware/libhardware_legacy/power.cpp
static void nativeReleaseSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) {
release_wake_lock(name.c_str());
}
复制代码
最终,也会在SystemSuspend中,在/sys/power/wake_unlock节点中写入SuspendBlocker的name,完成释放:
// system/hardware/interfaces/suspend/1.0/default/SystemSuspend.cpp
void SystemSuspend::decSuspendCounter(const string& name) {
auto l = std::lock_guard(mCounterLock);
if (mUseSuspendCounter) {
if (--mSuspendCounter == 0) {
mCounterCondVar.notify_one();
}
} else {
if (!WriteStringToFd(name, mWakeUnlockFd)) {
PLOG(ERROR) << "error writing " << name << " to " << kSysPowerWakeUnlock;
}
}
}
复制代码
至此,WakeLock释放流程执行完毕。
6、小结
WakeLock锁的申请和释放流程,实际上就是通过SuspendBlocker操作/sys/power/wake_lock和/sys/power/wake_unlock节点,来控制设备的唤醒和休眠。 SuspendBlocker官方的解释是:SuspendBlocker相当于持有部分唤醒锁,该接口在内部使用,以避免在高级别唤醒锁机制上引入内部依赖关系。
在PMS中,共创建了三个SuspendBlocker锁,其中两个已经见过了:
-
- mWakeLockSuspendBlocker锁:表示由WakeLock而使CPU保持唤醒状态,该锁的申请条件是
mWakeLockSummary & WAKE_LOCK_CPU) != 0
;
- mWakeLockSuspendBlocker锁:表示由WakeLock而使CPU保持唤醒状态,该锁的申请条件是
mWakeLockSummary汇总了所有WakeLock锁,当申请了PARTIAL_WAKE_LOCK、DRAW_WAKE_LOCK锁,或者屏幕处于唤醒、屏保时,会给该属性设置WAKE_LOCK_CPU标记位,从而申请mWakeLockSuspendBlocker锁,向/sys/power/wake_lock节点中写入"PowerManager.WakeLocks",使CPU保持唤醒状态。
-
- mDisplaySuspendBlocker锁:表示由于屏幕亮屏而使CPU保持唤醒状态。只要屏幕处于亮屏状态时,就会申请mDisplaySuspendBlocker锁,向/sys/power/wake_lock中写入“PowerManagerService.Display”;
-
- PowerManagerService.Broadcasts锁:表示由于需要确保Notifier中广播的成功发送,而使CPU保持唤醒状态;
该锁在分析流程中还暂时没有碰到,它以构造方法的形式传给了Notifier类,PMS中和其他服务的部分交互通过Notifier进行,还有亮灭屏广播都是由PMS交给Notifier来发送。如果CPU在广播发送过程中进入休眠,则广播无法发送完成,因此,需要一个锁来保证Notifier中广播的成功发送,这就是PowerManagerService.Broadcasts锁的作用。当广播发送完毕后,该锁就立即释放。
API17 后不推荐使用WakeLock 保持屏幕常亮,其中SCREEN_DIM_WAKE_LOCK保证屏幕亮起,但是亮度可能比较丢,同时键盘背光也可以不亮,而SCREEN_BRIGHT_WAKE_LOCK保证屏幕全亮,同时键盘背光也亮,FULL_WAKE_LOCK表现和SCREEN_BRIGHT_WAKE_LOCK类似,区别在于这个等级WakeLock 使用的是推荐的
WindowFlagWindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
,在代码中使用
//在Activity中
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);//在布局中
添加android:keepScreenOn=“true”
三、AlarmManager
AlarmManage有一个AlarmManagerService,该服务程序主要维护app注册下来的各类Alarm,并且一直监听Alarm设备,一旦有Alarm触发,或者是Alarm事件发生,AlarmManagerService就会遍历Alarm列表,找到相应的注册Alarm并发出广播
AlarmManager会维持一个cpu的wakelock。这样能保证电话休眠时,也能处理alarm的广播。一旦alarmreceiver的onReceive()方法执行完,wakelock会迅速被释放。如果在receiver中开启一个service,有可能service还没启动,wakelock已经被释放了。所以此时要实现单独的wakelock策略。有4种Alarm类型:
- RTC_WAKEUP:在指定的时刻(设置Alarm的时候),唤醒设备来触发Intent。
- RTC:在一个显式的时间触发Intent,但不唤醒设备。
- ELAPSED_REALTIME:从设备启动后,如果流逝的时间达到总时间,那么触发Intent,但不唤醒设备。流逝的时间包括设备睡眠的任何时间。注意一点的是,时间流逝的计算点是自从它最后一次启动算起。
- ELAPSED_REALTIME_WAKEUP:从设备启动后,达到流逝的总时间后,如果需要将唤醒设备并触发Intent。
四、采集电量相关日志报告
通过软件进行电量测试,主要是结合adb dump batterystats 导出电量报告并利用Google的Battery Historian工具自动进行分析,首先是采集Batterystats 数据,连接手机,打开开发者模式,连接adb。
1、通过adb dumpsys batterystats --reset
命令重置
因为设备始终在后台收集batterystats和其他调试信息, 重置会清除采集旧数据便于排除干扰。
2、再执行adb dumpsys batterystats --enable full-wake-history
打开完整唤醒日志
3、断开设备与电脑的连接,以便只从设备的电池中消耗电流,模拟场景并采集数据
然后使得设备不在充电状态,模拟执行一段时间后
4、不充电状态下获取完整的bugreport信息
重新链接设备,执行adb bugreport
导出电量日志。
在SDK 25后bugreport工具必须要在7.0 以上设备使用,旧版本需要下载SDK R20的platform-tools工具;Android <=6.0 时
adb bugreport >xxx.txt
Android > 6.0 时 直接adb bugreport xxx.zip
,导出完整的bugreport日志待用
五、Battery Historian
Batterystats 是包含在 Android 框架中的一种工具,用于收集设备上的电池数据。您可以使用 adb 将收集的电池数据转储到开发机器,并生成可使用 Battery Historian 分析的报告。Battery Historian 会将报告从 Batterystats 转换为可在浏览器中查看的 HTML 直观视图。适合的场景:
- 显示进程从什么位置以及通过何种方式消耗电池电量。
- 识别系统为了延长电池续航时间可能会延迟甚至移除应用中的哪些任务。
1、Battery Historian 常见条目解析
条目 | 说明 |
---|---|
CPU Running | CPU的运行状态是否被唤醒,有条状柱子就说明被唤醒运行中 |
Kernel only uptime | 只有内核运行的情况,有柱子就说明符合该条目,下同 |
Screen | 屏幕亮屏的情况 |
Top app | 当前运行的App |
JobScheduler | 异步作业JobScheduler 运行的情况 |
SyncManager | 同步操作SyncManager运行的情况 |
Sensor | Sensor使用情况 |
Phone scanning | |
Phone call | 是否正在打电话 |
GPS | 是否正在使用GPS |
Network Connectivity | 网络连接状态(wifi、mobile是否连接) |
Activity Manager Proc | 活跃的用户进程 |
Mobile network type | 网络类型 |
Mobile radio active | 移动蜂窝信息,基带BP测耗电情况 |
Crashes(logcat) | 某个时间点出现Crash的应用 |
Doze | 是否处于Doze模式,从Android 6.0(API级别23)开始,该平台包含Doze功能,这对应用程序实施了某些优化,例如,无论 JobScheduler 的调度方式如何,低电耗模式都会将作业处理成在简短的维护期间批量进行。 |
Doze active | 与Doze模式相反 |
Temp White List | 电量优化白名单 |
Mobile signal strength | 移动信号强度(greate\good\modrate\poor) |
Wifi scan | 是否在扫描Wifi信号 |
Wifi supplicant | 是否在处理Wifi请求 |
Wifi radio | 是否正在通过Wifi 传输数据 |
Wifi signal strength | Wifi信号强度(greate\good\modrate\poor) |
Wifi running | Wifi组件是否在工作(但未传输数据) |
Wifi on | Wifi组件是否在工作(但未传输数据) |
Audio | 音频是否开启 |
Camera | Camera是否正在工作 |
Video | 是否在播放视频 |
Package Install | 是否在安装包 |
Battery Level | 当前电量 |
Plugged | 是否在充电 |
2、Battery Historian 主要功能介绍
Battery Historian 在全系统级别以HTML表示形式从系统日志中查看相关的事件。 在特定应用程序级别,该工具提供了各种数据,比如可帮助您确定电池耗尽应用程序的行为。将鼠标悬停在信息图标上可查看有关每个指标的更多信息,包括图表中使用的颜色的关键字,将鼠标悬停在栏上,即可查看有关该指标的详细信息以及时间线上特定点的电池状态,
2.1、Add Metrics
从下拉列表中添加其他指标。
2.2、System Stats
System Stats选项卡包含系统范围的统计信息,如单元信号级别和屏幕亮度。 这些信息提供了设备发生情况的整体情况。 这对确保没有外部事件影响您的测试特别有用。另外Battery Historian图表下面的stats部分可以查看bugreport文件中的其他信息,还可以查询以下信息(不限于):
- 该应用在设备上的预计用电量。
- 网络信息。
- Wakelocks。
- 服务。
- 进程信息。
这些表格提供了有关您的应用的两个维度数据。 首先,您可以查看您的应用的用电量排名与其他应用的相比。 为此,请单击表下的设备功率估算表。
2.3、App Stats
App Stats选项卡包含有关特定应用程序的信息。 使用左侧的应用程序选择窗格中(图3)排序应用程序下拉列表对应用程序列表进行排序。 您可以选择一个特定的应用程序来查看使用下面(图4)个应用程序下拉列表的统计信息。