在Android中,申请WakeLock可以让你的进程持续执行即使手机进入睡眠模式,比较实用的是比如:后台有网络功能,可以保证操作持续进行.
使用方法
在需要的地方开启WakeLock,需要结束的时候调用结束方法。
另外如果要使用WakeLock需要在Manifest中添加如下权限
你可能还需要
WakeLock的设置是 Activiy 级别的,不是针对整个Application应用的。
private void acquireWakeLock() {
if (wakeLock ==null) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getCanonicalName());
wakeLock.acquire();
}
}
private void releaseWakeLock() {
if (wakeLock !=null&& wakeLock.isHeld()) {
wakeLock.release();
wakeLock =null;
}
}
PowerManager和WakeLock的操作步骤
1.PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);通过 Context.getSystemService() 方法获取PowerManager实例。
2.然后通过PowerManager的newWakeLock((int flags, String tag)来生成WakeLock实例。int Flags指示要获取哪种WakeLock, 不同的Lock对cpu 、屏幕、键盘灯有不同影响。
3.获取WakeLock实例后通过acquire()获取相应的锁,然后进行其他业务逻辑的操作,最后使用release()释放(释放是必须的)。
WakeLock的等级
newWakeLock((int flags, String tag)的第一个参数是WakeLock levelAndFlag,分别代表了一种WakeLock等级,并且可以通过「或」位操作组合使用。他们是:
1.PARTIAL_WAKE_LOCK:保证CPU保持高性能运行,而屏幕和键盘背光(也可能是触摸按键的背光)关闭。一般情况下都会使用这个WakeLock。
2.ACQUIRE_CAUSES_WAKEUP:这个WakeLock除了会使CPU高性能运行外还会导致屏幕亮起,即使屏幕原先处于关闭的状态下。
3.ON_AFTER_RELEASE:如果释放WakeLock的时候屏幕处于亮着的状态,则在释放WakeLock之后让屏幕再保持亮一小会。如果释放WakeLock的时候屏幕本身就没亮,则不会有动作。
被弃用的WakeLock:
1.SCREEN_DIM_WAKE_LOCK:保证屏幕亮起,但是亮度可能比较低。同时键盘背光也可以不亮。
2.SCREEN_BRIGHT_WAKE_LOCK :保证屏幕全亮,同时键盘背光也亮。
3.FULL_WAKE_LOCK:表现和SCREEN_BRIGHT_WAKE_LOCK 类似,但是区别在于这个等级的WakeLock使用的是最高亮度.
这三个Level在API17被弃用。被弃用说明肯定有替代品吗,上面三个类型的作用无非就是保持屏幕长亮。 所以推荐是用WindowFlagWindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON。使用方法是: 在Activity中: getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 甚至可以在布局中添加这个属性:android:keepScreenOn=”true”
被隐藏的WakeLock:
android中的部分api并不对用户应用开放,他们通过在注释中加入{@hide}来注明。但是在SDK提供的源代码中是可以看到的。
WAIT_FOR_PROXIMITY_NEGATIVE:用于和接近传感器配合使用,来实现电话应用中打电话时可以使屏幕黑掉,手机离开时又能使屏幕亮起来的功能。 acqure的时候屏幕会暗下来,release之后屏幕会亮。其值是32(int)。虽然被hide起来,但是在API21开放了.经试验, 不过newWakeLock的时候flag直接用32代替是可以创建这个等级的WakeLock的,但是因为是非开放API,不能保证第三方OEM修改这个代码实现. 因此使用起来并不安全。
总结
除了这四个Level之外,PowerMager还提供了两个Flag,可以配合Level使用。
注意: 这两个Flag和PARTIAL_WAKE_LOCK组合是没有作用的。
WakeLock常用方法
1.void acquire():获取WakeLock
2.void acquire(long timeOut):获得WakeLock timeOut时长,当超过timeOut之后系统自动释放WakeLock。
3.release():释放WakeLock
4.boolean isHeld():判断是否已经获取WakeLock
5.void setReferenceCounted(boolean value):是否使用引用计数。类似于垃圾回收策略,只是把垃圾回收改成了WakeLock回收。 如果value是true的话将启用该特性,如果一个WakeLock acquire了多次也必须release多次才能释放掉。但是如果释放次数比acquire 多则会抛出RuntimeException(“WakeLock under-locked “ + mTag)异常。默认是开启了引用计数的。
PowerManager的几个实用方法
1.boolean isScreenOn() 判断屏幕是否亮着(不管是暗的dimed还是正常亮度),在API20被弃用,推荐boolean isInteractive ()
2.void goToSleep(long time)time是时间戳,一般是System.currentTimeMillis() +timeDelay。强制系统立刻休眠,需要Manifest中添加权限”android.permission.DEVICE_POWER”。按下电源键锁屏时调用的就是这个方法。
3.void wakeUp(long time)与上面对应。参数含义,所需权限与上同。按下电源键解锁屏幕时调用的就是这个方法。
4.void reboot(String reason)重启手机,reason是要传给linux内核的参数,比如“recovery”重启进recovery模式,“fastboot ”重启进fastboot模式。需要权限”android.permission.REBOOT”。
源码分析
看一下WakeLock的源码实现:
public WakeLock newWakeLock(int levelAndFlags, String tag) {
validateWakeLockParameters(levelAndFlags, tag);
return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName());
}
public static void validateWakeLockParameters(int levelAndFlags, String tag) {
switch (levelAndFlags & WAKE_LOCK_LEVEL_MASK) {
case PARTIAL_WAKE_LOCK:
case SCREEN_DIM_WAKE_LOCK:
case SCREEN_BRIGHT_WAKE_LOCK:
case FULL_WAKE_LOCK:
case PROXIMITY_SCREEN_OFF_WAKE_LOCK:
break;
default:
throw new IllegalArgumentException("Must specify a valid wake lock level.");
}
if (tag == null) {
throw new IllegalArgumentException("The tag must not be null.");
}
}
newWakeLock方法首先检测LevelAndFlags和Tag的合法性:tag不能为空,Level必须用PowerManager提供的几个Level。 接下来,就进入到WakeLock的构造函数了。WakeLock是PowerManager的内部类。
public final class WakeLock {
private final int mFlags;
private final String mTag;
private final String mPackageName;
private final IBinder mToken;
private int mCount;
private boolean mRefCounted = true;
private boolean mHeld;
private final Runnable mReleaser = new Runnable() {
public void run() {
release();
}
};
WakeLock(int flags, String tag, String packageName) {
mFlags = flags;
mTag = tag;
mPackageName = packageName;
mToken = new Binder();
}
/** * Acquires the wake lock. * <p> * Ensures that the device is on at the level requested when the wake * lock was created. * </p> */
public void acquire() {
synchronized (mToken) {
acquireLocked();
}
}
private void acquireLocked() {
if (!mRefCounted || mCount++ == 0) {
// Do this even if the wake lock is already thought to be held
// (mHeld == true)
// because non-reference counted wake locks are not always
// properly released.
// For example, the keyguard's wake lock might be forcibly
// released by the
// power manager without the keyguard knowing. A subsequent call
// to acquire
// should immediately acquire the wake lock once again despite
// never having
// been explicitly released by the keyguard.
mHandler.removeCallbacks(mReleaser);
try {
mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource);
} catch (RemoteException e) {
}
mHeld = true;
}
}
/** * Releases the wake lock. * <p> * This method releases your claim to the CPU or screen being on. The * screen may turn off shortly after you release the wake lock, or it * may not if there are other wake locks still held. * </p> */
public void release() {
release(0);
}
/** * Releases the wake lock with flags to modify the release behavior. * <p> * This method releases your claim to the CPU or screen being on. The * screen may turn off shortly after you release the wake lock, or it * may not if there are other wake locks still held. * </p> * * @param flags * Combination of flag values to modify the release behavior. * Currently only {@link #WAIT_FOR_PROXIMITY_NEGATIVE} is * supported. * * {@hide} */
public void release(int flags) {
synchronized (mToken) {
if (!mRefCounted || --mCount == 0) {
mHandler.removeCallbacks(mReleaser);
if (mHeld) {
try {
mService.releaseWakeLock(mToken, flags);
} catch (RemoteException e) {
}
mHeld = false;
}
}
if (mCount < 0) {
throw new RuntimeException("WakeLock under-locked " + mTag);
}
}
}
/** * Returns true if the wake lock has been acquired but not yet released. * * @return True if the wake lock is held. */
public boolean isHeld() {
synchronized (mToken) {
return mHeld;
}
}
}
以acquire方法为例,通过对源码的分析,我们发现获取WakeLock的实现是通过mService进行的:
mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource);
而mService是在PowerManager类里实例化的:
final IPowerManager mService;
mService实例化类为/frameworks/base/services/java/com/android/server/power/PowerManagerService.java, 而到这里类里,你最终发现acquireWakeLock是由JNI层的native方法实现的。
private static native void nativeAcquireSuspendBlocker(String name);
而这个方法的实现是在/frameworks/base/services/jni/com_android_server_power_PowerManagerService.cpp代码中:
static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass clazz, jstring nameStr) {
ScopedUtfChars name(env, nameStr);
acquire_wake_lock(PARTIAL_WAKE_LOCK, name.c_str());
}
这个acquire_wake_lock是在/hardware/libhardware_legacy/power/power.c里定义的
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;
if (lock == PARTIAL_WAKE_LOCK) {
fd = g_fds[ACQUIRE_PARTIAL_WAKE_LOCK];
}
else {
return EINVAL;
}
return write(fd, id, strlen(id));
}
可以看到,acquire wake lock真正的实现是在fd所指向的文件中写了一串字符即可。 fd所指向的文件定义如下:
const char * const NEW_PATHS[] = {
"/sys/power/wake_lock",
"/sys/power/wake_unlock",
};
其他用法扩展(官方API Keeping the Device Awake )
保持屏幕点亮
某些应用程序需要保持屏幕打开,如游戏或电影应用程序。最好的办法是在你的activity中使用flag_keep_screen_on(必须只有一个activity, 不能是service或其他组件)。例如:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
这种方法的优点是,不像akeLock,它不需要特殊的权限,由系统正确地管理用户在应用程序之间切换,不需要担心你的应用程序需要释放未使用的资源。
另一种方式来实现这是在你的应用程序的XML布局文件,用android:keepScreenOn属性:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
...
</RelativeLayout>
使用Android的keepscreenon=“true”相当于使用flag_keep_screen_on。你可以使用两者中适合自己的方法。设置flag的方式好处是, 它给你一个机会在稍后可以清除flag,从而使屏幕关闭。
注意:你不需要清除FLAG_KEEP_SCREEN_ON,除非你不再想让屏幕点亮(例如,一段闲置时间后设置超时)。window manager负责确保当应用程序进入后台或返回到前台时, 能够正确运行。但如果你想清除flag,从而让屏幕关闭,使用clearflags():
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON).
保持CPU运行
如果你需要保存CPU运转,在设备进入睡眠之前完成一些工作,你可以使用PowerManager系统服务功能:WakeLock。WakeLock允许您的应用程序来控制设备的电源状态。 创建和保持WakeLock会对设备的电池时间产生巨大的影响。因此,你应该只有在必要的时候使用WakeLock,并使用尽可能短的时间。 例如,你不应该在Activity的整个生命周期中使用WakeLock。上面提到过,如果你想在activity中保持屏幕点亮,用flag_keep_screen_on。
使用WakeLock的一个合法的案例可能是一个后台service,需要用WakeLock使屏幕关闭时保持CPU进行工作。虽然,这种做法应尽量减少, 因为其对电池寿命的影响。
使用WakeLock,第一步是在manifest中添加wake_lock权限:
<uses-permission android:name="android.permission.WAKE_LOCK" />
如果你的应用程序包括一个broadcast receiver,使用一个service做了一些工作,你可以通过WakefulBroadcastReceiver管理你的wake lock (可以参考下面对WakefulBroadcastReceiver的讲解),这是首选方法。如果你的app没有遵循这个模式,这里是如何设置一个wake lock :
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"MyWakelockTag");
wakeLock.acquire();
释放WakeLock,调用wakelock.release()。这可以释放你对CPU的控制。当你的应用程序完成后,释放一个WakeLock是很重要的,避免浪费电量。
使用WakefulBroadcastReceiver
使用broadcast receiver与一个service相结合,可以让你管理一个后台任务的生命周期。WakefulBroadcastReceiver是一种特殊类型的广播接收器, 负责创建和管理你的应用程序的PARTIAL_WAKE_LOCK。WakefulBroadcastReceiver把任务转移给Service(通常是IntentService),并且确保系统在转换 过程中不会休眠.如果在任务迁移到service的过程中,你没有维持一个wake lock,设备就被允许在任务完成前进入休眠。最终的结果是, 应用程序可能无法完成这一工作,这是不是你想要的。
在使用wakefulbroadcastreceiver的第一步是添加到manifest,与任何其他广播接收器一样:
<receiver android:name=".MyWakefulReceiver"></receiver>
下面的代码通过startwakefulservice()方法开启MyIntentService。这种方法和startservice()类似, 除了WakefulBroadcastReceiver在service启动时维持了一个wake lock。通过startWakefulService()携带的intent也包含了一个额外的 WakeLock的识别标志。
public class MyWakefulReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Start the service, keeping the device awake while the service is
// launching. This is the Intent to deliver to the service.
Intent service = new Intent(context, MyIntentService.class);
startWakefulService(context, service);
}
}
当service结束时,调用MyWakefulReceiver.completeWakefulIntent()来释放wake lock. completeWakefulIntent()方法的参数是从 MyWakefulReceiver传递过来的那个Intent。
public class MyIntentService extends IntentService {
public static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
NotificationCompat.Builder builder;
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
// Do the work that requires your app to keep the CPU running.
// ...
// Release the wake lock provided by the WakefulBroadcastReceiver.
MyWakefulReceiver.completeWakefulIntent(intent);
}
}