之前在前公司处理了一个PVR录完无法待机的问题,涉及到了wakelock相关的知识。下面我们来系统的学习一下android wakelock的原理和机制。
Introduction
WakeLock是android系统设计用来让设备保持唤醒的一个机制。
OverView
如下是wakelock的基本架构(只是Android O的版本,不过其实没差):
我们从上层到下层依此分析学习一下:
Android Application
APK在使用wakelock的时候,只用按照Android SDK提供PowerManager的API去使用就好,如下是在gogole官网上找到的说明:
如果在设备休眠前,您为了去完成一些事情而需要CPU一直保持正常运行的话,那么您可以使用属于系统服务PowerManager的wakelock功能。wake locks允许您的应用可以控制设备的待机状态。
不过创建并持有wake locks会对主机设备的电池寿命影响巨大,因此建议只有在非常必要的情况下才使用wake locks,并尽可能短时间的持有它们。举个例子,如果您希望在某个activity中保持屏幕常亮,那么请使用FLAG_KEEP_SCREEN_ON
,而不需要使用wake lock。
使用wake lock的一个合理场景可能是您有某个后台服务需要在屏幕关闭时运行。不过也应该尽量少的使用wakelock,还是因为它会影响电池寿命。
要是用wakelock,第一步需要在apk的manifest里面增加WAKE_LOCK权限:1
<uses-permission android:name="android.permission.WAKE_LOCK" />
如果你的apk在使用service component来做某些事情的时候,包括了一个broadcast接收器,那么您可以通过WakefulBroadcastReceiver来管理wake lock(可以参考这个地方Use a broadcast receiver that keeps the device awake.我们推荐使用WakefulBroadcastReceiver,如果您的apk不遵守该模式,如下是直接设置wake lock的方法:1
2
3
4
5
6PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"MyApp::MyWakelockTag");
wakeLock.acquire();
...//do somethings
wakeLock.release();
可以通过call wakelock.release()
来释放wake lock。一旦您的apk做完了需要的工作,一定要尽快释放wake lock,以避免它耗尽电池的电量,这点非常重要。
Android Framework
这部分基本都是android原生做法,这部分我们先掠过好辣。。
Self API
这部分其实是我前公司的设计架构,为了支持Android SDK没有的功能,所以需要从Driver到JNI再到JAVA走另外一路上去给APK提供API。在这里我们可以看到PVR在录完音视频要待机时,需要释放它之前在power_hal里面加的wakelock,所以它会call到我们自己的Service API来解锁。
Android HAL
不论上面两条路有什么不同,最终它们都会call到Android HAL里以便和linux kernel打交道。这里的acquire_wake_lock()
和release_wake_lock()
其实是直接向File System的/sys/power/wake_lock
和/sys/power/wake_unlock
里面写入wake lock name。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41acquire_wake_lock(int lock, const char* id)
{
initialize_fds();
// ALOGI("acquire_wake_lock lock=%d id='%s'\n", lock, id);
if (g_error) return g_error;
int fd;
size_t len;
ssize_t ret;
if (lock != PARTIAL_WAKE_LOCK) {
return -EINVAL;
}
fd = g_fds[ACQUIRE_PARTIAL_WAKE_LOCK];
ret = write(fd, id, strlen(id));
if (ret < 0) {
return -errno;
}
return ret;
}
int
release_wake_lock(const char* id)
{
initialize_fds();
// ALOGI("release_wake_lock id='%s'\n", id);
if (g_error) return g_error;
ssize_t len = write(g_fds[RELEASE_WAKE_LOCK], id, strlen(id));
if (len < 0) {
return -errno;
}
return len;
}
还是一开始的问题,我有把ALOGI的log打开,发现它要待机的时候确实有跑到,不过奇怪的是没用作用,那我们只能继续往kernel去追了。
Tips
我们在debug的时候也可以直接手动echo wake lock name到这两个文件里,以检测是否真的是wakelock的问题。1
2
3
4# shell command to acquire a wake lock
echo lock_me > /sys/power/wake_lock
# shell command to release the same wake_lock
echo lock_me > /sys/power/wake_unlock
Linux Kernel
1)允许driver创建wakelock以阻止睡眠、注销wakelock以允许睡眠:由Wakeup events framework的wakeup source实现。
2)当系统中所有的wakelock都注销后,系统可以自动进入低功耗状态:由autosleep实现。
3)wake_lock和wake_unlock功能:由kernel wakelocks实现,其本质就是将wakeup source开发到用户空间访问。/sys/power/wake_lock
和/sys/power/wake_unlock
这两个sysfs文件在kernel/power/main.c中实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static ssize_t wake_lock_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
return pm_show_wakelocks(buf, true);
}
static ssize_t wake_lock_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
int error = pm_wake_lock(buf);
return error ? error : n;
}
power_attr(wake_lock);
static ssize_t wake_unlock_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
return pm_show_wakelocks(buf, false);
}
static ssize_t wake_unlock_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
int error = pm_wake_unlock(buf);
return error ? error : n;
}
power_attr(wake_unlock);
可以发现:
1)wakelocks功能不是linux kernel的必选功能,可以通过CONFIG_PM_WAKELOCKS开关。
2)wake_lock的写接口,直接调用pm_wake_lock;wake_unlock的写接口,直接调用pm_wake_unlock;它们的读接口,直接调用pm_show_wakelocks接口(参数不同)。这三个接口均在kernel/power/wakelock.c中实现。
这里我们先只看pm_wake_unlock():1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35int pm_wake_unlock(const char *buf)
{
struct wakelock *wl;
size_t len;
int ret = 0;
if (!capable(CAP_BLOCK_SUSPEND))
return -EPERM;
len = strlen(buf);
if (!len)
return -EINVAL;
if (buf[len-1] == '\n')
len--;
if (!len)
return -EINVAL;
mutex_lock(&wakelocks_lock);
wl = wakelock_lookup_add(buf, len, false);
if (IS_ERR(wl)) {
ret = PTR_ERR(wl);
goto out;
}
__pm_relax(&wl->ws);
wakelocks_lru_most_recent(wl);
wakelocks_gc();
out:
mutex_unlock(&wakelocks_lock);
return ret;
}
到这里就拨云见日了,原来是if (!capable(CAP_BLOCK_SUSPEND))
这行权限判断给拦住了。不过奇怪的是我再试着给Self Service增加android.permission.WAKE_LOCK权限,仍然会被拦住@@
root cause
为什么我自己的service没用权限去写wake_unlock呢?其他process又是如何有权限去写wake_unlock的呢?
这个时候通过查看hardware/libhardware_legacy/power.c
里面的ALOGI可以发现system_server是有权限写的。然后就去翻了system_server的code终于找到了当时修改的commit。
https://android.googlesource.com/platform/frameworks/base/+/fbd5904%5E%21/
通过这个commit可以清楚的得出结论:
google只给android.permission.BLUETOOTH_STACK增加了GID=3010的CAP_BLOCK_SUSPEND权限
所以这个问题就很容易啦,直接给Self Service的manifest里面增加BLUETOOTH_STACK权限就解决问题啦:<uses-permission android:name="android.permission.BLUETOOTH_STACK" />
所以看起来google的code也不是很严谨呀。。不过很大可能是他们根本不会有让其他process访问wake_lock/wake_unlock的需求XDDD
不过这个问题根源是android process权限问题,后续有时间了再补充这部分姿势~
PS:其实root cause也可以说是在power_hal里拿wakelock不合理@@,不过这属于我前司的STR和PVR架构设计问题了=。=
Reference
https://developer.android.com/training/scheduling/wakelock#java
https://stackoverflow.com/questions/21874122/how-to-take-wakelock-from-shell-command-on-android
http://www.wowotech.net/pm_subsystem/wakelocks.html