安卓电量优化之WakeLock锁机制全面解析
版权声明:本文出自汪磊的博客,转载请务必注明出处。
一、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" />
注意:在使用该类的时候,必须保证acquire和release是成对出现的。
三、保持屏幕常亮
最好的方式是在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,不要做电池杀手。
声明:文章将会陆续搬迁到个人公众号,以后文章也会第一时间发布到个人公众号,及时获取文章内容请关注公众号