Android 功耗分析之wakelock

作者: 小草凡 | 来源:发表于2017-08-23 21:50 被阅读2074次

    生活总是让我们遍体鳞伤,但到后来,那些受伤的地方一定会变成我们最强壮的地方。---海明威

    WakeLock是什么

    WakeLock是Android框架层提供的一套机制,应用使用该机制可以达到控制Android设备状态的目的。这里的设备状态主要指屏幕的打开关闭,cpu的保持运行。简单的理解WakeLock是让系统保持"清醒"的一种手段.

    WakeLock作用

    当手机灭屏状态下保持一段时间后,系统会进入休眠,一些后台运行的任务就可能得不到正常执行,比如网络下载中断,后台播放音乐暂停等。WakeLock正是为了解决这类问题,应用只要申请了WakeLock,那么在释放WakeLock之前,系统不会进入休眠,即使在灭屏的状态下,应用要执行的任务依旧不会被系统打断。

    WakeLock有那些分类

    WakeLock是PowerManager的内部类,其代码路径位于:

    frameworks/base/core/java/android/os/PowerManager.java

    WakeLock 分类如下:

    • PARTIAL_WAKE_LOCK: 灭屏,关闭键盘背光的情况下,CPU依然保持运行。
    • PROXIMITY_SCREEN_OFF_WAKE_LOCK: 基于距离感应器熄灭屏幕。最典型的运用场景是我们贴近耳朵打电话时,屏幕会自动熄灭。
    • SCREEN_DIM_WAKE_LOCK/SCREEN_BRIGHT_WAKE_LOCK/FULL_WAKE_LOCK:这三种WakeLock都已经过时了,它们的目的是为了保持屏幕长亮,Android官方建议用getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);方式替换。因为比起申请WakeLock,这种方式更简单,还不需要特别申请android.permission.WAKE_LOCK权限。
    • DOZE_WAKE_LOCK/DRAW_WAKE_LOCK: 隐藏的分类,系统级别才会用到。

    WakeLock的flag如下:

    • ACQUIRE_CAUSES_WAKEUP: 点亮屏幕,比如应用接收到通知后,屏幕亮起。
    • ON_AFTER_RELEASE: 释放WakeLock后,屏幕不马上熄灭。
    • UNIMPORTANT_FOR_LOGGING: 隐藏的flag,系统级别才会用到。

    WakeLock的设置过程

    WakeLock从用户空间下发设置操作,然后进入kernel空间,最终写入到了/sys/power/wake_lock文件节点。
    下面来从源码的角度跟踪下acquire WakeLock的过程。

    1. frameworks/base/core/java/android/os/PowerManager.java
      acquire--->acquireLocked---->PowerManagerService.acquireWakeLock

    2. frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
      acquireWakeLock--->acquireWakeLockInternal---->updatePowerStateLocked---->updateSuspendBlockerLocked---->mWakeLockSuspendBlocker.acquire---->PowerManagerService$SuspendBlockerImpl.acquire---->nativeAcquireSuspendBlocker

    3. frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp
      nativeAcquireSuspendBlocker---->acquire_wake_lock

    4. hardware/libhardware_legacy/power/power.c
      acquire_wake_lock,最终在该方法里将wakelock写入了节点

      int 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;
          size_t len;
          ssize_t ret;
      
          if (lock != PARTIAL_WAKE_LOCK) {
              return -EINVAL;
          }
      
          fd = g_fds[ACQUIRE_PARTIAL_WAKE_LOCK];
          //这个节点就是/sys/power/wake_lock
          ret = write(fd, id, strlen(id));
          if (ret < 0) {
              return -errno;
          }
      
          return ret;
      }
      

    WakeLock用法

    WakeLock的使用需要谨慎处理,使用不当会让应用变成“电量杀手”。使用的原则

    1. 能不用就尽量别用:比如要保持屏幕长亮,应该用getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);的方式。

    PowerManager.FULL_WAKE_LOCK会保持屏幕长亮,比如设置15s自动灭屏,当申请了该wakelock后,即使超过15s,依然不会灭屏。但用户主动按power键,还是会灭屏的。

    1. 如果非要用,用完之后记得释放。
      申请WakeLock有两种方式acquire()跟acquire(long timeout),后者相对更安全点,如果忘记了release WakeLock,经过timeout的时长后系统会自动release。

    WakeLock的典型用法如下:

    PowerManager pm = (PowerManager)mContext.getSystemService(
                                              Context.POWER_SERVICE);
    PowerManager.WakeLock wl = pm.newWakeLock(
                                          PowerManager.PARTIAL_WAKE_LOCK
                                          | PowerManager.ON_AFTER_RELEASE,
                                          TAG);
    wl.acquire();//为了保证任务不被系统休眠打断,申请WakeLock
    // 开始我们的任务
    wl.release();//任务结束后释放,如果不写该句。则可以用wl.acquire(timeout)的方式把释放的工作交给系统。
    

    同时需要在Manifest文件中添加权限

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

    WakeLock相关问题的debug方法

    应用层debug

    如果只是单纯的查看某一个应用的wakelock是否存在非正常释放的情况,可以用命令

    $ adb shell dumpsys power|grep -i wake
    

    来查看,比如下面的示例代码

    public class MainActivity extends Activity {
        private WakeLock wlLock;
        @Override
        protected void onResume() {
            PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
            wlLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK|PowerManager.ON_AFTER_RELEASE, "azhengye-test-wakelock");
            wlLock.acquire();
            super.onResume();
        }
        @Override
        protected void onPause() {
            wlLock.release();
            super.onPause();
        }
    }
    

    当进入onResume方法,此时dumpsys的信息如下:

    $ adb shell dumpsys power|grep -i wake
    
      mWakefulness=Awake
      mWakefulnessChanging=false
      mWakeLockSummary=0x1
      mLastWakeTime=34512600 (293075 ms ago)
      mHoldingWakeLockSuspendBlocker=true
      mWakeUpWhenPluggedOrUnpluggedConfig=true
      mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig=false
      mDoubleTapWakeEnabled=false
    Wake Locks: size=1
      PARTIAL_WAKE_LOCK              'azhengye-test-wakelock' ON_AFTER_RELEASE ACQ=-4m52s949ms LONG (uid=10124 pid=31473 pkg=com.azhengye.testpath)
      PowerManagerService.WakeLocks: ref count=1
    

    通过 PARTIAL_WAKE_LOCK 'azhengye-test-wakelock' ON_AFTER_RELEASE ACQ=-4m52s949ms LONG (uid=10124 pid=31473 pkg=com.azhengye.testpath) 可以看到wakelock申请成功了。
    如果进入onPause方法,刚申请的wakelock应该被释放掉,此时dumpsys出的信息如下:

    $ adb shell dumpsys power|grep -i wake
    
          mWakefulness=Awake
          mWakefulnessChanging=false
          mWakeLockSummary=0x1
          mLastWakeTime=34512600 (477908 ms ago)
          mHoldingWakeLockSuspendBlocker=true
          mWakeUpWhenPluggedOrUnpluggedConfig=true
          mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig=false
          mDoubleTapWakeEnabled=false
        Wake Locks: size=1
          PARTIAL_WAKE_LOCK              'AudioMix' ACQ=-2s539ms (uid=1041 pkg=audioserver)
          PowerManagerService.WakeLocks: ref count=1
    

    没毛病,已经正常释放掉。如果应用已经运行到释放wakelock的语句,但dumpsys出的信息仍然看到持有wakelock,这就是问题,需要我们去fix掉。

    系统层debug

    系统层面去debug wakelock相关问题就比较复杂了,基本上的步骤如下。

    1. 依据log分析
      首先打开debug开关
      frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java

      private static final boolean DEBUG = true;
      

      然后dumpsys power查看,同时在logcat里搜索acquireWakeLockInternal关键字查看申请wakelock情况,
      比如

      08-23 09:45:35.452  3054  3067 D PowerManagerService: acquireWakeLockInternal: lock=1945552, flags=0x1, tag="LocationManagerService", ws=WorkSource{1000 android}, uid=1000, pid=3054, packageName=android
      

      搜索releaseWakeLockInternal查看释放情况。lock=后面的整数能将acquire跟release对应起来,在结合logcat的时间戳,就能得到wakelock持有的时长,短时间的一般不会有问题,那种长期持有wakelock的会导致功耗增加,一般是有问题的,需要根据tag字段去代码里查看具体的acquire/release过程。

    2. 系统运行一段时间后抓取bugreport.用historian工具查看wakelock申请情况。具体可以查看之前的博客Android battery historian功耗分析之环境搭建

    相关文章

      网友评论

        本文标题:Android 功耗分析之wakelock

        本文链接:https://www.haomeiwen.com/subject/rcwjdxtx.html