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

来源:互联网 发布:中国网络墙谁设计的 编辑:程序博客网 时间:2024/05/25 23:25

第七章 最大化电池使用时间

电能很少,但是责任却很大。Android移动设备的运行依赖于电池,应用的所有操作都要汲取一定数量的电池能量。因为大多数设备晚上在家充电,在白天使用并且没有机会去重新充电,大多数的设备拥有者期望电池至少支持12小时。通常使用会使得电池消耗更快:比如,充电站在Google I/O是可用的时间段,比平时长。
尽管应用有时候看起来没有做多少事情,但实际上从电池提取这么多电量很简单,设备在中午就耗完了电量,使得用户几个小时不能使用手机或者平板。一个很快清空电量的应用,很可能会成为被删除的候选,当然使用的人越少,收入越少。作为结果,开发者需要尝试去使用尽量少的电量,使设备的电池更加有意义。
在本章,学习如何去测量电池使用和怎么样保证不对用户体验造成负面影响的情况下节省电能,使用一些Android应用吸引人的地方:网络,定位信息,感应器。同样将学习如何使用Android内部组件更加有效的工作,比如broadcast receivers,alarms,和wake locks。

电池

不同的设备有不同的容量。手机和平板的电池容量通常使用mAh测量,即毫安每小时。Table 7-1给出了第二章提到的设备的容量。

NOTE:安培,以名字命名,是一个电量单位,通常简写为amp。每安培时等同于3600库伦,因此每安培秒等同一库伦,每mAh等同于3.6库伦。库伦,很少用在消费品上。

Table 7-1 一些Android设备电池的容量


平板使用的电池容量比较大是一个很强的暗示:屏幕消耗很多电能。Android提供了一个方式让用户大约知道多少电能被应用使用,多少电能被系统组件使用。Figure 7-1给出了Galaxy Tab 10.1使用了多少电能,在大多数时间使用愤怒的小鸟的情况下。

图 7- 1 电池使用


在这个截图中清楚的列出了两项:屏幕和Wifi。因为这两个部件使用了很多电能,设备提供了用户配置他们的方式。比如,用户可以改变屏幕的亮度(手动的或者基于显示的图像自动的),定义没有活动多少时间屏幕需要关闭,同样的当屏幕关闭的时候也关闭Wifi。例如,Wifi连接可能仅仅占电池使用的几个百分比,如果屏幕关闭的时候它也关闭的话。

NOTE:这里使用的Galaxy Tab 10.1是Wi-Fi-only版本。其他设备的显示项可能不同,比如"Cell standby"或者"Voice calls"。

尽管用户自己可以主动的管理电池使用,这并非没有局限性。最终,设备使用多少电能很大一部分依赖于所有的应用做了什么,因此依赖于你如何设计和实现你的应用。
通常你的应用做的事情包括:
(1) 执行代码(明知故问不说的好)
(2) 传输数据(下载和上传,使用Wi-Fi,EDGE,3G, 4G)
(3) 跟踪位置(使用网络或者GPS)
(4) 使用传感器(加速,陀螺仪)
(5) 渲染图像(使用GPU或者CPU)
(6) 执行不同的任务
在我们学习如何最小化电池使用之前,要有一个方式去测量应用使用了多少电量。

测量电池使用

不幸的是,这样精确的测量需要电子设备,大多数开发者不会涉及到。然而,Android提供了获取电池使用信息的API。尽管没有像getBatteryInfo()这样的接口,可以通过被称为sticky intent的方式(即一个总是存在的broadcast intent)去提取电池信息,如Listing 7-1所示。

Listing 7-1 在Activity显示电池信息

import static android.os.BatteryManager.*;// 注意这里的static关键字(不知道他的作用的话,删掉试一下)public class BatteryInfoActivity extends Activity {    private static final String TAG = "BatteryInfo";        private BroadcastReceiver mBatteryChangedReceiver;    private TextView mTextView;    // 布局包含TextView显示电池信息        private static String healthCodeToString(int health) {        switch(health) {            // case BATTERY_HEALTH_COLD: return "Cold"; // 仅API 11            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_CHARING: 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 "Unkown";        }    }        private void showBatteryInfo(Intent intent) {        if (intent != null) {            int health = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);            String healString = "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_UNKOWN);            String statusString = "Status: " + statusCodeToString(status);            Log.i(TAG, statusString);                        String tecknology = intent.getStringExtra(EXTRA_TECHNOLOGY);            String tecknologyString = "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: 使用一个StringBuilder对象可能更有效                        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() {        // 不需要receiver        Intent intent = registerReceiver(null, new IntentFileter(Intent.ACTION_BATTERY_CHANGED));        showBatteryInfo(intent);    }        private void createBatteryReceiver() {        mBatterChangedReceiver = new BroadcastReceiver() {            @Override            public void onReceive(Context context, Intent intent) {                showBatteryInfo(intent);            }        };    }        /** 在Activity初次创建的时候调用 **/    @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(); //不需要receiver    }        @Override    protected void onPause() {        super.onPause();                // 取消receiver当application不在前台的时候,保存电能        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;    }}

就像你可以看到的,电池信息是intent的extra信息的一部分。这个activity希望被通知改变,因此它在onResume()注册了一个broadcast receiver。然而,因为通知的唯一目的是用新的电池信息更新用户界面,仅在activity在最前面的时候需要通知,或者用户直接和应用交互的时候,结果,在onPause()的时候注销这个broadcast receiver。

NOTE:另外一个可能的实现是把注册和注销移动到onStart()和onStop()实现。为了达到更好的电能节省,通常在onResume()和onPause()做注册和注销receiver。

如果你需要知道当前的电池信息,但是不需要有改变的时候被通知,可以简单的获取sticky intent包含的电池信息,而不是调用registerReceiver()并且使用null参数注册一个broadcast receiver。
为了测量电池使用,推荐在应用开始的时候获取电量级别,使用应用一段时间,在应用离开的时候获取电量级别。尽管两个级别不会告诉你精确的你的应用使用了多少电量(因为其他应用同时也会运行),可以给你应用使用的电量大概概念。比如,你可以决定电量被用光之前应用可以使用多长时间。


0 0