Maximizing Battery Life
7.1 Measuring Battery Usage测量电池用量
7.2 Disabling禁用 Broadcast Receivers
7.3 Networking网络
7.4 Location 位置
7.5 Sensors传感器7.6 Graphics图形
7.7 Alarms提醒
7.8WakeLocks保持进程在休眠时从屏幕消失
With little power comes great responsibility电池虽小地位却很重要. Android portable devices run on batteries, and everything your application does draws a certain amount of power from the device’s battery. Since most devices are charged at home during the night and will be used during the day when there is no opportunity to recharge the battery, most device owners expect the battery to last at least about 12 hours. Typical usage典型使用方式 may cause the battery to drain more quickly: for example, charging stations were available at Google I/O as many were using their devices for periods of time longer than usual during the event. Even though applications sometimes do not seem to be doing much, it is actually quiteeasy to draw so much power from the battery that the device runs out of juice in themiddle of the day, leaving the user without a phone or tablet for several hours. Anapplication that empties the battery quickly will most likely become a strong candidatefor deletion删除, poor reviews的差评, and possibly lower revenues不赚钱. As a consequence, you as a developer should try to use as little power as possible and make sensible use of the device’s battery. In this chapter, you learn how to measure battery usage and how to make sure you canconserve power without negatively impacting the user experience, using some of thevery things that make Android applications appealing: networking, access to locationinformation, and sensors. You also learn how to work efficiently with more internalcomponents of Android, such as broadcast receivers广播接收器, alarms, and wake locks.
7.1 Batteries
Different devices have different capacities容量. Battery capacity for phones and tablets is often measured in mAh—that is, milliampere-hour. Table 7–1 shows the capacities ofthe devices mentioned in Chapter 2. NOTE: The ampere, named after André-Marie Ampère, is an SI unit of electric current and is often shortened to “amp.” One ampere-hour equals 3,600 coulombs库伦 , and therefore one ampere-second equals one coulomb, and one mAh equals 3.6coulombs. The coulomb, an SI unit named after Charles-Augustin de Coulomb, is rarely used in the descriptions of consumer products很少在消费品中提到.Table 7–1. Capacities of Some Android Devices’ BatteriesDevice Manufacturer Battery capacityBlade ZTE 1,250 mAhLePhone Lenovo 1,500 mAhNexus S Samsung 1,500 mAhXoom Motorola 6,500 mAhGalaxy Tab (7’’) Samsung 4,000 mAhGalaxy Tab 10.1 Samsung 7,000 mAhRevue (set-top box) Logitech n/a (not battery-powered)NSZ-GT1 (Blu-ray player) Sony n/a (not battery-powered) The fact that tablets use batteries with much larger capacities is a strong indicator明显说明 that the screen alone consumes a lot of power. Android provides a way for the user to know approximately how much power is used by applications and systemcomponents. Figure 7–1 shows how much power was used on a Galaxy Tab 10.1while spending most of the time using a slingshot弹弓 to throw choleric feathered animalsat swine猪头.Figure 7–1. Battery usage Two items clearly stand out in this screenshot: Screen and Wi-Fi. As these twocomponents use a lot of power, devices provide ways for end-users to configure theirusage. For example, users can change the brightness of the screen调节屏幕亮度 (manually or automatically based on the p_w_picpath displayed), define after how much time without activity the screen should turn off屏幕没有活动后多长时间关闭, and also have Wi-Fi turned off whenever the screen turns off. For instance, the Wi-Fi connection may represent only a few percent of the total battery usage when it is turned off as soon as the screen turns off.
NOTE: The Galaxy Tab 10.1 used here is a Wi-Fi-only version. Other items will show with
different devices, for example “Cell standby”手机待机 or “Voice calls.”语音通信 Although users themselves can proactively主动地 manage the battery usage, this is not withoutits own limitations. Ultimately, how much power is used on a device is heavily dependent on what all the applications do严重依赖于应用做什么, and therefore也就是 dependent on how you designed and implemented your application应用如何设计和实现的. Typical things your applications do are:Executing code (Captain Obvious would not have said it better)显而易见Transferring data (downloading and uploading, using Wi-Fi, EDGE, 3G, 4G)Tracking location (using network or GPS)Using sensors (accelerometer, gyroscope)Rendering p_w_picpaths (using GPU or CPU)Waking up to perform various tasks唤醒以执行各种任务Before we learn how to minimize the battery usage, we should have a way to measure
how much power the application uses. 测量电池用量Measuring Battery UsageUnfortunately, such accurate measurements require electrical equipment most developers don’t have access to. However幸好, Android provides APIs to get information about the battery usage. While there is no API such as getBatteryInfo(), it is possible to retrieve the battery information via a so-called sticky固定的 intent, that is, a broadcast intent that is always around, as shown in Listing 7–1.
Listing 7–1. Activity Showing Battery Informationimport static android.os.BatteryManager.*;// note the static keyword here (don’t know what it does? Remove it and see!)public class BatteryInfoActivity extends Activity { private static final String TAG = "BatteryInfo";private BroadcastReceiver mBatteryChangedReceiver;private TextView mTextView; // layout contains TextView to show battery informationprivate static String healthCodeToString(int health) { switch (health) { //case BATTERY_HEALTH_COLD: return "Cold"; // API level 11 onlycase BATTERY_HEALTH_DEAD: return "Dead";case BATTERY_HEALTH_GOOD: return "Good";case BATTERY_HEALTH_OVERHEAT: return "Overheat";case BATTERY_HEALTH_OVER_VOLTAGE: return "Over voltage";case BATTERY_HEALTH_UNSPECIFIED_FAILURE: return "Unspecified failure";case BATTERY_HEALTH_UNKNOWN:default: return "Unknown";}}private static String pluggedCodeToString(int plugged) { switch (plugged) { case 0: return "Battery";case BATTERY_PLUGGED_AC: return "AC";case BATTERY_PLUGGED_USB: return "USB";default: return "Unknown";}}private static String statusCodeToString(int status) { switch (status) { case BATTERY_STATUS_CHARGING: return "Charging";case BATTERY_STATUS_DISCHARGING: return "Discharging";case BATTERY_STATUS_FULL: return "Full";case BATTERY_STATUS_NOT_CHARGING: return "Not charging";case BATTERY_STATUS_UNKNOWN:default: return "Unknown";}}private void showBatteryInfo(Intent intent) {
if (intent != null) { int health = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);String healthString = "Health: " + healthCodeToString(health);Log.i(TAG, healthString);int level = intent.getIntExtra(EXTRA_LEVEL, 0);int scale = intent.getIntExtra(EXTRA_SCALE, 100);float percentage = (scale != 0) ? (100.f * (level / (float)scale)) : 0.0f;String levelString = String.format("Level: %d/%d (%.2f%%)", level, scale,percentage);Log.i(TAG, levelString);int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0);String pluggedString = "Power source: " + pluggedCodeToString(plugged);Log.i(TAG, pluggedString);boolean present = intent.getBooleanExtra(EXTRA_PRESENT, false);String presentString = "Present? " + (present ? "Yes" : "No");Log.i(TAG, presentString);int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);String statusString = "Status: " + statusCodeToString(status);Log.i(TAG, statusString);String technology = intent.getStringExtra(EXTRA_TECHNOLOGY);String technologyString = "Technology: " + technology;Log.i(TAG, technologyString);int temperature = intent.getIntExtra(EXTRA_STATUS, Integer.MIN_VALUE);String temperatureString = "Temperature: " + temperature;Log.i(TAG, temperatureString);int voltage = intent.getIntExtra(EXTRA_VOLTAGE, Integer.MIN_VALUE);String voltageString = "Voltage: " + voltage;Log.i(TAG, voltageString);String s = healthString + "\n";s += levelString + "\n";s += pluggedString + "\n";s += presentString + "\n";s += statusString + "\n";s += technologyString + "\n";s += temperatureString + "\n";s += voltageString;mTextView.setText(s);// Note: using a StringBuilder object would have been more efficientint id = intent.getIntExtra(EXTRA_ICON_SMALL, 0);setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, id);} else { String s = "No battery information";Log.i(TAG, s);mTextView.setText(s);setFeatureDrawable(Window.FEATURE_LEFT_ICON, null);}}private void showBatteryInfo() { // no receiver neededIntent intent = registerReceiver(null, newIntentFilter(Intent.ACTION_BATTERY_CHANGED));showBatteryInfo(intent);}private void createBatteryReceiver() { mBatteryChangedReceiver = new BroadcastReceiver() { @Overridepublic void onReceive(Context context, Intent intent) { showBatteryInfo(intent);}};}/** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_LEFT_ICON);setContentView(R.layout.main);mTextView = (TextView) findViewById(R.id.battery);showBatteryInfo(); // no receiver needed}@Overrideprotected void onPause() { super.onPause();// unregistering the receiver when the application is not in the foregroundsaves powerunregisterReceiver(mBatteryChangedReceiver);}@Overrideprotected void onResume() { super.onResume();if (mBatteryChangedReceiver == null) { createBatteryReceiver();}registerReceiver(mBatteryChangedReceiver,new IntentFilter(Intent.ACTION_BATTERY_CHANGED));}@Overridepublic void onLowMemory() { super.onLowMemory();unregisterReceiver(mBatteryChangedReceiver);mBatteryChangedReceiver = null;}}As you can see, the battery information is part of the intent’s extra information. This activity wants to be notified of changes therefore it registers a broadcast receiver in onResume(). However, since the sole purpose of the notification is to update the user interface with the new battery information, the activity needs to be notified only when it is in the foreground and when the user is directly interacting with the application(activity只需要在前台和用户打交道时得到通知), and consequently it unregisters the broadcast receiver in onPause().
NOTE: Another possible implementation is to move the registration and un-registration of thereceiver to onStart() and onStop() respectively分别各自的. To achieve greater power savings, it isusually better to register and unregister broadcast receivers in onResume() and onPause()though.If you need to know the current battery information but do not need to be notified ofchanges, you can simply get the sticky intent containing the battery information withoutregistering any broadcast receiver by calling registerReceiver() and passing null asthe broadcast receiver. To measure the battery usage, it is recommended you get the battery level when your application starts, use your application for a while, and then when the application exits once again get the battery level. While the difference between the two levels won’t tell you exactly how much power your own application uses (as other applications can still be running at the same time), it should give you a good idea of your application’s power usage. For example, you could determine how much time one could use your application before the battery is empty. 7.2 Disabling禁用 Broadcast Receivers To preserve the battery, applications should avoid executing code that serves no purpose. In the example above, updating the TextView’s text when the user interface is not in the foreground is of little value and will only draw power from the battery unnecessarily只会浪费电量. In addition to the ACTION_BATTERY_CHANGED sticky intent containing the battery information shown above, Android defines four more broadcast intents your application can use:ACTION_BATTERY_LOWACTION_BATTERY_OKAYACTION_POWER_CONNECTEDACTION_POWER_DISCONNECTED While you could not receive the ACTION_BATTERY_CHANGED broadcast intent by simplydeclaring a receiver in your application’s manifest (this receiver has to be registeredexplicitly with a call to registerReceiver()), these other intents allow you to register thereceivers in your application’s manifest file, as shown in Listing 7–2.Listing 7–2. Declaring Broadcast Receiver In Manifest<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android=""package="com.apress.proandroid.ch07" android:versionCode="1"android:versionName="1.0"><uses-sdk android:minSdkVersion="8" /><application android:icon="@drawable/icon" android:label="@string/app_name"><activity android:name=".BatteryInfoActivity"android:label="@string/app_name"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><receiver android:name=".BatteryReceiver"><intent-filter><action android:name="android.intent.action.BATTERY_LOW" /></intent-filter><intent-filter><action android:name="android.intent.action.BATTERY_OKAY" /></intent-filter><intent-filter><action android:name="android.intent.action.ACTION_POWER_CONNECTED" /></intent-filter><intent-filter><action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/></intent-filter></receiver></application></manifest> A simple implementation of the broadcast receiver is shown in Listing 7–3. Here wedefine a single BatteryReceiver broadcast receiver that is responsible for handling allfour actions.Listing 7–3. BatteryReceiver Implementationpublic class BatteryReceiver extends BroadcastReceiver { private static final String TAG = "BatteryReceiver";@Overridepublic void onReceive(Context context, Intent intent) { String action = intent.getAction();String text;// the four actions are processed hereif (Intent.ACTION_BATTERY_LOW.equals(action)) { text = "Low power";} else if (Intent.ACTION_BATTERY_OKAY.equals(action)) { text = "Power okay (not low anymore)";} else if (Intent.ACTION_POWER_CONNECTED.equals(action)) { text = "Power now connected";} else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) { text = "Power now disconnected";} else { return;}Log.i(TAG, text);Toast.makeText(context, text, Toast.LENGTH_SHORT).show();}} As it is now, the application can be considered to have a serious flaw缺陷. As a matter of fact, the application will start (if it is not started already) whenever one of these four actions occurs. While this may be the desired behavior, in many cases you may wantyour application to behave differently. In this case for example, we can argue it onlymakes sense to show the Toast messages when the application is the foregroundapplication, as the Toast messages would actually interfere with other applicationsshould we always show them, therefore worsening the user experience损害用户的体验. When the application is not running or is in the background, let’s say we want to disablethese Toast messages. There are basically two ways to do this:We can add a flag in the application that is set to true in the activity’s onResume() and set to false in onPause(), and modify the receiver’s onReceive() method to check that flag.We can enable the broadcast receiver only when the application is the foreground application. While the first approach would work fine, it would not prevent the application from beingstarted whenever one of the four actions triggers. This would ultimately result inunnecessary instructions being executed, which would still draw power from the batteryfor what is essentially a no-op. Besides, you may have to modify that flag in multiple filesshould your application define several activities. The second approach is much better as we can guarantee instructions are executedonly when they serve an actual purpose, and therefore power will be drawn from thebattery only for a good reason. To achieve this, there are two things we need to do inthe application:The broadcast receiver needs to be disabled by default.The broadcast receiver needs to be enabled in onResume() and disabled again in onPause(). Listing 7–4 shows how to disable the broadcast receiver in the application’s manifestfile. Listing 7–4. Disabling Broadcast Receiver In Manifest…<receiver android:name=".BatteryReceiver" android:enabled="false" >…NOTE: The <application> element has its own enabled attribute. The broadcast receiver will be enabled when both the application and receiver attributes are set to true, and will be disabled when either one of them is set to false.Listing 7–5 shows how to enable and disable the broadcast receiver in onResume() andonPause().Listing 7–5. Enabling and Disabling Broadcast Receiverpublic class BatteryInfoActivity extends Activity { …private void enableBatteryReceiver(boolean enabled) { PackageManager pm = getPackageManager();ComponentName receiverName = new ComponentName(this, BatteryReceiver.class);int newState;if (enabled) { newState = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;} else { newState = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;}pm.setComponentEnabledSetting(receiverName, newState,PackageManager.DONT_KILL_APP);}…@Overrideprotected void onPause() { super.onPause();unregisterReceiver(mBatteryChangedReceiver);enableBatteryReceiver(false); // battery receiver now disabled// unregistering the receivers when the application is not in the foregroundsaves power}@Overrideprotected void onResume() { super.onResume();if (mBatteryChangedReceiver == null) { createBatteryReceiver();}registerReceiver(mBatteryChangedReceiver, newIntentFilter(Intent.ACTION_BATTERY_CHANGED));enableBatteryReceiver(true); // battery receiver now enabled}…}
Enabling broadcast receivers only when they are really needed只有真正需要时启动广播接收器 can make a big difference in power consumption. While this is an aspect that can be easily overlooked忽视 when developing an application, special attention should be given to receivers so that they are enabled only when required.
7.3 Networking网络
Many Android applications transfer data between the device and a server, or between devices. Like the battery state, applications may need to retrieve information about the network connections on the device. The ConnectivityManager class provides APIs applications can call to have access to the network information. Android devices often have multiple data connections available:
BluetoothEthernet 以太网Wi-FiWiMAXMobile (EDGE, UMTS, LTE)Listing 7–6 shows how to retrieve information about the active connection as well as allthe connections.Listing 7–6. Network Informationprivate void showNetworkInfoToast() { ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);// to show only the active connectionNetworkInfo info = cm.getActiveNetworkInfo();if (info != null) { Toast.makeText(this, "Active: " + info.toString(),Toast.LENGTH_LONG).show();}// to show all connectionsNetworkInfo[] array = cm.getAllNetworkInfo();if (array != null) { String s = "All: ";for (NetworkInfo i: array) { s += i.toString() + "\n";}Toast.makeText(this, s, Toast.LENGTH_LONG).show();}}NOTE: Your application needs the ACCESS_NETWORK_STATE permission to be able to retrievethe network information.Since the focus is on maximizing the battery life, we need to be aware of certain things:Background data settingData transfer ratesBackground Data Users have the ability to specify whether background data transfer is allowed or not inthe settings, presumably想必 to preserve battery life. If your application needs to perform data transfers when it is not the foreground application, it should check that flag, as shown in Listing 7–7. Services typically have to check that setting before initiating any transfer.Listing 7–7. Checking Background Data Settingprivate void transferData(byte[] array) { ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);boolean backgroundDataSetting = cm.getBackgroundDataSetting();if (backgroundDataSetting) { // transfer data} else { // honor setting and do not transfer data}} Because this is a voluntary check, your application could actually ignore that setting andtransfer data anyway. However, since it would go against the wish of the user, potentially slow down foreground data transfers, and impact battery life, such behavior would likely cause your application to be uninstalled by the user eventually. To be notified when the background data setting changes, your application can registera broadcast receiver explicitly in the Java code using the ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED string to build theintent filter or android.net.conn.BACKGROUND_DATA_SETTING_CHANGED in the application’smanifest file. Because this setting is to control background data transfer, it actuallymakes more sense to disable this broadcast receiver in onResume() and enable it againin onPause(). NOTE: The getBackgroundDataSetting() method is deprecated in Android 4.0 and willalways return true. Instead, the network will appear disconnected when background datatransfer is not available. 7.3.2 Data TransferTransfer rates can vary wildly差异非常大, typically from less than 100 kilobits per second on a GPRS data connection to several megabits per second on an LTE or Wi-Fi connection.
In addition to the connection type, the NetworkInfo class specifies the subtype of a connection. This is particularly important when the connection type is TYPE_MOBILE.Android defines the following connection subtypes (in the TelephonyManager class):NETWORK_TYPE_GPRS (API level 1)NETWORK_TYPE_EDGE (API level 1)NETWORK_TYPE_UMTS (API level 1)NETWORK_TYPE_CDMA (API level 4)NETWORK_TYPE_EVDO_0 (API level 4)NETWORK_TYPE_EVDO_A (API level 4)NETWORK_TYPE_1xRTT (API level 4)NETWORK_TYPE_HSDPA (API level 5)NETWORK_TYPE_HSUPA (API level 5)NETWORK_TYPE_HSPA (API level 5)NETWORK_TYPE_IDEN (API level 8)NETWORK_TYPE_EVDO_B (API level 9)NETWORK_TYPE_LTE (API level 11)NETWORK_TYPE_EHRPD (API level 11)NETWORK_TYPE_HSPAP (API level 13)Subtypes are added as new technologies are created and deployed. For example, theLTE subtype was added in API level 11, whereas the HSPAP subtype was added in API level 13. If your code depends on these values, make sure you handle the case where your application is presented with a new value it does not know about; otherwise it could result in your application not being able to transfer data. You should update your code when new subtypes are defined, so pay attention to each release of the Android SDK. A list of differences is available on , for example. Intuitively直觉的, your application should prefer faster connections. Even if the 3G radio chip consumes less power than the Wi-Fi radio chip, the Wi-Fi transfer rate may ultimately mean the Wi-Fi transfer reduces power consumption as the transfer can be completed in a shorter time. NOTE: Since data plans now typically allow for a limited amount of data to be transferred (for example, $30 for 2GB a month), Wi-Fi connections are usually preferred. Also, your application can use NetworkInfo.isRoaming() to know if the device is currently roaming on the given network. Since this can incur招致 additional cost, you should avoid transferring data when isRoaming() returns true. Table 7–2 shows the memory consumption of various components on the T-Mobile G1phone (also known as the HTC Dream, or Era G1). While the phone is somewhat old now(it was released in late 2008), the numbers still give a pretty good overview of how muchpower each component draws.Table 7–2. Android G1Phone Power Consumption (source: Google I/O 2009)Component Power consumptionIdle, airplane mode (radios turned off) 2 mAIdle, 3G radio on 5 mAIdle, EDGE radio on 5 mAIdle, Wi-Fi radio on 12 mADisplay (LCD) 90 mA (min brightness: 70 mA; max brightness: 110 mA)CPU (100% load) 110 mASensors 80 mAGPS 85 mA3G (max transfer) 150 mAEDGE (max transfer) 250 mAWi-Fi (max transfer) 275 mA While the exact numbers vary between devices各设备间差异很大, it is important to know roughly how much power your application would use. Since the G1 had a 1,150 mAh battery, an application that downloads and plays videos (for example, YouTube) would empty the battery in about three hours assuming it uses a 3G connection: 150mA for 3G, 90 mA for CPU, and 90mA for LCD would total 330 mA, or three and a half hours of usage (assuming nothing else runs on the phone). If you have control over what kind of data gets transferred, then you should considercompressing the data before it is sent to the device. While the CPU will have todecompress the data before it can be used (and therefore more power will be neededfor that purpose), the transfer will be faster and the radios (for example, 3G, Wi-Fi) canbe turned off again faster, preserving battery life. The things to consider are: Compress text data using GZIP and use the GZIPInputStream to access the data. Use JPEG instead of PNG if possible. Use assets that match the resolution of the device (for example, there is no need to download a 1920x1080 picture if it is going to be resized to 96x54). The slower the connection (for example, EDGE) the more important compression is, asyou want to reduce the time the radios are turned on. Since Android is running on more and more devices, from cell phones to tablets, fromset-top boxes to netbooks, generating assets for all these devices can become tedious漫长. However, using the right assets can greatly improve the battery life and therefore make your application more desirable. In addition to saving power, faster downloads anduploads will make your application more responsive.7.4 Location 位置 Any real estate agent×××经纪人 will tell you the three most important things are location, location, location. Android understands that and lets your application know where the device is. (It won’t tell you if the device is in a good school district, although I am pretty sure there is an application for that.) Listing 7–8 shows how to request location updates using thesystem location services.Listing 7–8. Receiving Location Updatesprivate void requestLocationUpdates() { LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);List<String> providers = lm.getAllProviders();if (providers != null && ! providers.isEmpty()) { LocationListener listener = new LocationListener() { @Overridepublic void onLocationChanged(Location location) { Log.i(TAG, location.toString());}@Overridepublic void onProviderDisabled(String provider) { Log.i(TAG, provider + " location provider disabled");}@Overridepublic void onProviderEnabled(String provider) { Log.i(TAG, provider + " location provider enabled");}@Overridepublic void onStatusChanged(String provider, int status, Bundle extras){ Log.i(TAG, provider + " location provider status changed to " +status);}};for (String name : providers) { Log.i(TAG, "Requesting location updates on " + name);lm.requestLocationUpdates(name, 0, 0, listener);}}} NOTE: Your application needs either the ACCESS_COARSE_LOCATION permission or theACCESS_FINE_LOCATION permission to be able to retrieve the location information. The GPSlocation provider requires the ACCESS_FINE_LOCATION permission while the network providerrequires either ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION. This piece of code has serious flaws, but you should still run it, for example in your application’s onCreate() method, in order to see the impact this code has on the battery life. On a Galaxy Tab 10.1, one can observe the following:Three location providers are available (network, gps, and passive被动定位服务).GPS location updates are very frequent (one update per second).Network location updates are not as frequent (one update every 45 seconds). If you let your application with this piece of code run for a while, you should see thebattery level going down faster than usual. This code’s three main flaws are:Location listeners are not unregistered.Location updates are most likely too frequent.Location updates are requested on multiple providers同时使用多种位置服务. Luckily, all these flaws can easily be fixed. Ultimately though, how you need to use the location services depends on what your application needs to do so there is no one single solution for all applications, so what is considered a flaw in one application may be a feature in another. Also note that there is typically not one single solution for all users: you should consider the needs of your users as well and offer different settings in your application to cater for your end-users. For example, some users may be willing to sacrifice battery life to get more frequent location updates一些用户宁愿牺牲电量来频繁更新位置 while others would rather limit the number of updates to make sure their device does not need to be charged during their lunch break. 7.4.1Unregistering a Listener
In order to unregister a listener, you can simply call removeUpdates() as showed in Listing 7–9. It is usually good to do that in onPause() like for broadcast receivers but your application many need to do that elsewhere. Listening for location updates for a long period of time will consume a lot of power so your application should try to get the information it needs and then stop listening for updates. In some cases it can be a good idea to offer a way for the user to force a location fix.
Listing 7–9. Disabling Location Listenerprivate void disableLocationListener(LocationListener listener) { LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);lm.removeUpdates(listener);}The frequency of the updates can be adjusted when calling requestLocationUpdates(). 7.4.2 Frequency of Updates更新频率Even though the LocationManager class defines five requestLocationUpdates() methods, all have two parameters in common: minTime and minDistance. The first one, minTime, specifies the time interval for notifications in milliseconds. This is used only as a hint for the system to save power and the actual time between updates may be greater (or less) than specified. Obviously, greater values will result in greater power savings值越大越省电.The second one, minDistance, specifies the minimum distance interval for notifications. Greater values can still result in power savings as fewer instructions would be executed if your application is not notified of all location updates. Listing 7–10 shows how to
register for updates with one hour between updates and a 100-meter threshold间距.Listing 7–10. Receiving Location Updates Not As Frequentlyprivate void requestLocationUpdates() { LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);List<String> providers = lm.getAllProviders();if (providers != null) { for (String name : providers) { LocationListener listener = new LocationListener() { …};Log.i(TAG, "Requesting location updates on " + name);lm.requestLocationUpdates(name, DateUtils.HOUR_IN_MILLIS * 1, 100,listener);}}} Choosing the right interval is more art than science and once again depends on your application. A navigation application would typically require frequent updates, but if the same navigation application can be used by hikers, for example, updates could be less frequent. Moreover, you may have to trade accuracy for power in some cases. Give your users the choice and offer settings that can be adjusted to let them choose the best behavior for themselves. 7.4.3 Multiple Providers 多种位置服务 As mentioned earlier, Android defines multiple location providers:GPS (Global Positioning System using satellites)Network (using Cell-ID and Wi-Fi locations)Passive被动 (since API level 8) The GPS location provider (LocationManager.GPS_PROVIDER) is typically the most accurate one, with a horizontal accuracy of about 10 meters (11 yards). The network location provider is not as accurate as the GPS, and the accuracy will depend on how many locations the system can use to compute the device’s location. For example, my own logs show an accuracy of 48 meters for locations coming from the network location provider, or the width of a football field. While more accurate, GPS locations are expensive in terms of time and battery usage. Getting a “fix” from the GPS location provider requires locking the signals from multiple satellites, which can take from a few seconds in an open field to an infinite amount of time if the device is indoors and signal cannot be locked (like in a parking garage when your car’s GPS tries to acquire satellites). For example, it took about 35 seconds to get the first location update after GPS was enabled while inside the house, but only 5 seconds when the same test was repeated outside. When using the network location provider, it took 5 seconds to get the first location update, inside and outside. An Assisted辅助 GPS (AGPS) would typically provide faster locations fixes, but actual times would depend on information already cached by the device and the network access. NOTE: The Galaxy Tab 10.1 used for these measurements is Wi-Fi only. Faster location fixeswould be achieved by the network provider if cell ids were also used.Even though receiving updates from multiple providers was listed as a flow earlier, it may not always be one. As a matter of fact, you may want to receive updates from several providers for a while in order to get a more accurate location fix如果用上基站定位会更快.
The passive location provider is the one that can preserve the battery the most最省电量. When your application uses the passive location provider, it says it is interested in location updates but won’t be proactively requesting location fixes. In other words, your application will simply wait for other applications, services, or system components to request location updates and will be notified together with these other listeners.Listing 7–11 shows how to receive passive location updates. To test whether yourapplication receives updates, open another application that makes use of the locationservices, such as Maps.Listing 7–11. Receiving Passive Location Updatesprivate void requestPassiveLocationUpdates() { LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);LocationListener listener = new LocationListener() { @Overridepublic void onLocationChanged(Location location) { Log.i(TAG, "[PASSIVE] " + location.toString());// let's say you only care about GPS location updatesif (LocationManager.GPS_PROVIDER.equals(location.getProvider())) { // if you care about accuracy, make sure you call hasAccuracy()// (same comment for altitude and bearing)if (location.hasAccuracy() && (location.getAccuracy() < 10.0f)) { // do something here}}}@Overridepublic void onProviderDisabled(String provider) { Log.i(TAG, "[PASSIVE] " + provider + " location provider disabled");}@Overridepublic void onProviderEnabled(String provider) { Log.i(TAG, "[PASSIVE] " + provider + " location provider enabled");}@Overridepublic void onStatusChanged(String provider, int status, Bundle extras) { Log.i(TAG, "[PASSIVE] " + provider + " location provider status changedto " + status);}};Log.i(TAG, "Requesting passive location updates");lm.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,DateUtils.SECOND_IN_MILLIS * 30, 100, listener);} If you use that code and disable or enable Wi-Fi or GPS, you will notice the passive listener does not get notified when a provider is disabled or enabled, or when a provider’s status changes. While this is usually not important, this may force your application to use the other location providers should it really care about being notified of these changes. A good trade-off is to register a listener with the network location provider, which uses less power than the GPS provider, while also registering a listener with the passive location provider in order to get possibly more accurate location information from the GPS.NOTE: Your application will need the ACCESS_FINE_LOCATION to use the passive location
provider even if it only receives location updates from the network provider. This may end upbeing a problem if you believe this may raise privacy concerns with your users. There is currentlyno way to receive passive updates only from the network location provider and only ask for theACCESS_COARSE_LOCATION permission. 7.4.4 Filtering Providers 筛选定位服务Since our focus is on battery life, your application may want to filter out location providers with a high power requirement if using a passive location provider is not an option. Listing
7–12 shows how you can get the power requirement of all the location providers.Listing 7–12. Location Providers Power Requirementprivate static String powerRequirementCodeToString(int powerRequirement) { switch (powerRequirement) { case Criteria.POWER_LOW: return "Low";case Criteria.POWER_MEDIUM: return "Medium";case Criteria.POWER_HIGH: return "High";default: return String.format("Unknown (%d)", powerRequirement);}}private void showLocationProvidersPowerRequirement() { LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);List<String> providers = lm.getAllProviders();if (providers != null) { for (String name : providers) { LocationProvider provider = lm.getProvider(name);if (provider != null) { int powerRequirement = provider.getPowerRequirement();Log.i(TAG, name + " location provider power requirement: " +powerRequirementCodeToString(powerRequirement));}}}} NOTE: As one would expect, the power requirement of the passive location provider is unknown. However, since your application may have very specific needs, it may be easier to first specify as precisely as possible what location provider you are looking for. For example, your application may want to use a location provider that reports speed information in addition to coordinates. Listing 7–13 shows how you can create a Criteria object and find out which location provider you should be using.Listing 7–13. Using Criteria to Find Location Providerprivate LocationProvider getMyLocationProvider() { LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);Criteria criteria = new Criteria();LocationProvider provider = null;// define your criteria herecriteria.setAccuracy(Criteria.ACCURACY_COARSE);criteria.setAltitudeRequired(true);criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT); // API level 9criteria.setBearingRequired(false);criteria.setCostAllowed(true); // most likely you want the user to be able toset thatcriteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW); // API level 9criteria.setPowerRequirement(Criteria.POWER_LOW);criteria.setSpeedAccuracy(Criteria.ACCURACY_MEDIUM); // API level 9criteria.setSpeedRequired(false);criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT); // API level 9List<String> names = lm.getProviders(criteria, false); // perfect matches onlyif ((names != null) && ! names.isEmpty()) { for (String name : names) { provider = lm.getProvider(name);Log.d(TAG, "[getMyLocationProvider] " + provider.getName() + " " +provider);}provider = lm.getProvider(names.get(0));} else { Log.d(TAG, "Could not find perfect match for location provider");String name = lm.getBestProvider(criteria, false); // not necessarilyperfect matchif (name != null) { provider = lm.getProvider(name);Log.d(TAG, "[getMyLocationProvider] " + provider.getName() + " " +provider);}}return provider;}LocationManager.getProviders() and LocationManager.getBestProvider() differ quitesignificantly. While getProviders() will only return perfect matches, getBestProvider()will first search for a perfect match but may return a location provider that does not meetthe criteria if no provider was a perfect match. The criteria are loosened in the followingsequence:Power requirementAccuracyBearing 方位角SpeedAltitude Since this order may not necessarily be the policy you want to adopt to find a location provider, you may have to develop your own algorithm to find the right provider for your needs. Also, the algorithm may depend on the current battery status: your application may not be willing to loosen the power requirement criterion if the battery is low.7.4.5 Last Known Location
Before you decide to register a location listener with a location provider, you may first want to check if a location is already known (and was cached by the system). The LocationManager class defines the getLastKnownLocation() method, which returns the last known location for a given provider, or null if no known location exists. While this location may be out of date, it is often a good starting point since this location can be retrieved instantly and calling this method won’t start the provider. Even applicationsthat register a location listener usually first retrieve the last known location in order to bemore responsive as it typically takes a few seconds before any location update is received. Listing 7–14 shows how a last known location can be retrieved.Listing 7–14. Last Known Locationprivate Location getLastKnownLocation() { LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);List<String> names = lm.getAllProviders();Location location = null;if (names != null) { for (String name : names) { if (! LocationManager.PASSIVE_PROVIDER.equals(name)) { Location l = lm.getLastKnownLocation(name);if ((l != null) && (location == null || l.getTime() >location.getTime())) { location = l;/** Warning: GPS and network providers' clocks may be out of sync so comparing the times* may not be such a good idea... We may not get the most recentlocation fix after all.*/}}}}return location;}While a GPS is sensitive to satellite signals, most Android devices come with other types of sensors that can make Android applications more interesting than what you would typically find on a traditional computer. 7.5 Sensors传感器 Sensors are fun. Everybody likes them and everybody wants to use them. The way to use sensors is very similar to the way to use location providers: your application registers a sensor listener with a specific sensor and is notified of updates. Listing 7–15 shows how you can register a listener with the device’s accelerometer.Listing 7–15. Registering Sensor Listener With Accelerometerprivate void registerWithAccelerometer() { SensorManager sm = (SensorManager) getSystemService(Context.SENSOR_SERVICE);List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ACCELEROMETER);if (sensors != null && ! sensors.isEmpty()) { SensorEventListener listener = new SensorEventListener() { @Overridepublic void onAccuracyChanged(Sensor sensor, int accuracy) { Log.i(TAG, "Accuracy changed to " + accuracy);}@Overridepublic void onSensorChanged(SensorEvent event) { /** Accelerometer: array of 3 values** event.values[0] = acceleration minus Gx on the x-axis* event.values[1] = acceleration minus Gy on the y-axis* event.values[2] = acceleration minus Gz on the z-axis*/Log.i(TAG, String.format("x:%.2f y:%.2f z:%.2f ",event.values[0], event.values[1], event.values[2]));// do something interesting here}};// we simply pick the first oneSensor sensor = sensors.get(0);Log.d(TAG, "Using sensor " + sensor.getName() + " from " +sensor.getVendor());sm.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL);In the same manner we could specify how often we wanted location updates, Androidlets applications specify how often they want to receive sensor updates去获取传感器更新. While we used milliseconds with the location providers, we have to use one of four possible values to specify how often we want to receive sensor updates:SENSOR_DELAY_NORMALSENSOR_DELAY_UISENSOR_DELAY_GAMESENSOR_DELAY_FASTEST On a Galaxy Tab 10.1 (using an MPL accelerometer from Invensense), theaccelerometer’s NORMAL, UI, GAME, and FASTEST delays are about 180, 60, 20, and10 milliseconds respectively. While these numbers vary depending on devices, fasterupdates will require more power. For example, on the Android G1 phone, using theNORMAL delay draws 10 mA from the battery while using the FASTEST delay draws 90mA (15 mA for UI and 80 mA for GAME). Like for location providers像位置服务一样, reducing the frequency of the notification is the best way to preserve battery life. Since every device is different, your application can measure the frequency of the notifications for each of the four delays and choose the one that gives the best user experience while still conserving power. Another strategy, which may not apply to all applications, is to use the NORMAL or UI delay when you detect that the values don’t seem to change that much, and switch to a GAME or FASTEST delay when you detect sudden variations. Such strategy may give acceptable results for someapplications and would result in a longer battery life. Like other listeners, the sensor listeners should be disabled whenever notifications arenot needed. For this purpose, use the SensorManager’s unregisterListener() methods.
7.6 Graphics图形
Applications can spend a lot of time drawing things on the screen. Whether it is a 3D
game using the GPU or simply a Calendar application using the CPU for most of itsrendering, the idea is to do as little work as possible while still getting the desired resultson the screen in order to preserve battery life. As we saw earlier, the CPU uses less power when it is not running at full speed非全速使用时.Modern CPUs use dynamic frequency scaling调整频率 and dynamic voltage电压 scaling to conserve power or reduce heat. Such techniques are usually used together and referred to as DVFS (Dynamic Voltage and Frequency Scaling动态电压和频率调整) and the Linux kernel, Android, and modern processors support such techniques.
Similarly, modern GPUs are able to turn off internal components, from a whole core toan individual pipeline, even between the renderings of two frames. While you have no direct control over things like voltage, frequency, or what hardwaremodule gets powered off, you do have direct control over what your application renders.While achieving a good frame rate is usually the first priority for most applications,reducing the power consumption should not be forgotten. Even though the frame rateon Android devices is usually capped 通常有上限(for example, at 60 frames per second), optimizing your rendering routines can still be beneficial even if your application has already reached the maximum frame rate. In addition to possibly reducing power consumption, you may also leave more room for other background applications to run, providing abetter overall user experience. For example, a typical pitfall缺陷 is to ignore the call to onVisibilityChanged() in a live wallpaper. The fact that the wallpaper can be invisible can easily be overlooked, and perpetually永远的 drawing the wallpaper can use a lot of power. For tips on how to optimize rendering, refer to Chapter 8.7.7 Alarms提醒
Your application may for some reason need to wake up every now and then to perform some operation. A typical example would be an RSS reader application that wakes up every 30 minutes to download RSS feeds so that the user always has an updated view of the feed when the application is started, or a stalker高视昂步者 application that sends a message to one of your contacts联系人 every 5 minutes. Listing 7–16 shows how an alarm can be created to wake up the application and start a service that simply prints out a message and terminates随后终止. Listing 7–17 shows the implementation of the service.Listing 7–16. Setting Up An Alarmprivate void setupAlarm(boolean cancel) { AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);Intent intent = new Intent(this, MyService.class);PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);if (cancel) { am.cancel(pendingIntent); // will cancel all alarms whose intent matchesthis one} else { long interval = DateUtils.HOUR_IN_MILLIS * 1;long firstInterval = DateUtils.MINUTE_IN_MILLIS * 30;am.setRepeating(AlarmManager.RTC_WAKEUP, firstInterval, interval,pendingIntent);// use am.set(…) to schedule a non-repeating alarm}}Listing 7–17. Service Implementationpublic class MyService extends Service { private static final String TAG = "MyService";@Overridepublic IBinder onBind(Intent intent) { // we return null as client cannot bind to the servicereturn null;}@Overridepublic void onStart(Intent intent, int startId) { super.onStart(intent, startId);Log.i(TAG, "Alarm went off – Service was started");stopSelf(); // remember to call stopSelf() when done to free resources}} As seen with the sensor event listeners, a single value can make a big difference as far as power consumption is concerned. Here this value is AlarmManager.RTC_WAKEUP.Android defines four types of alarms:ELAPSED_TIMEELAPSED_TIME_WAKEUPRTCRTC_WAKEUP The RTC and ELAPSED_TIME types differ only in the way the time is represented, in milliseconds since the Unix epoch for both RTC types (RTC and RTC_WAKEUP) vs. in milliseconds since boot time for both ELAPSED_TIME types (ELAPSED_TIME andELAPSED_TIME_WAKEUP). The key here is in the _WAKEUP suffix. An RTC or ELAPSED_TIME alarm that goes off whenthe device is asleep won’t be delivered until the next time the device wakes up, whereasan RTC_WAKEUP or ELAPSED_TIME_WAKEUP alarm will wake up the device when it goes off. Obviously, waking up the device continuously can have a dramatic戏剧 impact on power consumption even if the application that is woken up does not do anything:The device will wake up to start your own application. Other (well-behaved) alarms that were waiting for the device to wake up will be delivered. As you can see, this can trigger a chain reaction. Even when the other alarms are the ones consuming more power, for example because they end up transferring data over a3G connection, your application is the one that triggered everything. Few applications really need to forcibly wake up the device when an alarm goes off到时间. Ofcourse an application such as an alarm clock would need such capability, but it is oftenpreferred to simply wait for the device to wake up (most commonly following a user interaction with the device) before doing any work. As with the location provider, following a passive approach can lead to a longer battery life. Scheduling Alarms 调度提醒More often than not多数情况下, applications need to schedule alarms that go off at some point in
the future, without any strict requirement of when exactly they should go off. For that purpose, Android defines the AlarmManager.setInexactRepeating(), which takes the same parameters as its sibling setRepeating(). The main difference is in the way the system schedules the time the alarm will actually go off: Android can adjust the actual trigger time to fire multiple alarms (possibly from multiple applications) simultaneously. Such alarms are more power-efficient更节能 as the system will avoid waking up the devicemore often than necessary. Android defines five intervals for these alarms:INTERVAL_FIFTEEN_MINUTESINTERVAL_HALF_HOURINTERVAL_HOURINTERVAL_HALF_DAYINTERVAL_DAY While these values are defined as the number of milliseconds they represent (forexample, INTERVAL_HOUR equals 3,600,000), they are the only intervalssetInexactRepeating() understands to create “inexact alarms.” Passing any other valueto setInexactRepeating will be equivalent to calling setRepeating(). Listing 7–18 showshow to use inexact alarms.Listing 7–18. Setting Up An Inexact Alarmprivate void setupInexactAlarm(boolean cancel) { AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);Intent intent = new Intent(this, MyService.class);PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);if (cancel) { am.cancel(pendingIntent); // will cancel all alarms whose intent matchesthis one} else { long interval = AlarmManager.INTERVAL_HOUR;long firstInterval = DateUtils.MINUTE_IN_MILLIS * 30;am.setInexactRepeating(AlarmManager.RTC, firstInterval, interval,pendingIntent);}} TIP: There is no setInexact() method. If your application needs to schedule an inexact alarmthat goes off only once, call setInexactRepeating() and cancel the alarm after it goes offthe first time. Clearly, the best results are achieved when all applications use such alarms instead ofalarms with exact trigger times. To maximize power savings, your application can alsolet the user configure how often alarms should go off, as some may find that longerintervals do not negatively impact their user experience.7.8WakeLocks保持进程在休眠时从屏幕消失
Some applications, in some cases, need to prevent the device from going to sleep tomaintain a good user experience even if the user is not interacting with the device for anextended period of time. The simplest example, and probably the most relevant, is whena user is watching a video or movie on the device. In such a case, the CPU needs todecode the video while the screen needs to be on for the user to be able to watch it.Also, the screen should not be dimmed while the video is playing.The WakeLock class allows for such scenarios, as shown in Listing 7–19.Listing 7–19. Creating WakeLockprivate void runInWakeLock(Runnable runnable, int flags) { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);PowerManager.WakeLock wl = pm.newWakeLock(flags, "My WakeLock");wl.acquire();runnable.run();wl.release();} NOTE: Your application needs the WAKE_LOCK permission to be able to use WakeLock objects.How the system will behave depends on which flag is used when the WakeLock objectis created. Android defines the following flags:PARTIAL_WAKE_LOCK (CPU on)SCREEN_DIM_WAKE_LOCK (CPU on, display dimmed)SCREEN_BRIGHT_WAKE_LOCK (CPU on, bright display)FULL_WAKE_LOCK (CPU on, bright display and keyboard)The flags can be combined with two more flags:ACQUIRE_CAUSES_WAKEUP (to turn on screen and/or keyboard)ON_AFTER_RELEASE (to keep screen and/or keyboard turned onfor a little longer after the WakeLock is released) While their use is trivial, WakeLocks can cause significant problems if they are not released. A buggy童车 application may simply forget to release a WakeLock, causing, for example, the display to remain on for a very long time, emptying the battery very quickly.In general, WakeLocks should be released as soon as possible. For example, an application acquiring a WakeLock when a video is playing should most likely release it when the video is paused, and acquire it again when the user starts playing the video again. It should also release the WakeLock when the application is paused and acquireit again when the application is resumed (if the video is still playing at that time). As youcan see, the number of different cases to handle can grow quickly, making your application prone to have bugs. 防止出现问题 Preventing Issues To prevent possible problems, it is recommended you use the timeout version of WakeLock.acquire(), which will guarantee the WakeLock will be released after the given timeout. For example, an application playing a video could use the duration of the video as the WakeLock timeout视频长度作为wakeLock超时时间. Alternatively, if keeping the screen on is associated with a view in an activity, you canuse the XML attribute android:keepScreenOn in your layout files. A benefit of using that approach is that you don’t take the risk of forgetting to release the WakeLock, as it is handled by the system and no additional permission is needed in the application’s manifest file. Listing 7–20 shows how to use the attribute with a linear layout element.Listing 7–20. keepScreenOn XML Attribute<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android=""android:keepScreenOn="true"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">…</LinearLayout> TIP: android:keepScreenOn can be used in any view. As long as one visible view specifies the screen should remain on, the screen will remain on. You can also control whether the screen should remain on with the View.setKeepScreenOn() method.
Despite the problems they may cause, WakeLocks are sometimes necessary. If you have to use them, make sure you carefully think about when they should be acquiredand when they should be released. Also make sure you fully understand yourapplication’s lifecycle, and make sure the test cases are exhaustive确保详细用例. WakeLocks are only problematic when bugs exist!
SummaryUsers typically won’t notice if your application preserves battery life应用是否延长电池寿命. However, they most likely will notice if it does not. All applications need to behave and cooperate as much as possible in order to maximize the battery life, as a single application can ruin all the efforts made by others. As users often uninstall applications that consume too much power, your application needs to make a sensible use of the battery and yet should give the users options to configure certain things给用户配置选项的自由 as different users will ask for different things. Empower your users给你的用户配置的权利.