Android 功耗分析之wakelock

生活總是讓我們遍體鱗傷,但到後來,那些受傷的地方一定會變成我們最強壯的地方。—海明威

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鍵,還是會滅屏的。

  2. 如果非要用,用完之後記得釋放。
    申請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功耗分析之環境搭建

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章