生活總是讓我們遍體鱗傷,但到后來,那些受傷的地方一定會變成我們最強壯的地方。---海明威
WakeLock是什么
WakeLock是Android框架層提供的一套機制,應用使用該機制可以達到控制Android設備狀態(tài)的目的。這里的設備狀態(tài)主要指屏幕的打開關閉,cpu的保持運行。簡單的理解WakeLock是讓系統(tǒng)保持"清醒"的一種手段.
WakeLock作用
當手機滅屏狀態(tài)下保持一段時間后,系統(tǒng)會進入休眠,一些后臺運行的任務就可能得不到正常執(zhí)行,比如網(wǎng)絡下載中斷,后臺播放音樂暫停等。WakeLock正是為了解決這類問題,應用只要申請了WakeLock,那么在釋放WakeLock之前,系統(tǒng)不會進入休眠,即使在滅屏的狀態(tài)下,應用要執(zhí)行的任務依舊不會被系統(tǒng)打斷。
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都已經(jīng)過時了,它們的目的是為了保持屏幕長亮,Android官方建議用
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
方式替換。因為比起申請WakeLock,這種方式更簡單,還不需要特別申請android.permission.WAKE_LOCK
權限。 - DOZE_WAKE_LOCK/DRAW_WAKE_LOCK: 隱藏的分類,系統(tǒng)級別才會用到。
WakeLock的flag如下:
- ACQUIRE_CAUSES_WAKEUP: 點亮屏幕,比如應用接收到通知后,屏幕亮起。
- ON_AFTER_RELEASE: 釋放WakeLock后,屏幕不馬上熄滅。
- UNIMPORTANT_FOR_LOGGING: 隱藏的flag,系統(tǒng)級別才會用到。
WakeLock的設置過程
WakeLock從用戶空間下發(fā)設置操作,然后進入kernel空間,最終寫入到了/sys/power/wake_lock
文件節(jié)點。
下面來從源碼的角度跟蹤下acquire WakeLock的過程。
frameworks/base/core/java/android/os/PowerManager.java
acquire--->acquireLocked---->PowerManagerService.acquireWakeLockframeworks/base/services/core/java/com/android/server/power/PowerManagerService.java
acquireWakeLock--->acquireWakeLockInternal---->updatePowerStateLocked---->updateSuspendBlockerLocked---->mWakeLockSuspendBlocker.acquire---->PowerManagerService$SuspendBlockerImpl.acquire---->nativeAcquireSuspendBlockerframeworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp
nativeAcquireSuspendBlocker---->acquire_wake_lock-
hardware/libhardware_legacy/power/power.c
acquire_wake_lock,最終在該方法里將wakelock寫入了節(jié)點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]; //這個節(jié)點就是/sys/power/wake_lock ret = write(fd, id, strlen(id)); if (ret < 0) { return -errno; } return ret; }
WakeLock用法
WakeLock的使用需要謹慎處理,使用不當會讓應用變成“電量殺手”。使用的原則
- 能不用就盡量別用:比如要保持屏幕長亮,應該用
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
的方式。
PowerManager.FULL_WAKE_LOCK會保持屏幕長亮,比如設置15s自動滅屏,當申請了該wakelock后,即使超過15s,依然不會滅屏。但用戶主動按power鍵,還是會滅屏的。
- 如果非要用,用完之后記得釋放。
申請WakeLock有兩種方式acquire()跟acquire(long timeout),后者相對更安全點,如果忘記了release WakeLock,經(jīng)過timeout的時長后系統(tǒng)會自動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();//為了保證任務不被系統(tǒng)休眠打斷,申請WakeLock
// 開始我們的任務
wl.release();//任務結束后釋放,如果不寫該句。則可以用wl.acquire(timeout)的方式把釋放的工作交給系統(tǒng)。
同時需要在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
沒毛病,已經(jīng)正常釋放掉。如果應用已經(jīng)運行到釋放wakelock的語句,但dumpsys出的信息仍然看到持有wakelock,這就是問題,需要我們去fix掉。
系統(tǒng)層debug
系統(tǒng)層面去debug wakelock相關問題就比較復雜了,基本上的步驟如下。
-
依據(jù)log分析
首先打開debug開關
frameworks/base/services/core/java/com/android/server/power/PowerManagerService.javaprivate 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=
后面的整數(shù)能將acquire跟release對應起來,在結合logcat的時間戳,就能得到wakelock持有的時長,短時間的一般不會有問題,那種長期持有wakelock的會導致功耗增加,一般是有問題的,需要根據(jù)tag
字段去代碼里查看具體的acquire/release過程。 系統(tǒng)運行一段時間后抓取bugreport.用historian工具查看wakelock申請情況。具體可以查看之前的博客Android battery historian功耗分析之環(huán)境搭建