CFSection9's Studio.

WakeLock

字数统计: 1.6k阅读时长: 6 min
2018/10/28 Share

之前在前公司处理了一个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
6
PowerManager 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
41
acquire_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
#ifdef CONFIG_PM_WAKELOCKS
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);

#endif /* CONFIG_PM_WAKELOCKS */

可以发现:
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
35
int 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

CATALOG
  1. 1. Introduction
  2. 2. OverView
  3. 3. Android Application
  4. 4. Android Framework
  5. 5. Self API
  6. 6. Android HAL
    1. 6.1. Tips
  7. 7. Linux Kernel
  8. 8. root cause
  9. 9. Reference