关闭Broadcast Receiver
为了保存电量,应用应该避免执行无目的的代码。在上面的实例中,当用户界面不在最前的时候去更新Textview的text是没有价值的,仅仅无必要的从电池提取能量。
除了ACTION_BATTERY_CHANGED的sticky intent包含电池信息,Android定义了其他的四个intent,你的应用可以使用:
(1) ACTION_BATTERY_LOW
(2) ACTION_BATTERY_OKAY
(3) ACTION_POWER_CONNECTED
(4) ACTION_POWER_DISCONNECTED
尽管简单的在应用manifest中声明一个receiver,不能收到ACTION_BATTERY_CHANGED的broadcast intent(这个receiver需要显式的在代码中调用registerReceiver()),其他的intent允许在应用的manifest里面注册,如Listing 7-2。
Listing 7-2 在Manifest中声明receiver
<?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>
Listing 7-3给出了broadcast receiver的一个简单实现。我们定义了一个BatteryReceiver处理所有的四个action。
Listing 7-3 BatteryReceiver实现
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;
// 在这里处理4个action
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();
}
}
就像现在,应用可以被认为有一个严重的缺陷。事实上,其中任何一个action发生应用将启动(如果还没有start ready)当。尽管这可能是设计行为,在许多情况下可能需要的行为不同。比如在这个情况下,我们可以说只有当应用在最前的时候显示toast有意义,总是显示的话可能会干扰其他应用,使得用户体验变差。
当应用没有在运行,或者在后台运行,我们希望关闭这些Toast。两种方式去实现:
(1) 可以在应用中添加一个标志位,在activity的onResume里面设置为True,在onPause()设置为false,修改receicer的onReceive()方法去检测这个标志
(2) 我们可以仅当应用是foreground应用的时候开启broadcast receiver
尽管第一种方式工作的很好,不会阻止应用从四种Action触发的时候启动。这最终导致没有必要的指令被执行,依然会从电池提取电量。另外,你需要在多个文件里面修改这个标志,因为你的应用会定义多个activity。
第二种方法更好一些,我们可以保证指令仅当我们有一个明确的目的时候去执行,因此电量仅为了合理的理由消耗。为了达到这个目的,我们需要在应用中做两件事:
(1) 默认关闭receiver
(2) 在onResume()开启receiver并且在onPause()关闭
关闭和开启Broadcast Receiver
Listing 7-4显示了如何在应用的manifest文件中关闭broadcast receiver。
Listing 7-4 在Manifest中关闭Broadcast Receiver
...
<receiver android:name=".BatteryReceiver" android:enable="false" >
...
NOTE:<application>标签有自己的enabled属性。broadcast receiver仅在application和receiver的enabled属性都为true的时候才会开启,其他的情况下关闭。
Listing 7-5给出了如何在onResume()开启和在onPause()关闭broadcast receiver。
Listing 7-5 开启和关闭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
// 当应用没在前台为了保存电能注销receiver
}
@Override
protected void onResume() {
super.onResume();
if (mBatteryChangedReceiver == null) {
createBatteryReceiver();
}
registerReceiver(mBatteryChangedReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
enableBatteryReceiver(true); // 开启battery receiver
}
...
}
仅在确实需要的时候开启broadcast receiver在电能消耗上有很大的不同。尽管这是开发的时候很容易忽视的一个方面,需要特别注意receiver,他们仅在需要的时候被开启。
网络
许多应用在设备和服务器之间传递数据,或者设备之间传递数据。就像电池状态,应用需要从设备的网络连接获取信息。ConnectivityManager类提供可供应用访问网络信息的API。Android通常有多种数据连接可用:
(1) Bluetooth
(2) Ethernet
(3) Wi-Fi
(4) WiMAX
(5) Mobile(EDGE, UMTS, LTE)
Listing 7-6给出了如何获取活动的链接以及所有的链接的信息。
Listing 7-6 网络信息
private void showNetworkInfoToast() {
ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
// 仅仅显示active链接
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null) {
Toast.makeText(this, "Active: " + info.toString(), Toast.LENGTH_LONG().show();
}
// 显示所有链接
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: 应用需要ACCESS_NETWORK_STATE权限去获取网络信息。
因为聚焦于最大化电池使用时间,我们需要注意到如下事情:
(1) 后台数据设置
(2) 数据传输率
后台数据
用户可以设置指定是否允许后台数据传输,预期可以节省电池电量。如果你的应用不在前台的时候需要数据传输,需要检测标志位,如Listing 7-7所示。 Service通常需要在初始化任何传输前检查这个设置。
Listing 7-7 检测后台数据设置
private void transferData(byte[] array) {
ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
boolean backgroundDataSetting = cm.getBackgroundDataSetting();
if(backgroundDataSetting) {
// transfer data
} else {
// 兑现设置不传输数据
}
}
因为这是一个自愿的检测,你的应用实际上可能忽略这个设置,在任何情况下传输数据。然而,因为这可能和用户的意愿抵触,潜在的降低前台数据传输的速度,并且影响电池使用时间,这样的行为可能导致用户卸载掉你的应用。
为了当后台的数据设置改变的时候接到通知,可以在Java代码中注册一个receiver,使用ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED去构建intent filer或者在应用的manifest文件使用andoird.net.conn.BACKGOUND_DATA_SETTING_CHANGED。因为这个设置控制后台数据传输,实际上比在onResume()开启receiver和在onPause()关闭receiver更有意义。
NOTE:getBackgoundDataSetting()方法在4.0弃用了,会always返回true。另外,当后台数据传输不可用network可能会被断开。
数据传输
传输率变化范围很广,GPRS数据传输通常少于100K每秒,而LTE或者Wifi连接通常达到几M。除了连接类型,NetworkInfo类还指定了连接的子类型。当连接是TYPE_MOBILE的时候这很重要。Android定义了如下的连接子类型(在TelephonyManager类):
子类型随着新技术创建和开发而添加。比如,LTE子类型在API 11添加,HSPAP子类型在API 13添加。如果你的代码依赖于这些值,保证你处理了这种情况,即应用存在他不知道的数值;否则可能导致应用不能传输数据。当有新的子类型添加,需要更新你的代码,所以请关注每一个版本的Android SDK。改变列表在http://d.android.com/sdk/api_diff/13/changes.html给出,比如。
直观上你的应用应该选择更快的连接。即使3G射频芯片消耗比Wifi射频芯片消耗更少的电量,Wifi传输率可能最终意味着WIFI传输能够减少电量消耗,因为传输可以在很短的时间内完成。
NOTE:因为数据计划现在通常允许有限数量的数据传输(比如,¥30/2GB每月),Wifi连接经常被作为第一选择。同样的,你的应用可以使用NetworkInfo.isRoaming()去了解设备当前是否在漫游,因为这可以添加额外的消费,当isRoaming()返回true的时候,需要避免传输数据。
Table 7-2给出了T-Mobile G1手机不同的组件内存消耗(也叫HTC Dream, or Era G1)。尽管这个手机现在有些老(在2008晚期发布),数值同样给出了每个组件提取多少电量一个很好的概述。
Table 7-2 Android G1手机电能消耗(源自: Google I/O 2009)
尽管精确的数值会随不同设备变化,了解你的应用使用多少电能很重要。因为G1有一个1150mAh的电池,下载和播放视频的应用(比如,YouTube)在使用3G连接的情况下,大约在3个小时清空电池:3G需要150mA,CPU需要90mA,LCD需要90mA,总共330mA,或者3个半小时的使用(假设没有其他应用运行)。
如果你可以控制什么样的数据传输,可以考虑在传输前压缩数据。尽管CPU需要在数据使用前需要解压缩(因此需要更多的电量),数据传输将更快,射频芯片(比如,3G,Wifi)可以更快的关闭,以保存电池的寿命。考虑的事情包括:
(1) 使用GZIP压缩文本数据,使用GZIPInputStream去访问数据
(2) 如果可能使用JPEG而不是PNG
(3) 使用匹配设备分辨率的资源(比如,如果需要resize到96*54,没有必要去下载1920*1080的图片)
传输速度越慢(比如,EDGE),压缩的重要性越大,因为你希望去减少射频芯片开启的时间。
因为Android运行在越来越多的设备上,从手机到平板,从机顶盒到网络笔记本,为所有这些设备产生资源很乏味。然而,使用合适的资源可以大幅增加电池的寿命,因此使你的应用更加合意。除了保存电能,更快的下载和上传将使你的应用响应更好。