精通安卓性能优化-第七章(四)

来源:互联网 发布:java http multi 编辑:程序博客网 时间:2024/05/22 15:34

传感器

传感器是有趣的。每个人都喜欢并且希望去使用它们。使用传感器的方式和使用Location Provider的方式相似:应用针对指定的传感器注册一个sensor listener,会被通知更新。Listing 7-15给出了你如何注册一个设备加速器的listener。
Listing 7-15 注册一个加速器的传感器

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]));                                // 做一些感兴趣的事情            }        };                // 我们简单的获取第一个        Sensor sensor = sensors.get(0);        Log.d(TAG, "Using sensor " + sensor.getName() + " from " + sensor.getVendor());                sm.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL);    }}

我们可以以指定以什么样的频率获取位置更新的方式,Android允许应用指定以什么样的频率获取传感器更新。对location provider,我们使用毫秒为单位,我们使用下面四个可能的值之一去指定我们要以什么样的频率获取传感器更新:
(1) SENSOR_DELAY_NORMAL
(2) SENSOR_DELAY_UI
(3) SENSOR_DELAY_GAME
(4) SENSOR_DELAY_FASTEST
Galaxy Tab 10.1(使用Invensense公司的MPL加速器),加速器的NORMAL, UI, GAME和FASTEST延迟大约180,60,20和10毫秒。尽管这个值在不同的设备会有变化,越快的更新需要越多的电能。比如,在Android G1手机,使用NORMAL延迟提取大约10mA电能,而使用FASTEST延迟大约提取90mA(UI 15mA和Game 80mA)。
像location provider,减少通知的频率是节省电能最好的方式。因为每个设备不同,你的应用可以测量每个延迟通知的频率,选择给出最好用户体验同时节省电能的那个。另外一个策略,可能不会用于所有的应用,当值不会改变太快的时候,使用NORMAL或者UI Delay,检测到突然的改变的时候切换到GAME或者FASTEST Delay。这样的结果可以给出可接受的结果,而且使得电池的使用时间更长。
像其他的Listener一样,不需要通知的时候sensor listener需要被关闭。为了这个目的,使用SensorManager的unregisterListener()方法。

Graphics

应用花费很多时间在屏幕上画东西。不管是使用GPU的3D游戏或者使用CPU的简单日历应用等大多数的渲染,为了保存电池使用时间,思想是得到期望结果的前提下做尽量少的工作。
像我们之前看到的,CPU不在全速运行的时候使用更少的电能。现在的CPU使用动态频率调整和动态电压调整去保存电能或者减少热量。这样的技术通常一块使用,被称为DVFS(Dynamic Voltage and Frequency Scaling),Linux内核、Android和现在的处理器支持这样的技术。
相似的,现在的CPU可以关闭内部组件,从一个完整的core到单独的管道,甚至在两个帧渲染之间。
你不能直接控制电压,频率,或者关闭电能的硬件模块,你只能直接控制你的应用的渲染器。尽管达到好的帧率是大多数应用的第一优先级,不要忘了减少电能消费。尽管在Android设备上帧率被限制(比如,每秒60帧),优化你的渲染程序依然有好处,即使你的帧率已经达到了最大的帧率。除了尽可能减少电能消耗,你需要留出更多的空间给运行的其他后台应用,提供一个全局的用户体验。
比如,一个通常的陷阱是忽略在动态壁纸调用onVisibilityChanged()。墙纸可以看不到的事实很容易简单的被忽略,而一直获取wallpaper使用很多电能。
怎样优化渲染,参考第8章。

Alarms

你的应用可能为了某些原因需要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    }}

Listing 7-17 Service实现

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() 当释放完资源    }}

就像看到的传感器事件listener,当涉及到电能的消耗,一个单独的值可能导致很大的不同。下面是AlarmManager.RTC_WAKEUP的值,Android定义了4个:
(1) ELAPSED_TIME
(2) ELAPSED_TIME_WAKEUP
(3) RTC
(4) RTC_WAKEUP
RTC和ELAPSED_TIME类型仅仅是时间表示的方式不同,从Unix时代所有的RTC类型(RTC和RTC_WAKEUP)用毫秒,和从开机时间所有的ELAPSED_TIME(ELAPSED_TIME和ELAPSED_TIME_WAKEUP)用毫秒。
这里的关键是_WAKEUP后缀。RTC或者ELAPSED_TIME的Alarm当设备休眠的时候关闭,直到下次设备被唤醒,而RTC_WAKEUP或者ELAPSED_TIME_WAKEUP当设备休眠的时候将会唤醒设备。显然的,持续的唤醒设备对电量消费有显著的影响,即使应用被唤醒后没有做任何事情:
(1) 设备将为了启动你的应用被唤醒
(2) 等待设备被唤醒的其他的(well-behaved)闹钟将被交付
就像你可以看到的,这将导致链式反应。即使其他的alarms消耗更多电量,比如他们开启3G的数据传输,你的应用是触发所有事情的那个。
很少有应用需要当alarm到期的时候强制唤醒设备。当然一个应用比如闹钟需要这样的能力,但是它经常选择在做任何事情之前简单的等待设备唤醒(大多数跟着一个用户交互)。像location provider一样,使用passive的方式可以得到更多的电池使用时间。

Scheduling Alarms

多说比不说要好,应用需要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);    }}

TIP:没有setInexact()方法。如果你的应用需要schedule一个只触发一次的inexact alarm,调用setInexactRepeating()并且在到期后取消。

显然,当所有的应用使用这样的alarm而不是带有精确的触发次数的alarm,最好的结果达到了。为了最大化节省电能,你的应用同样可以让用户配置alarm多长时间到期,可能会发现更长的间隔并不会对用户体验有负面影响。

WakeLocks

某些应用在一些情况下,为了保持好的用户体验需要防止设备休眠,即使用户长时间没有和设备有交互。最简单也可能是最相关的例子,用户使用设备去观看电影。在这样的情况下,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();}

NOTE:为了使用WakeLock对象你的应用需要WAKE_LOCK权限。

系统如何表现依赖于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属性

<?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"    >        ...    </LineaLayout>

TIP:android:keepScreenOn可以用于任何的view。指定这个属性,当view可见的时候屏幕一直开启。你同样可以使用View.setKeepScreenOn()方法去控制屏幕是否开启。

尽管可能导致问题,WakeLock有些时候是必须的。如果你需要使用他们,保证你仔细的考虑过他们什么时候申请、什么时候释放。同样保证你完全理解你的应用的生命周期,保证测试实例存在。当Bug存在的时候WakeLock只是问题。

Summary

用户通常不会注意到你的应用节省了电能使用。然而,他们很可能会注意到你没有。所有的应用需要去表现和合作去最大化电池使用时间,因为一个应用可能会毁灭所有的其他的应用的努力。用户经常卸载耗电太多的应用,如果应用对电量敏感的话需要允许用户配置,因为不同的用户需求不同。给你的用户权利。

0 0
原创粉丝点击