1. 程式人生 > >Android最佳實踐之效能

Android最佳實踐之效能

Doze和App Standby的優化(API23)

參考地址:http://developer.android.com/training/monitoring-device-state/doze-standby.html
從Android 6.0 (API level 23)開始,Android提供了兩個節電功能用來增加電池的續航時間。Doze 可以在裝置長時間不使用時,通過延遲後臺CPU和網路的活動來減少電池的消耗;App Standby將延遲沒有互動的app網路活動。
Doze和App Standby在Android6.0以上的版本上管理所有App的行為。為保證最好的使用者體驗,需要在Doze 和App Standby 模式下測試你的app並適當調整你的程式碼。

理解Doze

如果使用者離開裝置一段時間,沒有插上電源,螢幕會關閉,裝置會進入Doze模式。在這種模式下,系統會限制app的網路和CPU的服務來儲存電量。系統也會組織app訪問網路,延遲它要做的任務、同步工作以及標準鬧鐘。
系統會定期的退出Doze模式一小會兒,讓app完成他們的延遲活動。在這個視窗期(Maintenance Window),系統執行所有的同步工作、任務以及鬧鐘,允許app訪問網路。
doze.png
在每個Maintenance Window結束後,系統會再次進入Doze模式,掛起網路操作等。隨著時間的推移,這個視窗期會出現的越來越不頻繁,這樣幫助裝置省電。

Doze的限制

  • 網路訪問掛起
  • 系統忽略喚醒鎖(wake locks.)
  • 標準的AlarmManager鬧鐘被推遲到下一次Maintenance Window執行
  • 系統不會進行WIFI掃描
  • 系統不允許sync adapters執行
  • 系統不允許JobScheduler工作

適配你的app到Doze模式

Doze特別會影響鬧鐘和定時器,因為在5.1(APi22)及以下版本中,鬧鐘在Doze模式下不會啟動。
在 Android 6.0 (API level 23) 中,提供了2個新的AlarmManager方法,setAndAllowWhileIdle()setExactAndAllowWhileIdle()

。這樣在Doze模式下,你也可以設定鬧鐘執行(每個App每次可以執行15分鐘)。
為確保你的應用在Doze模式下如何表現,你可以使用adb命令迫使系統進入和退出Doze模式,觀察你的應用程式的行為。

理解App Standby

App Standby 允許系統決定app在使用者沒使用它時的空閒狀態。當用戶不觸控app一段時間且下面條件都沒有時系統來做這個決定:

  • 使用者顯示的啟動app
  • app在前臺有一個任務(一個Activity或一個前臺的service,或被呼叫的Activity或前臺service)
  • app在鎖屏介面或通知欄生成一個notification
    當裝置充電時,系統將app從Standby狀態中釋放掉,允許它自由訪問網路,執行任意的任務和同步操作。如果裝置長時間空閒,系統將允許空閒的app一天訪問一次網路。

當app空閒時允許GCM來互動

Google Cloud Messaging (GCM),是一個推送服務。GCM通過high-priority GCM messages在Doze和App Standby模式下進行了優化。你的app可以使用它們高效的最低耗電的在系統和裝置間進行互動。
作為最佳實踐,如果你需要使用即時訊息,你應該使用GCM。如果你的伺服器和客戶端已經使用GCM,確保你的服務使用high-priority messages,因為這個可以可靠的喚醒app即使裝置在Doze模式下。

對其它使用場景的支援

幾乎所有的app都可以支援Doze,但也有一些使用者場景還不能滿足。在這些場景內,系統提供一個可配置的白名單來讓某些app免除 Doze和App Standby優化。
在白名單中的app可以在 Doze和App Standby模式下使用網路和喚醒鎖。然而,白名單的app也有受限,和其它app一樣。例如,白名單的app的job和同步任務被延遲了,和鬧鐘一樣被限制了。app可以通過呼叫isIgnoringBatteryOptimizations()來檢查自己是否在白名單中。
使用者可以在 Settings > Battery > Battery Optimization中手動配置白名單,系統也提供了方法讓app去要求使用者允許加入白名單。

  • 啟動ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS Intent,進入Battery Optimization介面,那裡可以新增白名單
  • 加了REQUEST_IGNORE_BATTERY_OPTIMIZATIONS許可權的app可以直接彈出一個系統對話方塊讓使用者直接新增app到白名單中而不用進入設定介面。使用**ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS**Intent來觸發這個Dialog。
  • 使用者可以手動從白名單中移除app

注意:Google Play政策禁止app直接免除Android 6.0+(Doze和App Standby模式)電量管理特性的,除非app的核心功能受到不利影響。

