安卓电量优化之WakeLock锁机制全面解析

时间:2024-01-23 16:26:21

版权声明:本文出自汪磊的博客,转载请务必注明出处。

一、WakeLock概述

wakelock是一种锁的机制,只要有应用拿着这个锁,CPU就无法进入休眠状态,一直处于工作状态。比如,手机屏幕在屏幕关闭的时候,有些应用依然可以唤醒屏幕提示用户消息,这里就是用到了wakelock锁机制,虽然手机屏幕关闭了,但是这些应用依然在运行着。手机耗电的问题,大部分是开发人员没有正确使用这个锁,成为"待机杀手"。

Android手机有两个处理器,一个叫Application Processor(AP),一个叫Baseband Processor(BP)。AP是ARM架构的处理器,用于运行Linux+Android系统;BP用于运行实时操作系统(RTOS),通讯协议栈运行于BP的RTOS之上。非通话时间,BP的能耗基本上在5mA左右,而AP只要处于非休眠状态,能耗至少在50mA以上,执行图形运算时会更高。另外LCD工作时功耗在100mA左右,WIFI也在100mA左右。一般手机待机时,AP、LCD、WIFI均进入休眠状态,这时Android中应用程序的代码也会停止执行。

Android为了确保应用程序中关键代码的正确执行,提供了Wake Lock的API,使得应用程序有权限通过代码阻止AP进入休眠状态。但如果不领会Android设计者的意图而滥用Wake Lock API,为了自身程序在后台的正常工作而长时间阻止AP进入休眠状态,就会成为待机电池杀手。

那么Wake Lock API具体有啥用呢?心跳包从请求到应答,断线重连重新登陆等关键逻辑的执行过程,就需要Wake Lock来保护。而一旦一个关键逻辑执行成功,就应该立即释放掉Wake Lock了。两次心跳请求间隔5到10分钟,基本不会怎么耗电。

二、WakeLock使用

获取WakeLock实例代码如下:

PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);  
WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag");

newWakeLock(int levelAndFlags, String tag)中PowerManager.PARTIIAL_WAKE_LOCK是一个标志位,标志位是用来控制获取的WakeLock对象的类型,主要控制CPU工作时屏幕是否需要亮着以及键盘灯需要亮着,标志位说明如下:

levelAndFlags CPU是否运行 屏幕是否亮着 键盘灯是否亮着
PARTIAL_WAKE_LOCK
SCREEN_DIM_WAKE_LOCK 低亮度
SCREEN_BRIGHT_WAKE_LOCK 高亮度
FULL_WAKE_LOCK

特殊说明:自API等级17开始,FULL_WAKE_LOCK将被弃用。应用应使用FLAG_KEEP_SCREEN_ON

WakeLock类可以用来控制设备的工作状态。使用该类中的acquire可以使CPU一直处于工作的状态,如果不需要使CPU处于工作状态就调用release来关闭。

(1)、自动release

如果我们调用的是acquire(long timeout)那么就无需我们自己手动调用release()来释放锁,系统会帮助我们在timeout时间后释放。

(2)、手动release

如果我们调用的是acquire()那么就需要我们自己手动调用release()来释放锁。

最后使用WakeLock类记得加上如下权限:

1 <uses-permission android:name="android.permission.WAKE_LOCK" />   

注意:在使用该类的时候,必须保证acquirerelease是成对出现的。

三、保持屏幕常亮

最好的方式是在Activity中使用FLAG_KEEP_SCREEN_ON的Flag。

1 public class MainActivity extends Activity {
2     @Override
3     protected void onCreate(Bundle savedInstanceState) {
4         super.onCreate(savedInstanceState);
5         setContentView(R.layout.activity_main);
6         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
7     }
8 }

这个方法的好处是不像唤醒锁(wake locks),需要一些特定的权限(permission)。并且能正确管理不同app之间的切换,不用担心无用资源的释放问题。 
另一个方式是在布局文件中使用android:keepScreenOn属性:

1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 
3     android:layout_width="match_parent"
4     android:layout_height="match_parent"
5     android:keepScreenOn="true">
6     ...
7 </RelativeLayout>

