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

从来好事天生俭,自古瓜儿苦后甜。这篇文章主要讲述安卓电量优化之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
【安卓电量优化之WakeLock锁机制全面解析】如果我们调用的是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 3protected void onCreate(Bundle savedInstanceState) { 4super.onCreate(savedInstanceState); 5setContentView(R.layout.activity_main); 6getWindow().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 3android:layout_width="match_parent" 4android:layout_height="match_parent" 5android: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 3private static final String TAG = "myTag"; 4 5@Override 6public void onReceive(Context context, Intent intent) { 7// 8String extra = intent.getStringExtra("msg"); 9Log.i(TAG, "onReceive:"+extra); 10Intent serviceIntent = new Intent(context, MyIntentService.class); 11serviceIntent.putExtra("msg", extra); 12startWakefulService(context, serviceIntent); 13} 14 }

很简单,就是打印一下信息以及调用startWakefulService方法来启动服务。
MyIntentService类如下:
1 public class MyIntentService extends IntentService { 2 3private static final String TAG = "myTag"; 4 5public MyIntentService() { 6super("MyIntentService"); 7} 8 9@Override 10protected void onHandleIntent(Intent intent) { 11//子线程中执行 12Log.i(TAG, "onHandleIntent"); 13for (int i = 0; i < 10; i++) { 14try { 15Thread.sleep(3000); 16String extra = intent.getStringExtra("msg"); 17Log.i(TAG, "onHandleIntent:"+extra); 18} catch (InterruptedException e) { 19e.printStackTrace(); 20} 21} 22//调用completeWakefulIntent来释放唤醒锁。 23WLWakefulReceiver.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,不要做电池杀手。
声明:文章将会陆续搬迁到个人公众号,以后文章也会第一时间发布到个人公众号,及时获取文章内容请关注公众号
安卓电量优化之WakeLock锁机制全面解析

文章图片

 

    推荐阅读