日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

性能优化系列(四)电量性能优化

發布時間:2023/12/10 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 性能优化系列(四)电量性能优化 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章首發「Android波斯灣」公眾號,更新地址:https://github.com/jeanboydev/Android-ReadTheFuckingSourceCode

耗電設備

手機各個硬件模塊的耗電量是不一樣的,有些模塊非常耗電,而有些模塊則相對顯得耗電量小很多。

電量消耗的計算與統計是一件麻煩而且矛盾的事情,記錄電量消耗本身也是一個費電量的事情。唯一可行的方案是使用第三方監測電量的設備,這樣才能夠獲取到真實的電量消耗。

屏幕

當設備處于待機狀態時消耗的電量是極少的,以 Nexus 5 為例,打開飛行模式,可以待機接近 1 個月。可是點亮屏幕,味著系統的各組件要開始進行工作,界面也需要開始執行渲染,這會需要消耗很多電量。

蜂窩網絡

通常情況下,使用移動網絡傳輸數據,電量的消耗有三種狀態:

  • Full Power

能量最高的狀態,移動網絡連接被激活,允許設備以最大的傳輸速率進行操作。

  • Low power

一種中間狀態,對電量的消耗差不多是 Full power 狀態下的 50%。

  • Standby

最低的狀態,沒有數據連接需要傳輸,電量消耗最少。

總之,為了減少電量的消耗,在蜂窩移動網絡下,最好做到批量執行網絡請求,盡量避免頻繁的間隔網絡請求。

使用 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 {@Overridepublic 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 {@Overridepublic 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

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 分鐘,基本不會怎么耗電。

WakeLock 使用

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 工作時屏幕是否需要亮著以及鍵盤燈需要亮著,標志位說明如下:

levelAndFlagsCPU是否運行屏幕是否亮著鍵盤燈是否亮著
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 來關閉。

  • 自動 release

如果我們調用的是 acquire(long timeout),那么就無需我們自己手動調用 release() 來釋放鎖,系統會幫助我們在 timeout 時間后釋放。

  • 手動 release

如果我們調用的是 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>

JobScheduler

在 API 21,Google 提供了一個新叫做 Job Scheduler API 的組件來處理這樣的場景。Job Scheduler API 允許同時執行多個任務,執行某些指定的任務時不需要考慮時機控制引起的電池消耗。

使用 Job Scheduler,應用需要做的事情就是判斷哪些任務是不緊急的,可以交給 Job Scheduler 來處理,Job Scheduler 集中處理收到的任務,選擇合適的時間,合適的網絡,再一起進行執行。

下面是使用 Job Scheduler 的一段簡要示例,需要先有一個 JobService:

public class MyJobService extends JobService {@Overridepublic 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;}@Overridepublic 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;@Overrideprotected 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 secconn.setConnectTimeout(15000); // 15 secconn.setRequestMethod("GET");//Starts the queryconn.connect();int response = conn.getResponseCode();Log.d(LOG_TAG, "The response is: " + response);is = conn.getInputStream();// Convert the input stream to a stringReader 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.";}}@Overrideprotected 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;@Overrideprotected 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() {@Overridepublic 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/googlesamples/android-JobScheduler

Energy Profiler

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

Battery Historian 是一款由 Google 提供的 Android 系統電量分析工具,能夠以網頁形式展示手機的電量消耗過程。

GitHub 地址:https://github.com/google/battery-historian

本文以 macOS 環境為例,介紹 Battery Historian 的使用。

Windows 環境請參考:Battery Historian 2.0 for windows 環境搭建。

安裝 Docker

手動下載 Docker 安裝包,下載鏈接:https://download.docker.com/mac/stable/Docker.dmg。

安裝好之后點擊圖標運行,在頂部菜單欄可以看到一個鯨魚圖標,說明 Docker 正在運行。

然后在控制臺輸入:

$ docker --version

看到如下內容,說明 Docker 可以正常使用:

Docker version 19.03.1, build 74b1e89

安裝 Battery Historian

通過下面命令安裝 Battery Historian:

$ docker run -d -p 9999:9999 bhaavan/battery-historian

上面的步驟都完成之后就可以啟動 Battery Historian 了,默認端口是 9999。

之后在瀏覽器中輸入 http://localhost:9999 就可以看到效果,然后上傳 bugreport 文件進行分析了。

獲取 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 節點文件
  • 執行 shell 命令獲得相關輸出
  • logcat 輸出
  • Android Framework Services 信息基本使用 dumpsys 命令通過 binder 調用服務中的 dump 函數獲得信息

結果分析參考:https://testerhome.com/topics/3733

我的 GitHub

github.com/jeanboydev

技術交流群

歡迎加入技術交流群,來一起交流學習。

我的公眾號

歡迎關注我的公眾號,分享各種技術干貨,各種學習資料,職業發展和行業動態。

參考資料

  • YouTube - Android 性能優化典范第 2 季
  • Udacity 學院 - Android 性能優化
  • 胡凱 - Android 性能優化之電量篇
  • Android 電量優化
  • Android 開發文檔 - Optimize for battery life
  • Android 開發文檔 - 使用 Energy Profiler 檢查能源使用情況
  • Android adb bugreport 工具分析和使用
  • Battery Historian 2.0 for windows 環境搭建

總結

以上是生活随笔為你收集整理的性能优化系列(四)电量性能优化的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。