android:keepScreenOn = ”true“的作用和FLAG_KEEP_SCREEN_ON一样。使用代码的好处是你允许你在需要的地方关闭屏幕。

注意:一般不需要人为的去掉FLAG_KEEP_SCREEN_ON的flag,windowManager会管理好程序进入后台回到前台的的操作。如果确实需要手动清掉常亮的flag,使用getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

四、WakefulBroadcastReceiver + IntentService实例

IntentService使用请参照我之前博客Android IntentService使用介绍以及源码解析

WakefulBroadcastReceiver是BroadcastReceiver的一种特例。它会为你的APP创建和管理一个PARTIAL_WAKE_LOCK类型的WakeLock。WakefulBroadcastReceiver把工作交接给service(通常是IntentService),并保证交接过程中设备不会进入休眠状态。如果不持有WakeLock,设备很容易在任务未执行完前休眠。最终结果是你的应用不知道会在什么时候能把工作完成,相信这不是你想要的。

使用startWakefulService()方法来启动服务,与startService()相比,在启动服务的同时,并启用了唤醒锁。

当后台服务的任务完成,要调用WLWakefulReceiver.completeWakefulIntent()来释放唤醒锁。

WLWakefulReceiver类如下:

 1 public class WLWakefulReceiver extends WakefulBroadcastReceiver {
 2 
 3     private static final String TAG = "myTag";
 4 
 5     @Override
 6     public void onReceive(Context context, Intent intent) {
 7         //
 8         String extra = intent.getStringExtra("msg");
 9         Log.i(TAG, "onReceive:"+extra);
10         Intent serviceIntent = new Intent(context, MyIntentService.class);
11         serviceIntent.putExtra("msg", extra);
12        startWakefulService(context, serviceIntent);
13     }
14 }

很简单,就是打印一下信息以及调用startWakefulService方法来启动服务。

MyIntentService类如下:

 1 public class MyIntentService extends IntentService {
 2 
 3     private static final String TAG = "myTag";
 4     
 5     public MyIntentService() {
 6         super("MyIntentService");
 7     }
 8 
 9     @Override
10     protected void onHandleIntent(Intent intent) {
11         //子线程中执行
12         Log.i(TAG, "onHandleIntent");
13         for (int i = 0; i < 10; i++) {
14             try {
15                 Thread.sleep(3000);
16                 String extra = intent.getStringExtra("msg");
17                 Log.i(TAG, "onHandleIntent:"+extra);
18             } catch (InterruptedException e) {
19                 e.printStackTrace();
20             }
21         }
22         //调用completeWakefulIntent来释放唤醒锁。
23         WLWakefulReceiver.completeWakefulIntent(intent); 
24     }
25 }

同样很简单,也是打印信息进行耗时操作,但是执行完自己业务逻辑后一点记得调用completeWakefulIntent来释放唤醒锁。

最后就是启动广播接收者以及加入权限和声明了:

Intent intent = new Intent("WANG_LEI");
intent.putExtra("msg", "学习WAKE_LOCK。。。");
sendBroadcast(intent);

<uses-permission android:name="android.permission.WAKE_LOCK" />

<receiver android:name=".WLWakefulReceiver" >
            <intent-filter>
                <action android:name="WANG_LEI" />
            </intent-filter>
</receiver>
<service android:name=".MyIntentService"></service>

好了,编写好程序运行发现及时按电源键屏幕关闭依然有LOG打印出。

本篇到此结束,wakelock锁主要是相对系统的休眠而言的,意思就是我的程序给CPU加了这个锁那系统就不会休眠了,这样做的目的是为了全力配合我们程序的运行。有的情况如果不这么做就会出现一些问题,比如微信等及时通讯的心跳包会在熄屏不久后停止网络访问等问题。所以微信里面是有大量使用到了wake_lock锁。希望经过上述共同学习你能正确使用WakeLock,不要做电池杀手。

声明:文章将会陆续搬迁到个人公众号,以后文章也会第一时间发布到个人公众号,及时获取文章内容请关注公众号