測試Doze和App Standby模式

為確保好的使用者體驗,上線app前需進行Doze和App Standby模式的完整測試:

Doze測試

1、配置一個Android6.0+的硬體裝置或虛擬機器
2、連線裝置到開發機,安裝app
3、執行app並保持活躍
4、關閉裝置螢幕(app時活動的)
5、強制使系統迴圈進入Doze模式:

$ adb shell dumpsys battery unplug
$ adb shell dumpsys deviceidle step

第二行命令可能不止執行一次,直至裝置狀態到空閒。
6、觀察你的app重新啟用後的行為,確保app在退出Doze模式後優雅的啟用。

測試App Standby模式

1、配置一個Android6.0+的硬體裝置或虛擬機器
2、連線裝置到開發機,安裝app
3、執行app並保持活躍
4、強制使系統迴圈進入App Standby模式:

$ adb shell dumpsys battery unplug
$ adb shell am set-inactive <packageName> true

5、模擬喚醒app:

$ adb shell am set-inactive <packageName> false
$ adb shell am get-inactive <packageName>

6、喚醒app後觀察app的行為。確保app從standby模式優雅的恢復過來。
特別的,你需要檢查你的app的通知和後臺工作是否繼續按預期執行。

可接受的白名單使用者場景

下面列表中高亮的是可接受的白名單場景:

Type 使用者場景 是否可以用GCM 白名單是否接受 備註
即時訊息、聊天或呼叫應用程式 在Doze或App Standby模式下,需要傳遞即時訊息 是的,可用GCM 不接受 需要high-priority messages 喚醒app和訪問網路
即時訊息、聊天或呼叫應用程式企業VOIP apps 同上 否,不能用GCM 接受
任務自動化的app app核心的功能就是即時訊息、語音呼叫、照片管理或地圖定位 如果適用的話 接受

監測電池電量和充電狀態

原文地址:http://developer.android.com/training/monitoring-device-state/battery-monitoring.html
執行應用程式更新對電量的影響取決於電池和充電狀態的電量值。裝置充電時執行更新的影響可以忽略不計,所以在大多數情況下當裝置被連線一個充電器時你可以最大化你的重新整理頻率。相反,如果裝置沒有充電,減少你的更新頻率有助於延長電池續航時間。
同樣,你可以檢測電池的電量,在電量幾乎用光時減少甚至停止程式的執行。

獲取當前的充電狀態

BatteryManager通過一個sticky的Intent廣播了所有的有關電池和充電狀態的資訊。
因為是sticky的Intent,所以不需要註冊BroadcastReceiver,簡單的傳null到registerReceiver中即可,然後電池狀態就返回了。這裡也可以傳一個實際的BroadcastReceiver物件,但這裡不是必須的,後面會有講解。

IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);

你可以提取當前的充電狀態,如果裝置在充電,還可以知道是通過USB或AC充電器來充電。

// Are we charging / charged?
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                     status == BatteryManager.BATTERY_STATUS_FULL;

// How are we charging?
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;

通常,在交流電(AC)充電情況下,你應該馬力全開的執行程式,而在USB充電時,減少頻率,在不充電的 情況下降低更低的頻率來執行程式更新。

監控充電狀態的變化

插拔裝置的資料線很容易的引起充電狀態的變化,監視充電狀態的變化時很重要的,它決定了程式的重新整理頻率。
BatteryManager廣播了一個action,告知我們裝置何時連上或斷開了電源。我們註冊一個BroadcastReceiver,通過定義 ACTION_POWER_CONNECTEDACTION_POWER_DISCONNECTED的IntentFilter來監聽:

<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>

在廣播中提取充電狀態:

public class PowerConnectionReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
        boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                            status == BatteryManager.BATTERY_STATUS_FULL;

        int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
        boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
        boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;
    }
}

獲取當前電量

int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);

float batteryPct = level / (float)scale;

監測電量的顯著變化

你不能很榮易地持續監控電池狀態,而且你也不需要。
一般來說,持續監控電量對電池的影響大於app本身行為對電量的影響,所以最好的實踐是僅僅監控電量顯著的變化-如裝置進入或離開低電量時。
下面這段manifest片段,當裝置電量很低或離開低電量時廣播被觸發,它監聽ACTION_BATTERY_LOWACTION_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>

一般在電池電量非常低時最好禁用所有你的後臺更新操作,因為不管你的資料多新,在你使用它之前手機就關閉螢幕了。
在很多情況下,給裝置充電和將它放到dock模式是一致的。

