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 quite
easy to draw so much power from the battery that the device runs out of juice in the
middle of the day, leaving the user without a phone or tablet for several hours. An
application that empties the battery quickly will most likely become a strong candidate
for 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 can
conserve power without negatively impacting the user experience, using some of the
very things that make Android applications appealing: networking, access to location
information, and sensors. You also learn how to work efficiently with more internal
components 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�C1 shows the capacities of
the 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.6
coulombs. The coulomb, an SI unit named after Charles-Augustin de Coulomb, is rarely used in the descriptions of consumer products很少在消费品中提到.
Table 7�C1. Capacities of Some Android Devices’ Batteries
Device Manufacturer Battery capacity
Blade ZTE 1,250 mAh
LePhone Lenovo 1,500 mAh
Nexus S Samsung 1,500 mAh
Xoom Motorola 6,500 mAh
Galaxy Tab (7’’) Samsung 4,000 mAh
Galaxy Tab 10.1 Samsung 7,000 mAh
Revue (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 system
components. Figure 7�C1 shows how much power was used on a Galaxy Tab 10.1
while spending most of the time using a slingshot弹弓 to throw choleric feathered animals
at swine猪头.
Figure 7�C1. Battery usage
Two items clearly stand out in this screenshot: Screen and Wi-Fi. As these two
components use a lot of power, devices provide ways for end-users to configure their
usage. For example, users can change the brightness of the screen调节屏幕亮度 (manually or automatically based on the image 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 without
its 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 images (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 Usage
Unfortunately, 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�C1.
Listing 7�C1. Activity Showing Battery Information
import 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 information
private static String healthCodeToString(int health) {
switch (health) {
//case BATTERY_HEALTH_COLD: return "Cold"; // API level 11 only
case 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 efficient
int 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 needed
Intent intent = registerReceiver(null, new
IntentFilter(Intent.ACTION_BATTERY_CHANGED));
showBatteryInfo(intent);
}
private void createBatteryReceiver() {
mBatteryChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
showBatteryInfo(intent);
}
};
}
/** Called when the activity is first created. */
@Override
public 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
}
@Override
protected void onPause() {
super.onPause();
// unregistering the receiver when the application is not in the foreground
saves power
unregisterReceiver(mBatteryChangedReceiver);
}
@Override
protected void onResume() {
super.onResume();
if (mBatteryChangedReceiver == null) {
createBatteryReceiver();
}
registerReceiver(mBatteryChangedReceiver,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
}
@Override
public 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 the
receiver to onStart() and onStop() respectively分别各自的. To achieve greater power savings, it is
usually 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 of
changes, you can simply get the sticky intent containing the battery information without
registering any broadcast receiver by calling registerReceiver() and passing null as
the 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_LOW
ACTION_BATTERY_OKAY
ACTION_POWER_CONNECTED
ACTION_POWER_DISCONNECTED
While you could not receive the ACTION_BATTERY_CHANGED broadcast intent by simply
declaring a receiver in your application’s manifest (this receiver has to be registered
explicitly with a call to registerReceiver()), these other intents allow you to register the
receivers in your application’s manifest file, as shown in Listing 7�C2.
Listing 7�C2. Declaring Broadcast Receiver In Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="
http://schemas.android.com/apk/res/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�C3. Here we
define a single BatteryReceiver broadcast receiver that is responsible for handling all
four actions.
Listing 7�C3. BatteryReceiver Implementation
public class BatteryReceiver extends BroadcastReceiver {
private static final String TAG = "BatteryReceiver";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
String text;
// the four actions are processed here
if (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 want
your application to behave differently. In this case for example, we can argue it only
makes sense to show the Toast messages when the application is the foreground
application, as the Toast messages would actually interfere with other applications
should 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 disable
these 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 being
started whenever one of the four actions triggers. This would ultimately result in
unnecessary instructions being executed, which would still draw power from the battery
for what is essentially a no-op. Besides, you may have to modify that flag in multiple files
should your application define several activities.
The second approach is much better as we can guarantee instructions are executed
only when they serve an actual purpose, and therefore power will be drawn from the
battery only for a good reason. To achieve this, there are two things we need to do in
the 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�C4 shows how to disable the broadcast receiver in the application’s manifest
file.
Listing 7�C4. 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�C5 shows how to enable and disable the broadcast receiver in onResume() and
onPause().
Listing 7�C5. Enabling and Disabling Broadcast Receiver
public 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);
}
…
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mBatteryChangedReceiver);
enableBatteryReceiver(false); // battery receiver now disabled
// unregistering the receivers when the application is not in the foreground
saves power
}
@Override
protected void onResume() {
super.onResume();
if (mBatteryChangedReceiver == null) {
createBatteryReceiver();
}
registerReceiver(mBatteryChangedReceiver, new
IntentFilter(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:
Bluetooth
Ethernet 以太网
Wi-Fi
WiMAX
Mobile (EDGE, UMTS, LTE)
Listing 7�C6 shows how to retrieve information about the active connection as well as all
the connections.
Listing 7�C6. Network Information
private void showNetworkInfoToast() {
ConnectivityManager cm = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
// to show only the active connection
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null) {
Toast.makeText(this, "Active: " + info.toString(),
Toast.LENGTH_LONG).show();
}
// to show all connections
NetworkInfo[] 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 retrieve
the network information.
Since the focus is on maximizing the battery life, we need to be aware of certain things:
Background data setting
Data transfer rates
Background Data
Users have the ability to specify whether background data transfer is allowed or not in
the 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�C7. Services typically have to check that setting before initiating any transfer.
Listing 7�C7. Checking Background Data Setting
private 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 and
transfer 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 register
a broadcast receiver explicitly in the Java code using the ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED string to build the
intent filter or android.net.conn.BACKGROUND_DATA_SETTING_CHANGED in the application’s
manifest file. Because this setting is to control background data transfer, it actually
makes more sense to disable this broadcast receiver in onResume() and enable it again
in onPause().
NOTE: The getBackgroundDataSetting() method is deprecated in Android 4.0 and will
always return true. Instead, the network will appear disconnected when background data
transfer is not available.
7.3.2 Data Transfer
Transfer 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, the
LTE 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 http://d.android.com/sdk/api_diff/13/changes.html, 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�C2 shows the memory consumption of various components on the T-Mobile G1
phone (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 much
power each component draws.
Table 7�C2. Android G1Phone Power Consumption (source: Google I/O 2009)
Component Power consumption
Idle, airplane mode (radios turned off) 2 mA
Idle, 3G radio on 5 mA
Idle, EDGE radio on 5 mA
Idle, Wi-Fi radio on 12 mA
Display (LCD) 90 mA (min brightness: 70 mA; max brightness: 110 mA)
CPU (100% load) 110 mA
Sensors 80 mA
GPS 85 mA
3G (max transfer) 150 mA
EDGE (max transfer) 250 mA
Wi-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 consider
compressing the data before it is sent to the device. While the CPU will have to
decompress the data before it can be used (and therefore more power will be needed
for that purpose), the transfer will be faster and the radios (for example, 3G, Wi-Fi) can
be 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, as
you want to reduce the time the radios are turned on.
Since Android is running on more and more devices, from cell phones to tablets, from
set-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 and
uploads 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�C8 shows how to request location updates using the
system location services.
Listing 7�C8. Receiving Location Updates
private void requestLocationUpdates() {
LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
List<String> providers = lm.getAllProviders();
if (providers != null && ! providers.isEmpty()) {
LocationListener listener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
Log.i(TAG, location.toString());
}
@Override
public void onProviderDisabled(String provider) {
Log.i(TAG, provider + " location provider disabled");
}
@Override
public void onProviderEnabled(String provider) {
Log.i(TAG, provider + " location provider enabled");
}
@Override
public 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 the
ACCESS_FINE_LOCATION permission to be able to retrieve the location information. The GPS
location provider requires the ACCESS_FINE_LOCATION permission while the network provider
requires 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 the
battery 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�C9. 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�C9. Disabling Location Listener
private 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�C10 shows how to
register for updates with one hour between updates and a 100-meter threshold间距.
Listing 7�C10. Receiving Location Updates Not As Frequently
private 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 fixes
would 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�C11 shows how to receive passive location updates. To test whether your
application receives updates, open another application that makes use of the location
services, such as Maps.
Listing 7�C11. Receiving Passive Location Updates
private void requestPassiveLocationUpdates() {
LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
LocationListener listener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
Log.i(TAG, "[PASSIVE] " + location.toString());
// let's say you only care about GPS location updates
if (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
}
}
}
@Override
public void onProviderDisabled(String provider) {
Log.i(TAG, "[PASSIVE] " + provider + " location provider disabled");
}
@Override
public void onProviderEnabled(String provider) {
Log.i(TAG, "[PASSIVE] " + provider + " location provider enabled");
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.i(TAG, "[PASSIVE] " + provider + " location provider status changed
to " + 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 up
being a problem if you believe this may raise privacy concerns with your users. There is currently
no way to receive passive updates only from the network location provider and only ask for the
ACCESS_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�C12 shows how you can get the power requirement of all the location providers.
Listing 7�C12. Location Providers Power Requirement
private 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�C13 shows how you can create a Criteria object and find out which location provider you should be using.
Listing 7�C13. Using Criteria to Find Location Provider
private LocationProvider getMyLocationProvider() {
LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
LocationProvider provider = null;
// define your criteria here
criteria.setAccuracy(Criteria.ACCURACY_COARSE);
criteria.setAltitudeRequired(true);
criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT); // API level 9
criteria.setBearingRequired(false);
criteria.setCostAllowed(true); // most likely you want the user to be able to
set that
criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW); // API level 9
criteria.setPowerRequirement(Criteria.POWER_LOW);
criteria.setSpeedAccuracy(Criteria.ACCURACY_MEDIUM); // API level 9
criteria.setSpeedRequired(false);
criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT); // API level 9
List<String> names = lm.getProviders(criteria, false); // perfect matches only
if ((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 necessarily
perfect match
if (name != null) {
provider = lm.getProvider(name);
Log.d(TAG, "[getMyLocationProvider] " + provider.getName() + " " +
provider);
}
}
return provider;
}
LocationManager.getProviders() and LocationManager.getBestProvider() differ quite
significantly. While getProviders() will only return perfect matches, getBestProvider()
will first search for a perfect match but may return a location provider that does not meet
the criteria if no provider was a perfect match. The criteria are loosened in the following
sequence:
Power requirement
Accuracy
Bearing 方位角
Speed
Altitude
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 applications
that register a location listener usually first retrieve the last known location in order to be
more responsive as it typically takes a few seconds before any location update is received. Listing 7�C14 shows how a last known location can be retrieved.
Listing 7�C14. Last Known Location
private 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 recent
location 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�C15 shows how you can register a listener with the device’s accelerometer.
Listing 7�C15. Registering Sensor Listener With Accelerometer
private 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() {
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
Log.i(TAG, "Accuracy changed to " + accuracy);
}
@Override
public 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 one
Sensor 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, Android
lets 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_NORMAL
SENSOR_DELAY_UI
SENSOR_DELAY_GAME
SENSOR_DELAY_FASTEST
On a Galaxy Tab 10.1 (using an MPL accelerometer from Invensense), the
accelerometer’s NORMAL, UI, GAME, and FASTEST delays are about 180, 60, 20, and
10 milliseconds respectively. While these numbers vary depending on devices, faster
updates will require more power. For example, on the Android G1 phone, using the
NORMAL delay draws 10 mA from the battery while using the FASTEST delay draws 90
mA (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 some
applications and would result in a longer battery life.
Like other listeners, the sensor listeners should be disabled whenever notifications are
not 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 its
rendering, the idea is to do as little work as possible while still getting the desired results
on 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 to
an individual pipeline, even between the renderings of two frames.
While you have no direct control over things like voltage, frequency, or what hardware
module 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 rate
on 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 a
better 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�C16 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�C17 shows the implementation of the service.
Listing 7�C16. Setting Up An Alarm
private 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 matches
this 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�C17. Service Implementation
public class MyService extends Service {
private static final String TAG = "MyService";
@Override
public IBinder onBind(Intent intent) {
// we return null as client cannot bind to the service
return null;
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
Log.i(TAG, "Alarm went off �C 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_TIME
ELAPSED_TIME_WAKEUP
RTC
RTC_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 and
ELAPSED_TIME_WAKEUP).
The key here is in the _WAKEUP suffix. An RTC or ELAPSED_TIME alarm that goes off when
the device is asleep won’t be delivered until the next time the device wakes up, whereas
an 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 a
3G connection, your application is the one that triggered everything.
Few applications really need to forcibly wake up the device when an alarm goes off到时间. Of
course an application such as an alarm clock would need such capability, but it is often
preferred 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 device
more often than necessary. Android defines five intervals for these alarms:
INTERVAL_FIFTEEN_MINUTES
INTERVAL_HALF_HOUR
INTERVAL_HOUR
INTERVAL_HALF_DAY
INTERVAL_DAY
While these values are defined as the number of milliseconds they represent (for
example, INTERVAL_HOUR equals 3,600,000), they are the only intervals
setInexactRepeating() understands to create “inexact alarms.” Passing any other value
to setInexactRepeating will be equivalent to calling setRepeating(). Listing 7�C18 shows
how to use inexact alarms.
Listing 7�C18. Setting Up An Inexact Alarm
private 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 matches
this 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 alarm
that goes off only once, call setInexactRepeating() and cancel the alarm after it goes off
the first time.
Clearly, the best results are achieved when all applications use such alarms instead of
alarms with exact trigger times. To maximize power savings, your application can also
let the user configure how often alarms should go off, as some may find that longer
intervals do not negatively impact their user experience.
7.8WakeLocks保持进程在休眠时从屏幕消失
Some applications, in some cases, need to prevent the device from going to sleep to
maintain a good user experience even if the user is not interacting with the device for an
extended period of time. The simplest example, and probably the most relevant, is when
a user is watching a video or movie on the device. In such a case, the CPU needs to
decode 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�C19.
Listing 7�C19. Creating WakeLock
private 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 object
is 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 on
for 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 acquire
it again when the application is resumed (if the video is still playing at that time). As you
can 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 can
use 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�C20 shows how to use the attribute with a linear layout element.
Listing 7�C20. keepScreenOn XML Attribute
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="
http://schemas.android.com/apk/res/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 acquired
and when they should be released. Also make sure you fully understand your
application’s lifecycle, and make sure the test cases are exhaustive确保详细用例. WakeLocks are only problematic when bugs exist!
Summary
Users 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给你的用户配置的权利.