你是否了解APP耗电问题?深入探索 Android 电量优化,flutter插件推荐
- 3)、最后,把電量輸送給電池,而整個(gè)降壓的過程中會(huì)產(chǎn)生熱能。
分類
-
1)、高壓低電流快充方案:在充電過程中提升充電電壓(7-20V)來提升充電功率。
-
2)、低壓大電流快充方案:在電壓一定情況下,增加電流,通常使用并聯(lián)電路的方式進(jìn)行分流。
-
3)、鋁-石墨烯超級(jí)電池
-
超高耐用性和安全性,快充充電1.1秒就能充滿電。
-
實(shí)驗(yàn)階段。
3、壽命
通常使用充電循環(huán)次數(shù)衡量。
4、安全性
嚴(yán)格控制電池容量,例如 VOOC 就使用了各種安全檢測技術(shù)。
5、電量和硬件
-
手機(jī)耗電是通過使用相應(yīng)的硬件模塊來消耗電能。
-
CPU、屏幕、WIFI、數(shù)據(jù)網(wǎng)絡(luò)、GPS、音視頻通話在日常耗電量中占比最大。
6、Android 耗電演進(jìn)
KITKAT
批處理傳感器
分批有效地收集和傳遞傳感器事件。
Alarm 對(duì)齊
批處理在合理的相似時(shí)間內(nèi)的所有應(yīng)用的鬧鈴,以便系統(tǒng)僅喚醒一次。
Lollipop
-
開啟 Volta 項(xiàng)目
-
Job Scheduler
-
dumpsys batterystats
-
Battery Historian
-
修復(fù) native fork 進(jìn)程?;畹?bug
Marshmallow
-
省電功能
-
Doze 低功耗模式
-
App Standby 應(yīng)用待機(jī)摸手機(jī)
Nougat
-
優(yōu)化省電功能
-
Doze 加強(qiáng)版
-
implicit broadcasts 顯示
-
混合編譯
Oreo
-
更多優(yōu)化省電功能
-
后臺(tái)執(zhí)行限制
-
后臺(tái)位置限制
P(電壓管理嚴(yán)格限制)
應(yīng)用待機(jī)分組(App Standby Bueckets)
-
從應(yīng)用安裝開始。
-
分組決定后臺(tái)被限制的程度。
-
不常用的應(yīng)用將被限制地更加嚴(yán)格。
應(yīng)用后臺(tái)限制(Background Restrictions)
-
用戶開啟。
-
停止后臺(tái)運(yùn)行。
-
提示用戶后臺(tái)耗電嚴(yán)重的應(yīng)用,用戶可選擇停止它們的后臺(tái)運(yùn)行。
省電模式(Battery Saver)
-
用戶開啟。
-
所有應(yīng)用進(jìn)入待機(jī)模式。
-
更加嚴(yán)格的后臺(tái)限制,而且無視應(yīng)用的 Target API。
三、電量檢測方案
=================================================================
對(duì)于電量的統(tǒng)計(jì)有一個(gè)公式,如下所示:
模塊電量(mAh) = 模塊電流(mA)* 模塊耗時(shí)(h)
Android 系統(tǒng)要求 ROM 廠商必須在 /frameworks/base/core/res/res/xml/power_profile.xml 提供組件的電源配置文件。而 Android 系統(tǒng)的電量計(jì)算 PowerProfile 正是通過讀取 power_profile.xml 的數(shù)據(jù)。
1、設(shè)置—耗電排行
-
1)、直觀,但沒有詳細(xì)數(shù)據(jù),對(duì)解決問題幫助不大。
-
2)、需要找特定場景專項(xiàng)測試,比如在某一個(gè)界面操作一段時(shí)間,然后來判斷這個(gè)頁面是否耗電。
2、使用廣播監(jiān)聽電量變化—ACTION_BATTERY_CHANGED
獲取電池電量、充電狀態(tài)、電池狀態(tài)等信息。
實(shí)戰(zhàn)案例
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
Intent intent = registerReceiver(null, filter);
LogUtils.i("battery " + intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1));
缺點(diǎn)
-
1)、價(jià)值不大:針對(duì)手機(jī)整體的耗電量,而非單個(gè) App。
-
2)、實(shí)時(shí)性差、精度較低,被動(dòng)通知。
3、dumpsys batterystats
batterystats 是 Android 5.0 提供的工具,它可以獲取各個(gè) App 的 WakeLock、CPU 時(shí)間占用等信息,同時(shí)增加了一個(gè) Estimated power use(mAh)功能,預(yù)估耗電量。
作用
將電量測量轉(zhuǎn)化為功能模塊的使用時(shí)間或者次數(shù)。
adb shell dumpsys batterystats > battery.txt
在 battery.txt 搜索 ‘Estimated power use’ 關(guān)鍵字,下面粗略統(tǒng)計(jì)了各個(gè) Uid 的總耗電量。
Estimated power use (mAh):
Capacity: 3350, Computed drain: 2767, actual drain: 3752-3853
Uid 1000: 1014 ( cpu=999 wake=1.36 radio=11.4 wifi=1.24 gps=0.435 sensor=0.808 ) Excluded from smearing
Unaccounted: 985 ( ) Including smearing: 0 ( ) Excluded from smearing
Uid 0: 416 ( cpu=157 wake=210 radio=38.8 wifi=9.51 ) Excluded from smearing
…
batterystats 所記錄的電量統(tǒng)計(jì)數(shù)據(jù)源自于 BatteryStatsService-電量統(tǒng)計(jì)服務(wù),其實(shí)現(xiàn)類為 BatteryStatsImpl,內(nèi)部正是使用的 PowerProfile 。
BatteryStatsImpl 為每一個(gè)應(yīng)用創(chuàng)建與之對(duì)應(yīng)的 UID 來監(jiān)控器系統(tǒng)資源的使用情況,其統(tǒng)計(jì)了 12 大模塊的電量消耗,如下所示:
-
Camera、Audio、Video
-
Bluetooth、Network、Wakelock
-
Sensor、Radio、Screen
-
WIFI、CPU、GPS
4、Battery Historian
特點(diǎn)
-
1)、查看自設(shè)備上次充電以來各種匯總統(tǒng)計(jì)信息,而且可以選擇對(duì)應(yīng)的 App 查看詳細(xì)信息。
-
2)、可視化展示指標(biāo):
-
耗電比例。
-
執(zhí)行時(shí)間、次數(shù)。
-
3)、僅適合線下使用。
安裝
-
1)、安裝 Docker
-
2)、docker – run -p:9999 gcr.io/android-battery-historian/stable:3.0 --port 9999 (需要翻墻)
導(dǎo)出電量信息
-
1)、使用 batterystats 命令重置手機(jī)電量:adb shell dumpsys batterystats --reset
-
2)、使用 batterystats 命令獲取電池?cái)?shù)據(jù)權(quán)限并開啟記錄全面的電量信息:adb shell dumpsys batterystats --enable full-wake-history
-
3)、測試完成后,使用 bugreport 導(dǎo)出電量信息:
-
7.0和7.0以后:adb bugreport bugreport.zip
-
6.0和6.0之前:adb bugreport > bugreport.txt
-
通過 historian 圖形化展示結(jié)果:python historian.py -a bugreport.txt > battery.html
上傳分析
- 1)、打開 http://localhost:
如果打不開,可以使用備用網(wǎng)站 https://bathist.ef.lc/
- 2)、上傳 bugreport 文件,點(diǎn) Submit 提交即可。
Battery Historian 數(shù)據(jù)分析
Hitorian V2 — 電量統(tǒng)計(jì)圖表
Add Metrics
習(xí)筆記總結(jié)+最新移動(dòng)架構(gòu)視頻+大廠安卓面試真題+項(xiàng)目實(shí)戰(zhàn)源碼講義》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整內(nèi)容開源分享
img.cn/img_convert/08cae2f8ea15e60a693875b47a2560fe.png)
在 Add Metrics 中我們可以增加更多的測量項(xiàng)。
CPU running
如果一直處于 running,則表明電量消耗比較高。
JobScheduler
選中 Job Scheduler 的某一個(gè)工作時(shí)間片,我們可以查看具體的 發(fā)生的時(shí)間、耗時(shí)以及次數(shù),最重要的是它統(tǒng)計(jì)出來了是哪一個(gè)進(jìn)程在使用這個(gè) JobScheduler。
App Selection
-
1)、選擇要分析電量的指定 App。
-
2)、點(diǎn)擊右邊區(qū)域的 System Stats 一欄可以在下方查看各個(gè)系統(tǒng)組件的電量百分比消耗詳情,例如 Userspace Wakelocks。
主入口處的 Switch to Bugreport Comparison
選擇多個(gè)文件進(jìn)行上傳對(duì)比。
5、電量專項(xiàng)測試
1)、耗電場景測試
-
復(fù)雜計(jì)算。
-
音視頻播放。
2)、傳感器相關(guān)
-
使用時(shí)長
-
耗電量
-
發(fā)熱
3)、后臺(tái)靜默測試
四、耗電優(yōu)化
===============================================================
1、耗電優(yōu)化的難點(diǎn)
-
1)、「缺乏現(xiàn)場,無法復(fù)現(xiàn)」。
-
2)、「信息不全,難以定位」。
-
3)、「無法評(píng)估結(jié)果」。
在 App 開發(fā)中,經(jīng)常會(huì)由于某個(gè)需求場景或 代碼 bug 而導(dǎo)致大量耗電。
2、后臺(tái)調(diào)度任務(wù)省電
思考步驟
-
需要后臺(tái)運(yùn)行
-
長時(shí)間下載:DownloadManager
-
數(shù)據(jù)同步:SyncAdapter
-
本地任務(wù):JobScheduler
-
特定時(shí)間執(zhí)行:AlarmManager
-
實(shí)時(shí)通信:推送服務(wù)
-
立刻執(zhí)行:Foreground Service
對(duì)于耗電優(yōu)化中,我們最常用的就是 JobScheduler,下面👇,我們來實(shí)戰(zhàn)一下。
Job Scheduler 實(shí)戰(zhàn)
/**
- 開啟 JobScheduler
*/
private void startJobScheduler() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), JobSchedulerService.class.getName()));
// 設(shè)置僅在 充電和WIFI 下才使用 JobScheduler 進(jìn)行批量任務(wù)處理
builder.setRequiresCharging(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
jobScheduler.schedule(builder.build());
}
}
其中,「JobSchedulerService 就是用于進(jìn)行批量任務(wù)處理的服務(wù)」,示例代碼如下所示:
/**
- 用于進(jìn)行批量任務(wù)處理的 JobSchedulerService
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class JobSchedulerService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
// 此處執(zhí)行在主線程
// 模擬一些處理:批量網(wǎng)絡(luò)請(qǐng)求,APM日志上報(bào)
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}
特點(diǎn)
-
1)、「僅支持 API 21 及之上」。
-
2)、「在符合某些條件時(shí)創(chuàng)建執(zhí)行在后臺(tái)的任務(wù)」。
-
3)、「把不緊急的任務(wù)放到更合適的時(shí)機(jī)批量處理」。
符合 Android 規(guī)則,手機(jī)在充電狀態(tài)才去做耗電工作。示例代碼如下所示:
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);
//獲取用戶是否在充電的狀態(tài)或者已經(jīng)充滿電了
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;
3、電量優(yōu)化套路總結(jié)
1、優(yōu)化應(yīng)用的后臺(tái)耗電
避免后臺(tái)長時(shí)間獲取 WakeLock、WIFI 和藍(lán)牙的掃描等。
2、符合系統(tǒng)的耗電規(guī)則
Android P 使用了 Android Vitals 監(jiān)控后臺(tái)耗電,其規(guī)則如下所示:
-
1)、Alarm Manager wakeup 喚醒過多:當(dāng)手機(jī)不在充電狀態(tài),每小時(shí) wakeup 喚醒次數(shù)大于 10 次。
-
2)、頻繁使用局部喚醒鎖:當(dāng)手機(jī)不在充電狀態(tài),partial wake lock 持有超過1小時(shí)。
-
3)、后臺(tái)網(wǎng)絡(luò)使用量過高:當(dāng)手機(jī)不在充電狀態(tài)而且應(yīng)用在后臺(tái),每小時(shí)網(wǎng)絡(luò)使用量超過 50MB。
-
4)、后臺(tái) WiFi scans 過多:當(dāng)手機(jī)不在充電狀態(tài)而且應(yīng)用在后臺(tái),每小時(shí)大于4次 WiFi scans。
3、CPU 時(shí)間片
「Android 手機(jī)保護(hù) AP 和 BP 兩個(gè) CPU。AP 即 Application Processor,所有的用戶界面以及 App 都是運(yùn)行在 AP 上的。BP 即 Baseband Processor,手機(jī)射頻都是運(yùn)行在這個(gè) CPU 上的。而一般我們所說的耗電,PowerProfile 文件里面的 CPU,指的是 AP」。
CPU 耗電通常有兩種情況:
-
1)、「長期頻繁喚醒:原本可以僅僅在 BP 上運(yùn)行,消耗 5mA 左右,但是因?yàn)閱拘?#xff0c;AP 就會(huì)運(yùn)作,不同手機(jī)情況不一樣,至少會(huì)導(dǎo)致 20~30 mA 左右的耗電」。
-
2)、「CPU 長期高負(fù)荷:例如 App 退到后臺(tái)的時(shí)候沒有停止動(dòng)畫,或者程序有不退出的死循環(huán)等等,導(dǎo)致 CPU 滿頻、滿核地跑」。
常用優(yōu)化 CPU 時(shí)間片的方式有:
-
1)、「獲取運(yùn)行過程線程 CPU 消耗,定位 CPU 占用率異常方法」。
-
2)、「減少后臺(tái)應(yīng)用的主動(dòng)運(yùn)行」。
4、網(wǎng)絡(luò)相關(guān)
通常情況下,使用 WIFI 連接網(wǎng)絡(luò)時(shí)的功耗要低于使用移動(dòng)網(wǎng)絡(luò)的功耗。而使用移動(dòng)網(wǎng)絡(luò)傳輸數(shù)據(jù),電量的消耗有以下3種狀態(tài):
-
「Full power:高功率狀態(tài),移動(dòng)網(wǎng)絡(luò)連接被激活,允許設(shè)備以最大的傳輸速率進(jìn)行操作」。
-
「Low power:低功耗狀態(tài),對(duì)電量的消耗差不多是 Full power 狀態(tài)下的 50%」。
-
「Standby:空閑態(tài),沒有數(shù)據(jù)連接需要傳輸,電量消耗最少」。
因此,為了避免網(wǎng)絡(luò)連接所帶來的電量消耗,我們可以采用如下幾種方案:
-
1)、盡量在 WIFI 環(huán)境下進(jìn)行數(shù)據(jù)傳輸,在使用 WIFI 傳輸數(shù)據(jù)時(shí),應(yīng)該盡可能增大每個(gè)包的大小(不超過 MTU),并降低發(fā)包的頻率。
-
2)、在蜂窩移動(dòng)網(wǎng)絡(luò)下需要對(duì)請(qǐng)求時(shí)機(jī)及次數(shù)控制:可以延遲執(zhí)行的網(wǎng)絡(luò)請(qǐng)求稍后一起發(fā)送,最好做到批量執(zhí)行,盡量避免頻繁的間隔網(wǎng)絡(luò)請(qǐng)求,以盡量多地保持在 Radio Standby 狀態(tài)。
-
3)、使用 JSON 和 Protobuf 進(jìn)行數(shù)據(jù)壓縮,減少時(shí)間。
-
4)、禁止使用輪詢功能:輪詢會(huì)導(dǎo)致網(wǎng)絡(luò)請(qǐng)求一直處于被激活的狀態(tài),耗電過高。
5、定位相關(guān)
-
1)、「根據(jù)場景謹(jǐn)慎選擇定位模式:對(duì)定位準(zhǔn)確度沒那么高的場景可以選擇低精度模式」。
-
2)、「可以考慮網(wǎng)絡(luò)定位代替 GPS」。
-
3)、「使用后務(wù)必及時(shí)關(guān)閉,減少更新頻率,例如定位開啟一定時(shí)間后超過某個(gè)閾值可以執(zhí)行一個(gè)兜底策略:強(qiáng)制關(guān)閉 GPS」。
6、界面相關(guān)
-
1)、「離開界面后停止相關(guān)活動(dòng),例如關(guān)閉動(dòng)畫」。
-
2)、「耗電操作判斷前后臺(tái),如果是后臺(tái)則不執(zhí)行相關(guān)操作」。
7、WakeLock 相關(guān)
WakeLock 常用于后臺(tái)播放音視頻、錄制音視頻、下載文件的情況。如果沒有合理使用 WakeLock,則會(huì)造成嚴(yán)重的耗電問題,為了避免該問題,「我們應(yīng)該定期針對(duì)使用了 WakeLock 的模塊進(jìn)行重點(diǎn)排查」。
我們可以使用 adb shell dumpsys power 命令查看系統(tǒng)當(dāng)前的耗電信息,其中我們可以看到 WakeLock 列表,它通常會(huì)以**「”mLocks.size“ 或者 ”Wake Locks:size“」** 開頭。關(guān)于 WakeLock 的使用我們要著重注意以下幾點(diǎn):
-
1)、「注意成對(duì)使用 acquire、release」。
-
2)、「建議使用帶參數(shù)的 acquire,避免沒有及時(shí)釋放而導(dǎo)致電量消耗過大」。
-
3)、「使用 finally 確保 release 一定會(huì)被調(diào)用」。
-
4)、「常亮場景使用 keepScreenOn 即可」。
-
5)、「WakeLock 有一個(gè)接口 setReferenceCounted,用來設(shè)置 WakeLock 的技術(shù)機(jī)制,官方默認(rèn)為計(jì)數(shù)。true 為計(jì)數(shù),false 為不計(jì)數(shù)。所謂計(jì)數(shù)即每一個(gè) acquire 必須對(duì)應(yīng)一個(gè) release;不計(jì)數(shù)則是無論有多少個(gè) acquire,一個(gè) release 就可以釋放。但是問題是有的第三方 ROM 它將默認(rèn)設(shè)置為了不計(jì)數(shù),以為我們需要在調(diào)用 newWakeLock 之后再調(diào)用 setReferenceCounted 為 false」。
8、計(jì)算優(yōu)化
「浮點(diǎn)運(yùn)算比整數(shù)運(yùn)算更消耗 CPU 時(shí)間片,因此耗電也會(huì)增加」。避開浮點(diǎn)運(yùn)算的優(yōu)化方法如下所示:
-
1)、「除法變乘法」。
-
2)、「充分利用移位」。
-
3)、「在 native 層開發(fā)時(shí),可以利用 ARM neon 指令集做并行運(yùn)算,注意需要 ARM V7 及以上架構(gòu) CPU 才能支持」。
9、滅屏?xí)r停止動(dòng)畫
「我們可以監(jiān)聽滅屏以及亮屏的廣播,在滅屏的時(shí)候停止 surfaceView 的動(dòng)畫繪制。在亮屏的時(shí)候,恢復(fù)動(dòng)畫的繪制」。
五、耗電監(jiān)控
===============================================================
以后臺(tái)耗電監(jiān)控為主,必須監(jiān)控的模塊有:
-
1)、「Alarm wakeup」
-
2)、「WakeLock」
-
3)、「WiFi scans」
-
4)、「Network」
「必須監(jiān)控的現(xiàn)場信息有」 :
-
1)、「堆棧信息」
-
2)、「是否充電」
-
3)、「電量水平」
-
4)、「應(yīng)用前后臺(tái)時(shí)間」
-
5)、「CPU 狀態(tài)信息」
最后,我們需要 「提煉規(guī)則,將監(jiān)控內(nèi)容 => 抽象成規(guī)則」。
1、Java Hook
我們可以通過代理對(duì)應(yīng)的 Service 實(shí)現(xiàn),完成收集 Wakelock、Alarm、GPS 的申請(qǐng)堆棧、釋放信息、手機(jī)充電狀態(tài)等等。
?
示例項(xiàng)目
?
2、電量輔助監(jiān)控實(shí)戰(zhàn)
1)、獲取運(yùn)行時(shí)能耗文件
-
1)、adb pull /system/framework/framework-res.apk
-
2)、反編譯,xml—》power_profile
2)、電量輔助監(jiān)控
線下使用 epic 進(jìn)行 AOP 電量輔助統(tǒng)計(jì)
這里我們就以 WakeLock 的監(jiān)控為例,切面代碼如下所示:
public static long sStartTime = 0;
@Insert(value = “acquire”)
@TargetClass(value = “com.optimize.performance.wakelock.WakeLockUtils”,scope = Scope.SELF)
public static void acquire(Context context){
trace = Log.getStackTraceString(new Throwable());
sStartTime = System.currentTimeMillis();
Origin.callVoid();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
WakeLockUtils.release();
}
},1000);
}
@Insert(value = “release”)
@TargetClass(value = “com.optimize.performance.wakelock.WakeLockUtils”,scope = Scope.SELF)
public static void release(){
LogUtils.i(“PowerManager “+(System.currentTimeMillis() - sStartTime)+”/n”+trace);
此外,我們也可以利用 epic 來監(jiān)控每個(gè)線程的執(zhí)行時(shí)間,超過閾值則警告,示例代碼如下所示:
public static long runTime = 0;
@Insert(value = “run”)
@TargetClass(value = “java.lang.Runnable”,scope = Scope.ALL)
public void run(){
runTime = System.currentTimeMillis();
Origin.callVoid();
LogUtils.i("runTime "+(System.currentTimeMillis() - runTime));
}
3、編譯插樁
「寫一個(gè)基礎(chǔ)類,然后在統(tǒng)一的調(diào)用接口中添加監(jiān)控邏輯」。這里我們可以參考 Facebook Battery-Metrics 獲取、監(jiān)控?cái)?shù)據(jù)的方式。其代碼如下所示:
public class WakelockMetrics {
/**
-
獲取 WakeLock
-
@param wakeLock WakeLock
-
@param timeout 超時(shí)時(shí)間
*/
public static void acquire(PowerManager.WakeLock wakeLock, long timeout) {
wakeLock.acquire(timeout);
// 監(jiān)控 wakelock 相關(guān)信息
Log.e(“HOOOOOOOOK”, “–acquireWakeLock–”);
總結(jié)
以上是生活随笔為你收集整理的你是否了解APP耗电问题?深入探索 Android 电量优化,flutter插件推荐的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2018年成功IPO的科技独角兽里,你最
- 下一篇: Flutter插件开发--获取Andro