1 前言
在今天移動互聯網蓬勃發展的浪潮下,再好的智能手機也能逃每天一充電的情況。而一款App的好用與否,電量的耗費也是衡量的重要指標之一。今天我們就來一起探索Android中電量的相關知識以及講講App進行省電優化的一些開發技巧。
2 獲得手機電量信息
在開發過程中,如果想要獲得手機的電量信息,可以通過Android提供了系統廣播:ACTION_BATTERY_CHANGED可以在電池信息改變時獲得相關的電池信息。如代碼:
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
int health = intent.getIntExtra("health", 0);
int iconSmall = intent.getIntExtra("Icon-small", 0);
int level = intent.getIntExtra("level", 0);
boolean present = intent.getBooleanExtra("present", false);
int plugged = intent.getIntExtra("plugged", 0);
int scale = intent.getIntExtra("scale", 0);
int status = intent.getIntExtra("status", 0);
String technology = intent.getStringExtra("technology");
int temperature = intent.getIntExtra("temperature", 0);
int voltage = intent.getIntExtra("voltage", 0);
}
}
};
電池信息對應變量說明如下:
變量 |
類型 |
說明 |
health |
int |
取得電池的健康狀態,返回的狀態類型由 android.os.BatteryManager 類定義的常量所決定,包括: 電池損壞( BATTERY_HEALTH_DEAD ) 電池健康( BATTERY_HEALTH_GOOD ) 電池過熱( BATTERY_HEALTH_OVERHEAT ) 電池電壓過大( BATTERY_HEALTH_OVER_VOLTAGE ) 未知狀態( BATTERY_HEALTH_UNKOWN ) 未明示故障( BATTERY_HEALTH_UNSPECIFIED_FAILURE ) |
iconSmall |
int |
取得電池對應的圖標 ID |
level |
int |
取得電池的剩餘容量 |
present |
boolean |
判斷當前是否存在電池 |
plugged |
int |
連接的電源插座類型,返回的狀態由 android.os.BatteryManager 類定義的常量所決定,包括: USB 電源( BATTERY_PLUGGED_USB ) 交流電電源( BATTERY_PLUGGED_AC ) |
scale |
int |
取得電池的總容量值,通常爲 100 |
status |
int |
取得電池的狀態,返回的狀態類型由 android.os.BatteryManager 類定義的常量所決定,包括: 電池充電狀態( BATTERY_STATUS_CHARGING ) 電池放電狀態( BATTERY_STATUS_DISCHARGING ) 電池滿電狀態( BATTERY_STATUS_FULL ) 電池不充電狀態( BATTERY_STATUS_NOT_CHARGING ) 電池未知狀態( BATTERY_STATUS_UNKNOWN ) |
technology |
String |
取得電池的類型 |
temperature |
int |
取得電池的溫度,單位是攝氏度 |
voltage |
int |
取得電池的電壓 |
3 分析電量
在Android5.0後,谷歌開源了一款用於檢測電池有關信息和事件的工具——Battery Historian。該工具非常強大地進行解析bugreport.txt轉換成Html的表示形式,然後可視化地展示分析相關指標耗電情況,還可以對App進行篩選給出總結性說明,幫助我們識別耗電的行爲。
3.1生成bugreport.txt文件
bugreport.txt就是通過adb命令導出手機的電量信息文件,步驟可以這樣:
步驟一、清除已有的耗電量數據,執行命令:adb shell dumpsys batterystats --enable full-wake-history
步驟二、設備耗電量數據重置,執行命令:adb shell dumpsys batterystats --reset
步驟三、啓動手機上的App進行相關操作
步驟四、Android 7.0之前執行命令: adb bugreport > bugreport.txt,Android 7.0及之後執行命令: adb bugreport bugreport.zip 。便可導出bugreport文件到電腦中,同時也會在手機目錄/data/user_de/0/com.android.shell/files/bugreports下生成txt或zip文件。
3.2安裝Battery Historian
Battery Historian Github官方介紹了兩種使用方式,分別是使用使用Docker和編譯源碼。如果你使用的是MAC系統,建議使用第一種方式,就是通過Docker來安裝Battery Historian,在安裝完Docker後在終端運行命令:docker run -d -p 9999:9999 bhaavan/battery-historian,然後等待下載完成即可。如:
如果你使用的是Win7系統,那麼就只能使用第二種安裝方法,步驟如下。
步驟一:下載和安裝GO編程語言,並配置環境變量,正常情況下安裝成功會自動生成環境變量,如果沒有則要自己動手設置一下。
a.配置環境變量GOROOT、GOPATH以及Path:
b.檢查是否安裝成功:
步驟二:下載和安裝Git,並配置環境變量。
步驟三:下載和安裝Python2.7(注意:是Python2.7,不是Python3),並配置環境變量。
步驟四:下載和安裝Java,並配置環境變量。
步驟五:打開C:\Program Files\Git\git-bash.exe,並執行命令:go get -d -u github.com/google/battery-historian/...
使在 %USERPROFILE%\go\src\github.com\google目錄下載battery-historian工程源碼:
步驟六:同樣,通過命令:go get -u github.com/golang/protobuf/protoc-gen-go
使在 %USERPROFILE%\go\src\github.com\golang\目錄下載protobuf工程源碼:
步驟七:執行命令:cd $GOPATH/src/github.com/google/battery-historian
進入到battery-historian目錄下,並執行命令:go run setup.go
該命令主要是在%USERPROFILE%\go\src\github.com\google\battery-historian\third_party目錄下創建third_party文件夾,並下載 closure-compiler、closure-library 和 flot-axislabels。如果因爲超時不成功,可以自行進行下載後放入目錄即可:
步驟八:執行命令:cd $GOPATH/src/github.com/google/battery-historian
進入到battery-historian目錄下,並執行命令:go run cmd/battery-historian/battery-historian.go
至此,Battery Historian便完裝完成。
3.3 分析bugreport
待Battery Historian完裝完成後,便可在瀏覽器中進行訪問http://localhost:9999 (9999是默認的端口,在安裝中可以指定),如下圖:
上傳bugreport.txt或bugreport.zip文件後便可以進行分析了,筆者在寫文章時嘗試進行上傳bugreprot,但因爲國內對谷歌訪問的限制,始終不能顯示提交按鈕,所以並沒有成功,所以只能貼一張官網上樣圖:
4 電量優化技巧
Android開發中,反映一個App電量的消耗主要由:CPU、位置服務、wakelock、傳感器、數據傳輸等組件的使用情況決定。
4.1 位置服務的使用
先擇合適的定位服務
Android系統爲開發者提供了三種定位服務:
GPS_PROVIDER
GPS定位,利用GPS芯片通過接收全球定位系統的衛星提供的經緯度座標信息實現位置服務,需要ACCESS_FINE_LOCATION權限。這種方式定位耗電也是最高,精準度也是最高,通常在10米以內。
NETWORK_PROVIDER
網絡定位,通過移動通信的基站信號和Wifi節點的地址來大致計算出手機所在的位置,需要ACCESS_COARSE_LOCATION或者ACCESS_FINE_LOCATION權限。這種方式定位精度比GPS定位差很多,通常在幾百米範圍內。
PASSIVE_PROVIDER
被動定位,使用其他應用或者系統組件發起定位請求後保存下來的位置信息。這種方式是最省電的。
我們在實際開發中需要綜合考慮App的具體需要在不同時機採用不同的定位服務。一般地往往在實際開發中很多App通常會選擇第三方的定位SDK,像百度定信、高德定位,因爲這些SDK無論在定位時間還是定位精度還是耗電量方面都做出專門的優化,還是非常靠譜的。
合理註冊和註銷定位監聽
一般地我們會在Activoity的onResume和onPause兩個生命週期回調中做註冊和註銷定位服務的監聽。如代碼:
public void onResume(){
super.onResume();
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 6000, 100, locationListener);
}
public void onPause() {
super.onPause();
locationManager.removeUpdates(locationListener);
}
Android SDK提供了requestLocationUpdates方法進行位置更新監聽的註冊,請注意方法的第二和第三個參數,分別是指位置更新通知的最小時間間隔毫秒數和最小單位爲米的距離。我們需要根據具體的業務需求設置一個合適的更新頻率值,通常需要在定位精度和耗電量之間綜合考慮。
在獲取到定位之後或者程序處於後臺時,需要及時註銷位置監聽,因爲長時間的監聽位置更新會耗費大量的電量。
4.2 慎用WakeLock
Android系統中爲了節省電量,在設置裏設定了會在用戶無操作一段時間之後進入休眠狀態。但有時候,我們需要使手機一直處於一種喚醒的狀態,例如一些播放器的App,在播放視頻時就算不操作屏幕也不需要進入休眠狀態,從而保證良好的用戶體驗。這時就需要使用WakeLock。WakeLock它是一種鎖機制,只要拿着該鎖,系統就無法進入休眠,同時也可使App更加的耗電。
WakeLock的鎖類型有很多種,不同的類型對cpu、屏幕和鍵盤的影響不相,具體情況如下:
PARTIAL_WAKE_LOCK 保持CPU正常運轉,但屏幕和鍵盤燈可能是關閉的。
SCREEN_DIM_WAKE_LOCK 保持CPU正常運轉,允許屏幕點亮但可能是置灰的,鍵盤燈可能是關閉的。
SCREEN_BRIGHT_WAKE_LOCK 保持CPU正常運轉,允許屏幕高亮度顯示,鍵盤燈可能是關閉的。
FULL_WAKE_LOCK 保持CPU正常運轉,允許屏幕高亮度顯示,鍵盤燈也保持亮度。
ACQUIRE_CAUSES_WAKEUP 強制屏幕和鍵盤燈亮起,這種鎖針對一些必須通知用戶的操作。
ON_AFTER_RELEASE 當WakeLock被釋放後,繼續保持屏幕和鍵盤燈開啓一定時間。
WakeLock的使用如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, this.getClass().getName());
}
@Override
protected void onResume() {
super.onResume();
mWakeLock.acquire(60 * 1000);
}
@Override
protected void onPause() {
super.onPause();
mWakeLock.release();
}
在使用WakeLock時,建議在申請時傳入超時參數,防止由於忘記或者異常情況下沒有釋放鎖,還要切記在任務結束之後及時釋放鎖,如果不是會導至屏幕一直顯示很長一段時間從而耗費了手機的電量。
4.3 傳感器合理使用
Android提供了對設備傳感器的支持,只要設備的硬件有支持這些傳感器,App就可以通過傳感器來獲取設備的外界條件,包括手機的運行狀態、當前擺放的方向等。
傳感器幾個常用的類型有:
Sensor.TYPE_ORIENTATION 方向傳感器
Sensor.TYPE_ACCELEROMETER 重力傳感器
Sensor.TYPE_LIGHT 光線傳感器
Sensor.TYPE_MAGNETIC_FIELD 磁場傳感器
傳感器頻率總共分爲4等,分別是:
SensorManager.SENSOR_DELAY_FASTEST 最快,延遲最小。
SensorManager.SENSOR_DELAY_GAME 適合遊戲的頻率。
SensorManager.SENSOR_DELAY_NORMAL 正常頻率。
SensorManager.SENSOR_DELAY_UI 適合普通用戶界面UI變化的頻率。
Android爲我們提供了這幾個採樣率的參數,方便我們使用。但對於選擇那種採樣率而言,並不是越快越好,要參照實際開發的應用的情況來說,越高的採樣率類型就會越費耗電量。
傳感器的使用如下:
private SensorManager mSensorManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
}
@Override
protected void onResume() {
super.onResume();
mSensorManager.registerListener(mSensorEventListener, mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION), SensorManager.SENSOR_DELAY_GAME);
}
@Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(mSensorEventListener);
}
在使用傳感器時,一定要切記在任務結束之後或者處理後臺時要及時註銷傳感器的監聽。
4.4 數據傳輸技巧
Android中的數據傳輸方式有很多種,如常見的:藍牙、Wifi、蜂窩數據。無論哪一種傳輸方式,爲了更好地延長電池的使用時間,我們在使用過程中都需要重點關注幾點:
1 處於後臺時根據具體業務需求,嚴格限制App處於後臺時是否需要繼續真的需要數據傳輸,儘量能夠避免無效的數據傳輸。
2 數據傳輸的頻率問題,要確定好數據傳輸的頻率,避免冗餘重複的數據傳輸,同時一定要避免輪詢情況。
3 數據傳輸中要進行壓縮數據大小,合併網絡請求。
4 失效重試機制,要注意重試是在網絡正常的情況下才去重試,否則除了沒有重試成功外,而且還增加了消耗電量。
更多的網絡數據傳輸技巧,請見《Andorid性能優化(八) 之 網絡請求優化》
4.5 反註冊後臺BroadcastReceiver
減少應用損耗的電量,那麼就要儘量避免無用的操作代碼的執行。例如廣播的使用中,如果App退到後臺,一切界面刷新都是沒有意義的而且會浪費內存和電量,所以通常的做法是在Activity的onPause方法回調時根據具體業務需要選擇是否應該反註冊廣播。
4.6 AlarmManager使用上注意
AlarmManager是一個系統級別的服務,它可以在特定的時刻廣播一個指定的Intent。AlarmManager常用的方法有:
set 設置一次性的鬧鐘操作。
setRepeating 設置重複性的鬧鐘操作。事實上,seatRepeating方法並不靠譜。根據資料:在API 19(即Kitkat)之後,這一方法將不再準確地保證每次工作都在你設置的時間開始。原來,操作系統爲了節能省電,將會調整alarm喚醒的時間。故通過AlarmManager.setRepeating()方法將不再保證你定義的工作能按時開始。
AlarmManager的喚醒操作也是比較耗電的,通常情況下需要保證兩次喚醒操作的時間間隔不要太短,在不需要使用喚醒功能的情況下應儘早取消它,否則會一直處於耗電狀態。
4.7善用JobScheduler
Android5.0後提供了作業調度器JobScheduler。它的作用是讓系統在某個時刻某個特定條件下批處理一些APP的任務請求,而且這項任務的執行是在你自己的應用程序進程中。它的具體使用可見前面文章《Android裏JobScheduler的實現》 。它可以對任務進行排期,例如設備在充電或者空閒的時候才執行一些特定任務從而達到優化耗電的任務安排方式的效果。