確定和監控Docking(底座)狀態和型別

原文地址:http://developer.android.com/training/monitoring-device-state/docking-monitoring.html#CurrentDockState
Android裝置可以放到到幾種不同型別的dock(底座)中。包括car dock、home dock以及Digital和Analog的dock。dock狀態和充電狀態緊密相連因為很多的底座為dock狀態下的裝置提供電量。
手機的dock狀態如何影響你的更新頻率取決於你的app。你可以選擇在desktop dock增加體育中心app的更新頻率,或者如果裝置在car dock模式下完全禁用你的更新。相反,在car dock模式下如果你的後臺服務需要更新交通狀況你可以選擇來最大化你的更新頻率。
dock狀態也是由一個sticky的intent來廣播的,它允許你查詢裝置是否在dock模式,如果是,是哪一種dock。

確定當前的dock狀態

dock狀態在sticky廣播的ACTION_DOCK_EVENT的action中。因為是sticky,不需要註冊一個廣播:

IntentFilter ifilter = new IntentFilter(Intent.ACTION_DOCK_EVENT);
Intent dockStatus = context.registerReceiver(null, ifilter);

可以從EXTRA_DOCK_STATE 中提取當前的dock狀態:

int dockState = battery.getIntExtra(EXTRA_DOCK_STATE, -1);
boolean isDocked = dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;

確定當前Dock型別

有四種Dock型別:

  • Car:車載底座
  • Desk:桌面底座
  • Low-End (Analog) Desk:模擬底座
  • High-End (Digital) Desk:數字底座
    後兩個型別在Android in API level 11中才有。所以最好的實踐就是檢查是否在3種desk的型別之中,而不用關心具體是哪一種:
boolean isCar = dockState == EXTRA_DOCK_STATE_CAR;
boolean isDesk = dockState == EXTRA_DOCK_STATE_DESK ||
                 dockState == EXTRA_DOCK_STATE_LE_DESK ||
                 dockState == EXTRA_DOCK_STATE_HE_DESK;

監控Dock的狀態和型別變化

裝置不管何時dock或undock,都會觸發ACTION_DOCK_EVENT的廣播。需要監聽這個廣播只需要在manifest中註冊,然後加對應的action即可:

<action android:name="android.intent.action.ACTION_DOCK_EVENT"/>

確定和監控連線狀態

原文地址:http://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html
鬧鐘和後臺服務最常見的用途是定期從伺服器更新資料到App,並快取資料,或執行長時間的下載。但是如果你沒有連線到網際網路,或你的下載連線太慢了,你都要喚醒裝置來進行更新嗎?
使用ConnectivityManager來檢測是否連上網路,如果連上可以知道連線的是哪種網路。

檢測是否連上網路

如果沒有連上網路,就不需要進行任何的更新操作了。下面的程式碼判斷裝置是否連上網路:

ConnectivityManager cm =
        (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);

NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null &&
                      activeNetwork.isConnectedOrConnecting();

判斷連線的網路型別

boolean isWiFi = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;

行動網路資料的開銷比WI-FI要大,所以大多數情況下,你的裝置需要在WI-FI情況下進行資料更新。
沒有網路時需要禁用網路更新,一旦建立網路連線,可以恢復更新操作。

監控網路連線的變化

當網路連線發生變化時ConnectivityManager廣播了一個 CONNECTIVITY_ACTION (“android.net.conn.CONNECTIVITY_CHANGE”) 的action,你可以在manifest註冊一個broadcast receiver來監聽這個action。

<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>

裝置的網路連線狀態可能會變化的很頻繁,當每次裝置網路從移動蜂窩資料切換到WI-FI時就會被觸發。因此,最好的實踐就是僅當你要將之前暫停的更新或下載恢復時才監聽這個廣播。這個一般就夠了,在進行一個更新之前,先檢查是否有網路連線;然後沒有網路連線,掛起更新操作直到網路恢復。

按需操作Broadcast Receivers

切換Receivers可用狀態來提高效率

我們可以使用PackageManager來切換在Manifest註冊的包括broadcast receiver的任意元件。

ComponentName receiver = new ComponentName(context, myReceiver.class);

PackageManager pm = context.getPackageManager();

pm.setComponentEnabledSetting(receiver,
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
        PackageManager.DONT_KILL_APP)

使用這個技術,如果你確定網路連線已經丟失,你可以禁用除連線狀態改變receiver之外的所有的receiver。相反,一旦網路連線上,你可以停止監聽連線狀態的receiver,然後執行更新操作前簡單檢查下是否連上網路。