手機各個硬件模塊的耗電量是不同的,有些模塊很是耗電,而有些模塊則相對顯得耗電量小不少。android
電量消耗的計算與統計是一件麻煩並且矛盾的事情,記錄電量消耗自己也是一個費電量的事情。惟一可行的方案是使用第三方監測電量的設備,這樣纔可以獲取到真實的電量消耗。git
當設備處於待機狀態時消耗的電量是極少的,以 Nexus 5 爲例,打開飛行模式,能夠待機接近 1 個月。但是點亮屏幕,味着系統的各組件要開始進行工做,界面也須要開始執行渲染,這會須要消耗不少電量。github
一般狀況下,使用移動網絡傳輸數據,電量的消耗有三種狀態:web
能量最高的狀態,移動網絡鏈接被激活,容許設備以最大的傳輸速率進行操做。面試
一種中間狀態,對電量的消耗差很少是 Full power 狀態下的 50%。docker
最低的狀態,沒有數據鏈接須要傳輸,電量消耗最少。shell
總之,爲了減小電量的消耗,在蜂窩移動網絡下,最好作到批量執行網絡請求,儘可能避免頻繁的間隔網絡請求。windows
使用 Battery Historian 咱們能夠獲得設備的電量消耗數據,若是數據中的移動蜂窩網絡(Mobile Radio)電量消耗呈現下面的狀況,間隔很小,又頻繁斷斷續續的出現,說明電量消耗性能很很差:瀏覽器
通過優化以後,若是呈現下面的圖示,說明電量消耗的性能是良好的:緩存
另外 WiFi 鏈接下,網絡傳輸的電量消耗要比移動網絡少不少,應該儘可能減小移動網絡下的數據傳輸,多在 WiFi 環境下傳輸數據。
那麼如何纔可以把任務緩存起來,作到批量化執行呢?咱們可使用 JobScheduler 來優化。
咱們能夠經過下面的代碼來獲取手機的當前充電狀態:
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent batteryStatus = this.registerReceiver(null, filter); int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC); if (acCharge) { Log.v(LOG_TAG, "The phone is charging!"); }
在上面的例子演示瞭如何當即獲取到手機的充電狀態,獲得充電狀態信息以後,咱們能夠有針對性的對部分代碼作優化。
好比:咱們能夠判斷只有當前手機爲 AC 充電狀態時 纔去執行一些很是耗電的操做。
private boolean checkForPower() { IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent batteryStatus = this.registerReceiver(null, filter); int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); boolean usbCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_USB); boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC); boolean wirelessCharge = false; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { wirelessCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS); } return (usbCharge || acCharge || wirelessCharge); }
在清單文件中註冊一個 BroadcastReceiver,經過在一個 Intent 過濾器內定義 ACTION_POWER_CONNECTED
和 ACTION_POWER_DISCONNECTED
來同時偵聽這兩種事件。
<receiver android:name=".PowerConnectionReceiver"> <intent-filter> <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/> <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/> </intent-filter> </receiver>
建立監聽充電狀態變化的 PowerConnectionReceiver。
public class PowerConnectionReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); String batteryStatus = ""; switch (status) { case BatteryManager.BATTERY_STATUS_CHARGING: batteryStatus = "正在充電"; break; case BatteryManager.BATTERY_STATUS_DISCHARGING: batteryStatus = "正在放電"; break; case BatteryManager.BATTERY_STATUS_NOT_CHARGING: batteryStatus = "未充電"; break; case BatteryManager.BATTERY_STATUS_FULL: batteryStatus = "充滿電"; break; case BatteryManager.BATTERY_STATUS_UNKNOWN: batteryStatus = "未知道狀態"; break; } Toast.makeText(context, "batteryStatus = " + batteryStatus, Toast.LENGTH_LONG).show(); int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_AC); String chargePlug = ""; switch (plugged) { case BatteryManager.BATTERY_PLUGGED_AC: chargePlug = "AC充電"; break; case BatteryManager.BATTERY_PLUGGED_USB: chargePlug = "USB充電"; break; case BatteryManager.BATTERY_PLUGGED_WIRELESS: chargePlug = "無線充電"; break; } Toast.makeText(context, "chargePlug=" + chargePlug, Toast.LENGTH_LONG).show(); } }
最後註冊 PowerConnectionReceiver,這時當充電狀態發生變化時 PowerConnectionReceiver 就會收到通知。
IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); this.registerReceiver(new PowerConnectionReceiver(), intentFilter);
在清單文件中註冊一個 BroadcastReceiver,經過偵聽 ACTION_BATTERY_LOW
和 ACTION_BATTERY_OKAY
,每當設備電池電量不足或退出不足狀態時,便會觸發該接收器。
<receiver android:name=".BatteryLevelReceiver"> <intent-filter> <action android:name="android.intent.action.ACTION_BATTERY_LOW"/> <action android:name="android.intent.action.ACTION_BATTERY_OKAY"/> </intent-filter> </receiver>
建立監聽電池電量變化的 BatteryLevelReceiver。
public class BatteryLevelReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 當前剩餘電量 int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); // 電量最大值 int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); // 電量百分比 float batteryPct = level / (float) scale; Log.d("BatteryLevelReceiver", "batteryPct = " + batteryPct); Toast.makeText(context, "batteryPct = " + batteryPct, Toast.LENGTH_LONG).show(); } }
最後註冊 BatteryLevelReceiver,這時當電池電量發生變化時 BatteryLevelReceiver 就會收到通知。
IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); this.registerReceiver(new BatteryLevelReceiver(), intentFilter);
一般,若是設備鏈接了交流充電器,您應該最大限度提升後臺更新的頻率;而若是設備是經過 USB 充電,則應下降更新頻率,若是電池正在放電,則應進一步下降更新頻率;在電池電量極低時停用全部後臺更新。
WakeLock 是一種鎖的機制,只要有應用拿着這個鎖,CPU 就沒法進入休眠狀態,一直處於工做狀態。
好比,手機屏幕在屏幕關閉的時候,有些應用依然能夠喚醒屏幕提示用戶消息,這裏就是用到了 Wakelock 鎖機制,雖然手機屏幕關閉了,可是這些應用依然在運行着。
手機耗電的問題,大部分是開發人員沒有正確使用這個鎖,成爲「待機殺手」。
Android 手機有兩個處理器,一個叫 Application Processor(AP),一個叫 Baseband Processor(BP)。
AP 是 ARM 架構的處理器,用於運行 Linux + Android 系統;BP 用於運行實時操做系統(RTOS),通信協議棧運行於 BP 的 RTOS 之上。非通話時間,BP 的能耗基本上在 5mA 左右,而 AP 只要處於非休眠狀態,能耗至少在 50mA 以上,執行圖形運算時會更高。另外 LCD 工做時功耗在 100mA 左右,WiFi 也在 100mA 左右。
通常手機待機時,AP、LCD、WIFI 均進入休眠狀態,這時 Android 中應用程序的代碼也會中止執行。
Android 爲了確保應用程序中關鍵代碼的正確執行,提供了 Wake Lock 的 API,使得應用程序有權限經過代碼阻止 AP 進入休眠狀態。但若是不領會 Android 設計者的意圖而濫用 Wake Lock API,爲了自身程序在後臺的正常工做而長時間阻止 AP 進入休眠狀態,就會成爲待機電池殺手。
那麼 Wake Lock API 具體有啥用呢?心跳包從請求到應答,斷線重連從新登錄等關鍵邏輯的執行過程,就須要 Wake Lock 來保護。而一旦一個關鍵邏輯執行成功,就應該當即釋放掉 Wake Lock 了。兩次心跳請求間隔 5 到 10 分鐘,基本不會怎麼耗電。
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag");
newWakeLock(int levelAndFlags, String tag)
中 PowerManager.PARTIIAL_WAKE_LOCK
是一個標誌位,標誌位是用來控制獲取的 WakeLock 對象的類型,主要控制 CPU 工做時屏幕是否須要亮着以及鍵盤燈須要亮着,標誌位說明以下:
levelAndFlags | CPU是否運行 | 屏幕是否亮着 | 鍵盤燈是否亮着 |
---|---|---|---|
PARTIAL_WAKE_LOCK | 是 | 否 | 否 |
SCREEN_DIM_WAKE_LOCK | 是 | 低亮度 | 否 |
SCREEN_BRIGHT_WAKE_LOCK | 是 | 高亮度 | 否 |
FULL_WAKE_LOCK | 是 | 是 | 是 |
特殊說明:自 API 等級 17 開始,FULL_WAKE_LOCK 將被棄用。應用應使用 FLAG_KEEP_SCREEN_ON。
WakeLock 類能夠用來控制設備的工做狀態。使用該類中的 acquire 可使 CPU 一直處於工做的狀態,若是不須要使 CPU 處於工做狀態就調用 release 來關閉。
若是咱們調用的是 acquire(long timeout),那麼就無需咱們本身手動調用 release() 來釋放鎖,系統會幫助咱們在 timeout 時間後釋放。
若是咱們調用的是 acquire() 那麼就須要咱們本身手動調用 release() 來釋放鎖。
最後使用 WakeLock 類記得加上以下權限:
<uses-permission android:name="android.permission.WAKE_LOCK" />
注意:在使用該類的時候,必須保證 acquire 和 release 是成對出現的。
當設備從休眠狀態中,被應用程序喚醒一瞬間會耗電過多,咱們能夠保持屏幕常亮來節省電量,代碼聲明:
// 屏幕保持常亮 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // 通常不須要人爲的去掉 FLAG_KEEP_SCREEN_ON 的 flag, // windowManager 會管理好程序進入後臺回到前臺的的操做 //getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
或者,直接在佈局中加上 keepScreenOn = true :
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:keepScreenOn="true" tools:context="com.jeanboy.app.batterysample.MainActivity"> </android.support.constraint.ConstraintLayout>
在 API 21,Google 提供了一個新叫作 Job Scheduler API 的組件來處理這樣的場景。Job Scheduler API 容許同時執行多個任務,執行某些指定的任務時不須要考慮時機控制引發的電池消耗。
使用 Job Scheduler,應用須要作的事情就是判斷哪些任務是不緊急的,能夠交給 Job Scheduler 來處理,Job Scheduler 集中處理收到的任務,選擇合適的時間,合適的網絡,再一塊兒進行執行。
下面是使用 Job Scheduler 的一段簡要示例,須要先有一個 JobService:
public class MyJobService extends JobService { @Override public boolean onStartJob(JobParameters params) { Log.i("MyJobService", "Totally and completely working on job " + params.getJobId()); // 檢查網絡狀態 if (isNetworkConnected()) { new SimpleDownloadTask() .execute(params); // 返回 true,表示該工做耗時, // 同時工做處理完成後須要調用 onStopJob 銷燬(jobFinished) return true; } else { Log.i("MyJobService", "No connection on job " + params.getJobId() + "; sad face"); } // 返回 false,任務運行不須要很長時間,到 return 時已完成任務處理 return false; } @Override public boolean onStopJob(JobParameters params) { Log.i("MyJobService", "Something changed, so I'm calling it on job " + params.getJobId()); // 有且僅有 onStartJob 返回值爲 true 時,纔會調用 onStopJob 來銷燬 job // 返回 false 來銷燬這個工做 return false; } private boolean isNetworkConnected() { ConnectivityManager connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); return (networkInfo != null && networkInfo.isConnected()); } private class SimpleDownloadTask extends AsyncTask<JobParameters, Void, String> { protected JobParameters mJobParam; @Override protected String doInBackground(JobParameters... params) { mJobParam = params[0]; try { InputStream is = null; int len = 50; URL url = new URL("https://www.google.com"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(10000); // 10 sec conn.setConnectTimeout(15000); // 15 sec conn.setRequestMethod("GET"); //Starts the query conn.connect(); int response = conn.getResponseCode(); Log.d(LOG_TAG, "The response is: " + response); is = conn.getInputStream(); // Convert the input stream to a string Reader reader = null; reader = new InputStreamReader(is, "UTF-8"); char[] buffer = new char[len]; reader.read(buffer); return new String(buffer); } catch (IOException e) { return "Unable to retrieve web page."; } } @Override protected void onPostExecute(String result) { // 當任務完成時,須要調用 jobFinished() 讓系統知道完成了哪項任務 jobFinished(mJobParam, false); Log.i("SimpleDownloadTask", result); } } }
定義了 JobService 的子類後,而後須要在 AndroidManifest.xml 中進行聲明:
<service android:name="pkgName.JobSchedulerService" android:permission="android.permission.BIND_JOB_SERVICE" />
最後模擬經過點擊 Button 觸發 N 個任務,交給 JobService 來處理:
public class FreeTheWakelockActivity extends ActionBarActivity { public static final String LOG_TAG = "FreeTheWakelockActivity"; TextView mWakeLockMsg; ComponentName mServiceComponent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_wakelock); mWakeLockMsg = (TextView) findViewById(R.id.wakelock_txt); mServiceComponent = new ComponentName(this, MyJobService.class); Intent startServiceIntent = new Intent(this, MyJobService.class); startService(startServiceIntent); Button theButtonThatWakelocks = (Button) findViewById(R.id.wakelock_poll); theButtonThatWakelocks.setText(R.string.poll_server_button); theButtonThatWakelocks.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { pollServer(); } }); } public void pollServer() { JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); for (int i = 0; i < 10; i++) { JobInfo jobInfo = new JobInfo.Builder(i, mServiceComponent) .setMinimumLatency(5000) // 5 seconds .setOverrideDeadline(60000) // 60 seconds .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) // WiFi or data connections .build(); mWakeLockMsg.append("Scheduling job " + i + "!\n"); scheduler.schedule(jobInfo); } } }
官方 demo 地址:https://github.com/googlesamp...
Energy Profiler 是 Android Profiler 中的一個組件,可幫助開發者找到應用程序能量消耗的位置。
Energy Profiler 經過監控 CPU、網絡和 GPS 傳感器的使用狀況,並以圖形化顯示每一個組件使用多少能量。Energy Profiler 還會顯示可能影響能耗的系統事件(WakeLock、Alarms、Jobs 和 Location),Energy Profiler 不直接測量能耗,相反,它使用一種模型來估算設備上每種資源的能耗。
能夠在 View > Tool Windows > Android Profiler 中打開 Energy Profiler 界面。
Energy Profiler 的具體使用可查看 Android 開發文檔 - 使用 Energy Profiler 檢查能源使用狀況。
Energy Profiler 支持 Android 8.0 (API 26) 及以上的系統,Android 8.0 (API 26) 如下請使用 Battery Historian。
Battery Historian 是一款由 Google 提供的 Android 系統電量分析工具,可以以網頁形式展現手機的電量消耗過程。
GitHub 地址:https://github.com/google/bat...
本文以 macOS 環境爲例,介紹 Battery Historian 的使用。Windows 環境請參考:Battery Historian 2.0 for windows 環境搭建。
手動下載 Docker 安裝包,下載連接:https://download.docker.com/m...。
安裝好以後點擊圖標運行,在頂部菜單欄能夠看到一個鯨魚圖標,說明 Docker 正在運行。
而後在控制檯輸入:
$ docker --version
看到以下內容,說明 Docker 能夠正常使用:
Docker version 19.03.1, build 74b1e89
經過下面命令安裝 Battery Historian:
$ docker run -d -p 9999:9999 bhaavan/battery-historian
上面的步驟都完成以後就能夠啓動 Battery Historian 了,默認端口是 9999。
以後在瀏覽器中輸入 http://localhost:9999
就能夠看到效果,而後上傳 bugreport 文件進行分析了。
根據系統版本不一樣 bugreport 的獲取方式略有差異:
若是 是Android 7.0 及以上版本,經過下面命令來獲取 bugreport:
$ adb bugreport bugreport.zip
若是是 Android 6.0 及如下版本,經過下面命令來獲取 bugreport:
$ adb bugreport > bugreport.txt
獲取到 bugreport 文件以後,咱們就能夠將其上傳到 Battery Historian 上進行分析,下面是它的輸出結果。
在頁面的下方咱們能夠查看這段時間內系統的狀態 system stats,也能夠選擇某個應用查看應用的狀態 app stats。
其中咱們能夠看到 Device estimated power use
中顯示了估算的應用耗電量值爲 0.18%
。
Battery Historian 還有個比較功能,在首頁選擇 Switch to Bugreport Comparisor,而後就能夠上傳兩個不一樣的 bugreport 文件,submit 以後就能夠看到它們的對比結果了,這個功能用來分析同一個應用的兩個不一樣版本先後的耗電量很是有用。
須要注意的是,通常開始統計數據以前須要使用下面的命令將之前的累積數據清空:
$ adb shell dumpsys batterystats --enable full-wake-history$ adb shell dumpsys batterystats --reset
上面的操做至關於初始化操做,若是不這麼作會有一大堆的干擾的數據,看起來會比較痛苦。
關於 bugreport 相關的知識推薦閱讀 Android adb bugreport 工具分析和使用 這篇文章,做者簡單地從源碼角度分析了 adb bugreport
命令的運行原理,結論是 bugreport 實際上是啓動了 dumpstate 服務來輸出數據,其中數據來源包括:
/proc
和 /sys
節點文件