传感器是有趣的。每个人都喜欢并且希望去使用它们。使用传感器的方式和使用Location Provider的方式相似:应用针对指定的传感器注册一个sensor listener,会被通知更新。Listing 7-15给出了你如何注册一个设备加速器的listener。
Listing 7-15 注册一个加速器的传感器
private void registerWithAccelerometer() {
SensorManager sm = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
List 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]));
// 做一些感兴趣的事情
}
};
// 我们简单的获取第一个
Sensor sensor = sensors.get(0);
Log.d(TAG, "Using sensor " + sensor.getName() + " from " + sensor.getVendor());
sm.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}
应用花费很多时间在屏幕上画东西。不管是使用GPU的3D游戏或者使用CPU的简单日历应用等大多数的渲染,为了保存电池使用时间,思想是得到期望结果的前提下做尽量少的工作。
像我们之前看到的,CPU不在全速运行的时候使用更少的电能。现在的CPU使用动态频率调整和动态电压调整去保存电能或者减少热量。这样的技术通常一块使用,被称为DVFS(Dynamic Voltage and Frequency Scaling),Linux内核、Android和现在的处理器支持这样的技术。
相似的,现在的CPU可以关闭内部组件,从一个完整的core到单独的管道,甚至在两个帧渲染之间。
你不能直接控制电压,频率,或者关闭电能的硬件模块,你只能直接控制你的应用的渲染器。尽管达到好的帧率是大多数应用的第一优先级,不要忘了减少电能消费。尽管在Android设备上帧率被限制(比如,每秒60帧),优化你的渲染程序依然有好处,即使你的帧率已经达到了最大的帧率。除了尽可能减少电能消耗,你需要留出更多的空间给运行的其他后台应用,提供一个全局的用户体验。
比如,一个通常的陷阱是忽略在动态壁纸调用onVisibilityChanged()。墙纸可以看不到的事实很容易简单的被忽略,而一直获取wallpaper使用很多电能。
怎样优化渲染,参考第8章。
你的应用可能为了某些原因需要wake up并且执行一些操作。一个经典的例子是一个RSS阅读器应用每30分钟wake up一次,下载RSS feeds,当应用启动的时候用户经常可以看到更新的feed页面,或者一个死缠烂打的应用每5分钟发送信息给你联系人列表中的一个。Listing 7-16给出如何创建一个alarm唤醒应用,启动service简单的输出信息并终止。Listing 7-17给出了如何实现这个service。
Listing 7-16 设置一个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); // 将会取消所有的匹配这个intent的alarm
} else {
long interval = DateUtils.HOUR_IN_MLLIS * 1;
long firstInterval = DateUtils.MINUTE_IN_MILLIS * 30;
am.setRepeating(AlarmManager.RTC_WAKEUP, firstInterval, interval, pendingIntent);
// 使用am.set(...) schedule一个不重复的alarm
}
}
public class MyService extends Service {
private static final String TAG = "MyService";
@Override
public IBinder onBind(Intent intent) {
// 如果client不能bind到service的话,返回null
return null;
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
Log.i(TAG, "Alarm went off - Service was started");
stopSelf(); // 记得调用stopSelf() 当释放完资源
}
}
多说比不说要好,应用需要schedule alarms在将来某个时间到期,对精确的什么时候到期没有严格的需求。对这个目的,Android定义了AlarmManager.setInexactRepeating(),和setRepeating()的参数相同。主要的不同是系统schedule alarm实际到期的时间方式不同:Android可以调整实际的触发时间去同时响应多个alarm(很可能来自不同的应用)。这样的alarm使得电能使用更加有效,因为系统避免无必要的唤醒设备。Android定义了5种间隔:
(1) INTERVAL_FIFTEEN_MINUTES
(2) INTERVAL_HALF_HOUR
(3) INTERVAL_HOUR
(4) INTERVAL_HALF_DAY
(5) INTERVAL_DAY
这些值定义了他们代表的毫秒数(比如,INTERVAL_HOUR等于3,600,000),他们是setInexactRepeating()理解的创建"inexact alarms"的间隔。传递任何其他的值给setInexactRepeating()将会等同于调用setRepeating()。Listing 7-18给出了如何使用inexact alarm。
Listing 7-18 设置一个不精确的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); // 将会取消掉所有匹配这个intent的alarms
} else {
long interval = AlarmManager.INTERVAL_HOUR;
long firstInterval = DateUtils.MINUTE_IN_MILLIS * 30;
am.setInexactRepeating(AlarmManager.RTC, firstInterval, interval, pendingIntent);
}
}
显然,当所有的应用使用这样的alarm而不是带有精确的触发次数的alarm,最好的结果达到了。为了最大化节省电能,你的应用同样可以让用户配置alarm多长时间到期,可能会发现更长的间隔并不会对用户体验有负面影响。
某些应用在一些情况下,为了保持好的用户体验需要防止设备休眠,即使用户长时间没有和设备有交互。最简单也可能是最相关的例子,用户使用设备去观看电影。在这样的情况下,CPU需要去解密视频,屏幕需要一直开启,用户才能去观看。同样的,当视频播放的时候屏幕也不能变暗。
WakeLock类允许这样的场景,如Listing 7-19所示。
Listing 7-19 创建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();
reunnable.run();
wl.release();
}
系统如何表现依赖于WakeLock对象使用哪个标志位创建。Android定义了如下的标志:
(1) PARTIAL_WAKE_LOCK(CPU ON)
(2) SCREEN_DIM_WAKE_LOCK(CPU ON, DISPLAY DIMMED)
(3) SCREEN_BRIGHT_WAKE_LOCK(CPU ON, BRIGHT DISPLAY)
(4) FULL_WAKE_LOCK(CPU ON, BRIGHT DISPLAY AND KEYBOARD)
上面的标志位可以和另外两个联合:
(1) ACQUIRE_CAUSES_WAKEUP(开启或者关闭屏幕或者键盘)
(2) ON_AFTER_RELEASE(当WakeLock释放后保持屏幕或者键盘开启一段时间)
尽管他们的使用是细节问题,没有释放WakeLock会导致显著的问题。一个应用可能简单的忘记去释放WakeLock,导致显示持续很长的时间,非常快的清空电能。通常,WakeLock需要被尽快的释放。比如,一个应用当播放视频的时候获取了一个WakeLock,当视频暂停的时候释放它,当再次启动播放的时候再次获取它。当应用被暂停的时候需要去释放WakeLock,当恢复的时候再次获取(如果仍然在播放)。就像你可以看到的,不同情况问题的数量增长很快,导致你的应用出问题。
为了防止可能的问题,推荐你使用WakeLock.acquire()的timeout版本,会保证超过给定的timeout后释放WakeLock。比如,一个播放视频的应用使用视频的时长作为timeout的时间。
或者,如果保持屏幕开启的行为和activity的一个view相关,你可以在Layout文件中使用XML属性android:keepScreenOn。使用这种方式的好处是你不用担心忘记释放WakeLock,系统会处理,而且在应用的manifest文件里面不需要声明额外的权限。Listing 7-20给出了如何去使用这个属性。
Listing 7-20 keepScreenOn XML属性
...
尽管可能导致问题,WakeLock有些时候是必须的。如果你需要使用他们,保证你仔细的考虑过他们什么时候申请、什么时候释放。同样保证你完全理解你的应用的生命周期,保证测试实例存在。当Bug存在的时候WakeLock只是问题。
用户通常不会注意到你的应用节省了电能使用。然而,他们很可能会注意到你没有。所有的应用需要去表现和合作去最大化电池使用时间,因为一个应用可能会毁灭所有的其他的应用的努力。用户经常卸载耗电太多的应用,如果应用对电量敏感的话需要允许用户配置,因为不同的用户需求不同。给你的用户权利。