生活總是讓我們遍體鱗傷烹困,但到后來(lái)晶渠,那些受傷的地方一定會(huì)變成我們最強(qiáng)壯的地方。---海明威
WakeLock是什么
WakeLock是Android框架層提供的一套機(jī)制曲伊,應(yīng)用使用該機(jī)制可以達(dá)到控制Android設(shè)備狀態(tài)的目的叽讳。這里的設(shè)備狀態(tài)主要指屏幕的打開(kāi)關(guān)閉,cpu的保持運(yùn)行坟募。簡(jiǎn)單的理解WakeLock是讓系統(tǒng)保持"清醒"的一種手段.
WakeLock作用
當(dāng)手機(jī)滅屏狀態(tài)下保持一段時(shí)間后岛蚤,系統(tǒng)會(huì)進(jìn)入休眠,一些后臺(tái)運(yùn)行的任務(wù)就可能得不到正常執(zhí)行懈糯,比如網(wǎng)絡(luò)下載中斷涤妒,后臺(tái)播放音樂(lè)暫停等。WakeLock正是為了解決這類問(wèn)題赚哗,應(yīng)用只要申請(qǐng)了WakeLock她紫,那么在釋放WakeLock之前硅堆,系統(tǒng)不會(huì)進(jìn)入休眠,即使在滅屏的狀態(tài)下贿讹,應(yīng)用要執(zhí)行的任務(wù)依舊不會(huì)被系統(tǒng)打斷渐逃。
WakeLock有那些分類
WakeLock是PowerManager的內(nèi)部類,其代碼路徑位于:
frameworks/base/core/java/android/os/PowerManager.java
WakeLock 分類如下:
- PARTIAL_WAKE_LOCK: 滅屏民褂,關(guān)閉鍵盤背光的情況下茄菊,CPU依然保持運(yùn)行。
- PROXIMITY_SCREEN_OFF_WAKE_LOCK: 基于距離感應(yīng)器熄滅屏幕赊堪。最典型的運(yùn)用場(chǎng)景是我們貼近耳朵打電話時(shí)面殖,屏幕會(huì)自動(dòng)熄滅。
- SCREEN_DIM_WAKE_LOCK/SCREEN_BRIGHT_WAKE_LOCK/FULL_WAKE_LOCK:這三種WakeLock都已經(jīng)過(guò)時(shí)了雹食,它們的目的是為了保持屏幕長(zhǎng)亮畜普,Android官方建議用
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
方式替換。因?yàn)楸绕鹕暾?qǐng)WakeLock,這種方式更簡(jiǎn)單群叶,還不需要特別申請(qǐng)android.permission.WAKE_LOCK
權(quán)限吃挑。 - DOZE_WAKE_LOCK/DRAW_WAKE_LOCK: 隱藏的分類,系統(tǒng)級(jí)別才會(huì)用到街立。
WakeLock的flag如下:
- ACQUIRE_CAUSES_WAKEUP: 點(diǎn)亮屏幕舶衬,比如應(yīng)用接收到通知后,屏幕亮起。
- ON_AFTER_RELEASE: 釋放WakeLock后跛锌,屏幕不馬上熄滅舱呻。
- UNIMPORTANT_FOR_LOGGING: 隱藏的flag,系統(tǒng)級(jí)別才會(huì)用到虽画。
WakeLock的設(shè)置過(guò)程
WakeLock從用戶空間下發(fā)設(shè)置操作,然后進(jìn)入kernel空間荣病,最終寫入到了/sys/power/wake_lock
文件節(jié)點(diǎn)码撰。
下面來(lái)從源碼的角度跟蹤下acquire WakeLock的過(guò)程。
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é)點(diǎn)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]; //這個(gè)節(jié)點(diǎn)就是/sys/power/wake_lock ret = write(fd, id, strlen(id)); if (ret < 0) { return -errno; } return ret; }
WakeLock用法
WakeLock的使用需要謹(jǐn)慎處理个盆,使用不當(dāng)會(huì)讓應(yīng)用變成“電量殺手”脖岛。使用的原則
- 能不用就盡量別用:比如要保持屏幕長(zhǎng)亮,應(yīng)該用
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
的方式颊亮。
PowerManager.FULL_WAKE_LOCK會(huì)保持屏幕長(zhǎng)亮柴梆,比如設(shè)置15s自動(dòng)滅屏,當(dāng)申請(qǐng)了該wakelock后终惑,即使超過(guò)15s绍在,依然不會(huì)滅屏。但用戶主動(dòng)按power鍵,還是會(huì)滅屏的揣苏。
- 如果非要用悯嗓,用完之后記得釋放。
申請(qǐng)WakeLock有兩種方式acquire()跟acquire(long timeout)卸察,后者相對(duì)更安全點(diǎn)脯厨,如果忘記了release WakeLock,經(jīng)過(guò)timeout的時(shí)長(zhǎng)后系統(tǒng)會(huì)自動(dò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();//為了保證任務(wù)不被系統(tǒng)休眠打斷合武,申請(qǐng)WakeLock
// 開(kāi)始我們的任務(wù)
wl.release();//任務(wù)結(jié)束后釋放,如果不寫該句涡扼。則可以用wl.acquire(timeout)的方式把釋放的工作交給系統(tǒng)稼跳。
同時(shí)需要在Manifest文件中添加權(quán)限
<uses-permission android:name="android.permission.WAKE_LOCK."/>
WakeLock相關(guān)問(wèn)題的debug方法
應(yīng)用層debug
如果只是單純的查看某一個(gè)應(yīng)用的wakelock是否存在非正常釋放的情況,可以用命令
$ adb shell dumpsys power|grep -i wake
來(lái)查看吃沪,比如下面的示例代碼
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();
}
}
當(dāng)進(jìn)入onResume方法汤善,此時(shí)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
通過(guò) PARTIAL_WAKE_LOCK 'azhengye-test-wakelock' ON_AFTER_RELEASE ACQ=-4m52s949ms LONG (uid=10124 pid=31473 pkg=com.azhengye.testpath)
可以看到wakelock申請(qǐng)成功了。
如果進(jìn)入onPause方法票彪,剛申請(qǐng)的wakelock應(yīng)該被釋放掉红淡,此時(shí)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
沒(méi)毛病,已經(jīng)正常釋放掉降铸。如果應(yīng)用已經(jīng)運(yùn)行到釋放wakelock的語(yǔ)句在旱,但dumpsys出的信息仍然看到持有wakelock,這就是問(wèn)題推掸,需要我們?nèi)ix掉桶蝎。
系統(tǒng)層debug
系統(tǒng)層面去debug wakelock相關(guān)問(wèn)題就比較復(fù)雜了,基本上的步驟如下谅畅。
-
依據(jù)log分析
首先打開(kāi)debug開(kāi)關(guān)
frameworks/base/services/core/java/com/android/server/power/PowerManagerService.javaprivate static final boolean DEBUG = true;
然后dumpsys power查看登渣,同時(shí)在logcat里搜索
acquireWakeLockInternal
關(guān)鍵字查看申請(qǐng)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對(duì)應(yīng)起來(lái)绍豁,在結(jié)合logcat的時(shí)間戳,就能得到wakelock持有的時(shí)長(zhǎng)牙捉,短時(shí)間的一般不會(huì)有問(wèn)題,那種長(zhǎng)期持有wakelock的會(huì)導(dǎo)致功耗增加敬飒,一般是有問(wèn)題的邪铲,需要根據(jù)tag
字段去代碼里查看具體的acquire/release過(guò)程。 系統(tǒng)運(yùn)行一段時(shí)間后抓取bugreport.用historian工具查看wakelock申請(qǐng)情況无拗。具體可以查看之前的博客Android battery historian功耗分析之環(huán)境搭建