zoukankan      html  css  js  c++  java
  • 安卓电量优化之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" />   

    注意:在使用该类的时候,必须保证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,不要做电池杀手。

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

  • 相关阅读:
    Android RSS阅读器
    X86汇编语言学习教程之1 ————前言
    beanstalk源码剖析——概述
    从软件质量看如何写代码(1)
    软件开发模型
    软件质量思考
    数据结构概述
    Don't Distract New Programmers with OOP(转)
    Linus Torvalds’s Lessons on Software Development Management(转)
    谈测试驱动开发
  • 原文地址:https://www.cnblogs.com/leipDao/p/8241468.html
Copyright © 2011-2022 走看看