APP的噩夢(mèng)——“XXX正在后臺(tái)高耗電”
幾個(gè)場(chǎng)景
場(chǎng)景一:查查資料需纳,看個(gè)視頻芦倒,突然提示我:微信正在后臺(tái)高耗電!想想不翩,微信嘛兵扬,高耗電太正常了,把通知一關(guān)口蝠,該干啥干啥器钟。
場(chǎng)景二:支付寶付完款,放后臺(tái)干別的妙蔗,突然提示我:支付寶正在后臺(tái)高耗電傲霸!想想,支付寶高耗電也正常眉反,把進(jìn)程一殺昙啄,該干啥干啥。
場(chǎng)景三:要去哪個(gè)地方寸五,地圖導(dǎo)航完梳凛,放后臺(tái)。過段時(shí)間提示又來了:XX地圖正在后臺(tái)高耗電播歼!殺掉進(jìn)程,該干啥干啥掰读。
場(chǎng)景四:剛用完哪個(gè)不知名小APP秘狞,放后臺(tái),刷完朋友圈放松一下蹈集。我擦烁试,XXX正在高耗電,你誰(shuí)啊就敢耗我的電池拢肆,找出來减响,卸載靖诗。
某不知名APP開發(fā)者哭暈在廁所……
高耗電的危害
很多APP都遇到過后臺(tái)耗電的窘境,莫名其妙不知道發(fā)生了啥就被系統(tǒng)給黃牌警告支示。被提示耗電量高刊橘,會(huì)顯著降低用戶好感度,增加用戶卸載率颂鸿。一旦卸載促绵,任憑你功能多牛叉,體驗(yàn)多優(yōu)秀嘴纺,一切玩完败晴。
噩夢(mèng)的源頭——“Talk is cheap. Show me the code.”
要解決高耗電這一問題,得從源頭來分析栽渴。APP耗電分哪些部分尖坤,各是怎么計(jì)算的,才好對(duì)癥下藥闲擦,遠(yuǎn)離系統(tǒng)的黃牌警告慢味。
以android 25的代碼為例,計(jì)算APP耗電量的核心代碼是這個(gè)類——BatteryStatsHelper佛致,接下來我們一步步來分析贮缕。
里面有啥?
我們來看一下這個(gè)類有哪些成員變量俺榆,這個(gè)類的成員變量不多感昼,比較重要的有這些。
PowerCalculator mCpuPowerCalculator;
PowerCalculator mWakelockPowerCalculator;
MobileRadioPowerCalculator mMobileRadioPowerCalculator;
PowerCalculator mWifiPowerCalculator;
PowerCalculator mBluetoothPowerCalculator;
PowerCalculator mSensorPowerCalculator;
PowerCalculator mCameraPowerCalculator;
PowerCalculator mFlashlightPowerCalculator;
看這些變量名罐脊,是不是察覺到了什么定嗓?對(duì),這是系統(tǒng)對(duì)于不同維度耗電量的計(jì)算工具萍桌。
private PowerProfile mPowerProfile;
這個(gè)是對(duì)系統(tǒng)整個(gè)耗電情況的一個(gè)描述宵溅,比如CPU各頻段功耗、WIFI耗電情況上炎、藍(lán)牙耗電情況等恃逻。
private BatteryStats mStats;
這個(gè)成員提供了了對(duì)電源各項(xiàng)參數(shù)的訪問接口,諸如各進(jìn)程在cpu藕施、wakelock等方面的詳細(xì)耗電情況寇损,可以將耗電情況細(xì)化到進(jìn)程級(jí)別。
其余成員變量較多裳食,不再一一贅述矛市,有興趣的可以查看一下源碼。
耗電是咋計(jì)算的诲祸?
可以看到這個(gè)類對(duì)外提供了一系列的refreshStats()方法浊吏,跟進(jìn)查看一下而昨。
public void refreshStats(int statsType, SparseArray asUsers, long rawRealtimeUs,?long rawUptimeUs) {
? ? //初始化電源信息,各維度電量Calculator找田,以及時(shí)間參數(shù)
? ? …………
? ? //注意這個(gè)方法里會(huì)去詳細(xì)計(jì)算各維度的耗電量
? ? processAppUsage(asUsers);
? ? //計(jì)算完成后各uid的排序歌憨,以及格式化等操作
? ? …………
}
再看processAppUsage()方法
private void processAppUsage(SparseArray asUsers) {
? ? …………
? ? for(int iu =0;iu < NU; iu++) {
? ? ? ? finalUid u = uidStats.valueAt(iu);
? ? ? ? finalBatterySipper app =newBatterySipper(BatterySipper.DrainType.APP,u,0);
? ? ? ? mCpuPowerCalculator.calculateApp(app,u,mRawRealtimeUs,mRawUptimeUs,mStatsType);
? ? ? ? mWakelockPowerCalculator.calculateApp(app,u,mRawRealtimeUs,mRawUptimeUs,mStatsType);
? ? ? ? mMobileRadioPowerCalculator.calculateApp(app,u,mRawRealtimeUs,mRawUptimeUs,mStatsType);
? ? ? ? mWifiPowerCalculator.calculateApp(app,u,mRawRealtimeUs,mRawUptimeUs,mStatsType);
? ? ? ? mBluetoothPowerCalculator.calculateApp(app,u,mRawRealtimeUs,mRawUptimeUs,mStatsType);
? ? ? ? mSensorPowerCalculator.calculateApp(app,u,mRawRealtimeUs,mRawUptimeUs,mStatsType);
? ? ? ? mCameraPowerCalculator.calculateApp(app,u,mRawRealtimeUs,mRawUptimeUs,mStatsType);
? ? ? ? mFlashlightPowerCalculator.calculateApp(app,u,mRawRealtimeUs,mRawUptimeUs,mStatsType);
? ? ? ? …………
? ? ?}
? ? …………
}
可以看到,這里針對(duì)每個(gè)uid午阵,都計(jì)算了CPU/wakelock/移動(dòng)數(shù)據(jù)/WIFI數(shù)據(jù)/藍(lán)牙/傳感器/攝像頭/閃光燈八個(gè)維度的詳細(xì)電量信息躺孝,并做了排序。
水落石出
看到這里底桂,一切就清楚了植袍。APP耗電高,一定是這八個(gè)維度的某一方面消耗了大量電量籽懦。
(這里僅分析了APP耗電量的來源于个,具體的計(jì)算方法有興趣的讀者可以自行查看源碼)
捻手搭脈,對(duì)癥下藥
CPU耗電
CPU耗電主要分為以下幾個(gè)場(chǎng)景
進(jìn)程被頻繁拉起
大多數(shù)場(chǎng)景是存在不恰當(dāng)?shù)撵o態(tài)廣播注冊(cè)暮顺,導(dǎo)致進(jìn)程不斷被廣播拉起厅篓,產(chǎn)生一系列的初始化操作,不久又被ROM的省電策略所清理捶码,大量初始化不僅會(huì)造成大量CPU耗電羽氮,還有可能被ROM記錄為頻繁自啟甚至提示用戶,降低用戶好感度惫恼。
不恰當(dāng)?shù)膶?duì)外提供的ContentProvider與Service也存在這個(gè)問題档押。
如何定位:不同廠商的ROM有不同的啟動(dòng)日志,觀察APP的啟動(dòng)情況以及啟動(dòng)原因祈纯。
解決辦法:分析梳理上述場(chǎng)景令宿,并根據(jù)實(shí)際業(yè)務(wù)情況做清理與優(yōu)化。
高CPU占用率
由于CPU耗電分為多個(gè)不同的等級(jí)腕窥,低占用率耗電量比高占用率要低很多粒没。而在一些業(yè)務(wù)場(chǎng)景中,會(huì)存在非常高的CPU占用簇爆,比如大量IO/快速循環(huán)等癞松,會(huì)產(chǎn)生很高的耗電。
如何定位:利用top命令查看當(dāng)前包名下所有線程的CPU占用率入蛆,找出占用率很高的線程响蓉,分析這個(gè)線程在做什么。
解決辦法:根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景做優(yōu)化(利用緩存機(jī)制減少IO的次數(shù)安寺,快速循環(huán)里增加延時(shí)厕妖,或者直接重構(gòu)等)首尼。
低CPU占用率
這個(gè)場(chǎng)景比較復(fù)雜挑庶,比如有的動(dòng)畫機(jī)制是基于循環(huán)Handler的Message言秸,導(dǎo)致每隔一小段時(shí)間就會(huì)有CPU活動(dòng),久而久之也是不小的耗電量迎捺。
如何定位:這種場(chǎng)景下CPU占用率比較低举畸,無法直接找出對(duì)應(yīng)線程〉手Γ可采用MAT內(nèi)存分析工具抄沮,查看當(dāng)前內(nèi)存內(nèi)是否存在可疑的東西,比如大量堆積的Message等岖瑰。
解決辦法:按照實(shí)際問題與業(yè)務(wù)場(chǎng)景來做優(yōu)化叛买。
移動(dòng)數(shù)據(jù)&WIFI耗電
這兩個(gè)統(tǒng)稱流量耗電,耗電場(chǎng)景如下:
圖片/視頻加載
這是最常見的耗電場(chǎng)景蹋订,但是沒有什么特別好的辦法來解決率挣,如果圖片是由自身服務(wù)器配置的話,可以通過優(yōu)化圖片大小來減少流量消耗露戒。另外WIFI耗電是小于移動(dòng)數(shù)據(jù)耗電的椒功,大的圖片可以考慮在WIFI網(wǎng)絡(luò)下去加載。
服務(wù)器網(wǎng)絡(luò)請(qǐng)求
這種場(chǎng)景需要查看與服務(wù)器請(qǐng)求的時(shí)機(jī)是否不合理智什,或者過多动漾,服務(wù)器返回的消息內(nèi)容是否過長(zhǎng),并做相應(yīng)優(yōu)化荠锭。
其他耗電場(chǎng)景
其他的場(chǎng)景視各業(yè)務(wù)實(shí)際場(chǎng)景而定旱眯,整體思路是優(yōu)化耗電動(dòng)作的時(shí)機(jī),減少耗電動(dòng)作的次數(shù)节沦。
從噩夢(mèng)中醒來——“后記”
負(fù)責(zé)項(xiàng)目功耗專項(xiàng)也有大半年了键思,一開始手足無措,后來慢慢理清思路甫贯,開始鉆研源碼吼鳞,把耗電算法理解透。最后再分析代碼叫搁,從CPU/流量維度討論出了優(yōu)化方案赔桌,并最終修改解決。
最大的收獲是在源碼面前渴逻,一切BUG都無所遁形疾党。
關(guān)于耗電量的算法,這位仁兄研究要比我透徹得多惨奕,參考