Android开发笔记(一百一十七)app省电方略

电源管理PowerManager

PowerManager是Android的电源管理类,用于管理电源操作如睡眠、唤醒、重启以及调节屏幕亮度等等。
PowerManager的对象从系统服务POWER_SERVICE中获取,它的主要方法如下:
goToSleep : 睡眠,即锁屏。
wakeUp : 唤醒,即解锁。
reboot : 重启。
另有下列几个隐藏的方法:
getMinimumScreenBrightnessSetting : 获取屏幕亮度的最小值。
getMaximumScreenBrightnessSetting : 获取屏幕亮度的最大值。
getDefaultScreenBrightnessSetting : 获取屏幕亮度的默认值。
setBacklightBrightness : 设置屏幕亮度。


但对多数开发者来说,PowerManager在实际开发中毫无用处,因为一旦调用该类的方法,你的app运行时就会崩溃,查看日志报错“java.lang.SecurityException: Neither user 10150 nor current process has android.permission.DEVICE_POWER.”这个错误信息倒是容易看懂,好吧,那我便在AndroidManifest.xml中加上DEVICE_POWER的权限。可是加了权限之后,ADT又提示错误“Permission is only granted to system apps”。这下傻眼了,怎么会说“权限只授予系统应用程序”呢?不过这难不倒我,咱把app工程clean一下,错误提示就不见了,然后重新Run之,结果Console栏出现红色文字“Installation error: INSTALL_FAILED_SHARED_USER_INCOMPATIBLE”,还是不行呀。
找了大量的资料,才发现这是因为电源管理的权限,只有系统程序(打了系统签名)才可以获得,用户程序无法获取这个权限。大伙对该问题基本是束手无策,只有Stack Overflow上的大神给了个解决方案,主要做三方面的修改:
1、在AndroidManifest.xml中加上DEVICE_POWER、REBOOT、SHUTDOWN的权限。
2、在AndroidManifest.xml的manifest节点中增加属性说明“android:sharedUserId="android.uid.system"”,这表示使用系统用户的uid。
3、为了能够共享系统用户的uid,你的app得采用系统签名打包,即先找到目标Android系统的platform.pk8和platform.x509.pem密钥文件,然后使用signapk.jar将apk签名到指定密钥。
这个解决方案理论上可行,但就真机来说,每个品牌每个型号的手机,其系统签名都是不一样的。因此,就算你真的搞出来一个系统应用,那也仅适用于该签名版本的Android系统,而不能用于其他签名的Android系统,所以PowerManager只能是手机厂商内部使用了。


下面是PowerManager几个用途的示例代码(一般用不到,仅供参考):
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. import java.lang.reflect.Field;  
  2. import java.lang.reflect.Method;  
  3.   
  4. import android.annotation.TargetApi;  
  5. import android.content.Context;  
  6. import android.content.Intent;  
  7. import android.os.Build;  
  8. import android.os.IBinder;  
  9. import android.os.PowerManager;  
  10. import android.os.SystemClock;  
  11. import android.util.Log;  
  12.   
  13. //注意,PowerManager只有系统应用才能操作,普通应用不能操作,所以下面代码仅供参考  
  14. public class PowerUtil {  
  15.       
  16.     private final static String TAG = "PowerUtil";  
  17.       
  18.     private static int getValue(Context ctx, String methodName, int defValue) {  
  19.         int value = defValue;  
  20.         PowerManager pm = (PowerManager) ctx.getSystemService(Context.POWER_SERVICE);  
  21.         try {  
  22.             Class<?> pmClass = Class.forName(pm.getClass().getName());  
  23.             Field field = pmClass.getDeclaredField("mService");  
  24.             field.setAccessible(true);  
  25.             Object iPM = field.get(pm);  
  26.             Class<?> iPMClass = Class.forName(iPM.getClass().getName());  
  27.             Method method = iPMClass.getDeclaredMethod(methodName);  
  28.             method.setAccessible(true);  
  29.             value = (Integer) method.invoke(iPM);  
  30.         } catch (Exception e) {  
  31.             e.printStackTrace();  
  32.         }  
  33.         Log.d(TAG, "methodName="+methodName+", value="+value);  
  34.         return value;  
  35.     }  
  36.   
  37.     public static int getMinLight(Context ctx) {  
  38.         return getValue(ctx, "getMinimumScreenBrightnessSetting"0);  
  39.     }  
  40.   
  41.     public static int getMaxLight(Context ctx) {  
  42.         return getValue(ctx, "getMaximumScreenBrightnessSetting"255);  
  43.     }  
  44.   
  45.     public static int getDefLight(Context ctx) {  
  46.         return getValue(ctx, "getDefaultScreenBrightnessSetting"100);  
  47.     }  
  48.       
  49.     //设置屏幕亮度。light取值0-255  
  50.     public static void setLight(Context ctx, int light) {  
  51.         PowerManager pm = (PowerManager) ctx.getSystemService(Context.POWER_SERVICE);  
  52.         try {  
  53.             Class<?> pmClass = Class.forName(pm.getClass().getName());  
  54.             // 得到PowerManager类中的成员mService(mService为PowerManagerService类型)  
  55.             Field field = pmClass.getDeclaredField("mService");  
  56.             field.setAccessible(true);  
  57.             // 实例化mService  
  58.             Object iPM = field.get(pm);  
  59.             // 得到PowerManagerService对应的Class对象  
  60.             Class<?> iPMClass = Class.forName(iPM.getClass().getName());  
  61.             /* 
  62.              * 得到PowerManagerService的函数setBacklightBrightness对应的Method对象, 
  63.              * PowerManager的函数setBacklightBrightness实现在PowerManagerService中 
  64.              */  
  65.             Method method = iPMClass.getDeclaredMethod("setBacklightBrightness"int.class);  
  66.             method.setAccessible(true);  
  67.             // 调用实现PowerManagerService的setBacklightBrightness  
  68.             method.invoke(iPM, light);  
  69.         } catch (Exception e) {  
  70.             e.printStackTrace();  
  71.         }  
  72.     }  
  73.       
  74.     public static void resetLight(Context ctx, int light) {  
  75.         try {  
  76.             Object power;  
  77.             Class <?> ServiceManager = Class.forName("android.os.ServiceManager");  
  78.             Class <?> Stub = Class.forName("android.os.IPowerManager$Stub");  
  79.   
  80.             Method getService = ServiceManager.getMethod("getService"new Class[] {String.class});  
  81.             //Method asInterface = GetStub.getMethod("asInterface", new Class[] {IBinder.class});//of this class?  
  82.             Method asInterface = Stub.getMethod("asInterface"new Class[] {IBinder.class});    //of this class?  
  83.             IBinder iBinder = (IBinder) getService.invoke(nullnew Object[] {Context.POWER_SERVICE});//  
  84.             power = asInterface.invoke(null,iBinder);//or call constructor Stub?//  
  85.   
  86.             Method setBacklightBrightness = power.getClass().getMethod("setBacklightBrightness"new Class[]{int.class});   
  87.             setBacklightBrightness.invoke(power, new Object[]{light});  
  88.         } catch (Exception e) {  
  89.             e.printStackTrace();  
  90.         }  
  91.     }  
  92.       
  93.     //锁屏  
  94.     public static void lockScreen(Context ctx) {  
  95.         PowerManager pm = (PowerManager) ctx.getSystemService(Context.POWER_SERVICE);  
  96.         pm.goToSleep(SystemClock.uptimeMillis());  
  97.     }  
  98.   
  99.     //解锁  
  100.     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)  
  101.     public static void unLockScreen(Context ctx) {  
  102.         PowerManager pm = (PowerManager) ctx.getSystemService(Context.POWER_SERVICE);  
  103.         pm.wakeUp(SystemClock.uptimeMillis());  
  104.     }  
  105.   
  106.     //重启  
  107.     public static void reboot(Context ctx) {  
  108.         PowerManager pm = (PowerManager) ctx.getSystemService(Context.POWER_SERVICE);  
  109.         pm.reboot(null);  
  110.     }  
  111.   
  112.     //关机  
  113.     public static void shutDown(Context ctx) {  
  114.         Intent intent = new Intent("android.intent.action.ACTION_REQUEST_SHUTDOWN");  
  115.         intent.putExtra("android.intent.extra.KEY_CONFIRM"false);  
  116.         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  117.         // 弹出系统内置的对话框,选择确定关机或取消关机  
  118.         ctx.startActivity(intent);  
  119.     }  
  120.       
  121. }  


电池管理BatteryManager

BatteryManager名为电池管理,然而查看该类的源代码,里面只有一些常量定义,并非真正意义上的电池管理。事实上,开发者并不能直接管理电池,要想获取电池的相关信息,得通过监听电量改变事件来得知。


电池的电量改变事件,其动作名称是Intent.ACTION_BATTERY_CHANGED,因为接受该事件要求app必须处于活动状态,所以用来监听的广播接收器不能在AndroidManifest.xml中静态注册,而只能在app代码中通过registerReceiver方法来动态注册。下面是电量改变事件中携带的参数信息:
BatteryManager.EXTRA_SCALE : 电量刻度,通过getIntExtra获取。通常是100
BatteryManager.EXTRA_LEVEL : 当前电量,通过getIntExtra获取。
BatteryManager.EXTRA_STATUS : 当前状态,通过getIntExtra获取。
--BATTERY_STATUS_UNKNOWN = 1; 表示未知
--BATTERY_STATUS_CHARGING = 2; 表示正在充电
--BATTERY_STATUS_DISCHARGING = 3; 表示正在断电
--BATTERY_STATUS_NOT_CHARGING = 4; 表示不在充电
--BATTERY_STATUS_FULL = 5; 表示充满
BatteryManager.EXTRA_HEALTH : 健康程度,通过getIntExtra获取。
--BATTERY_HEALTH_UNKNOWN = 1; 表示未知
--BATTERY_HEALTH_GOOD = 2; 表示良好
--BATTERY_HEALTH_OVERHEAT = 3; 表示过热
--BATTERY_HEALTH_DEAD = 4; 表示坏了
--BATTERY_HEALTH_OVER_VOLTAGE = 5; 表示短路
--BATTERY_HEALTH_UNSPECIFIED_FAILURE = 6; 表示未知错误
--BATTERY_HEALTH_COLD = 7; 表示冷却
BatteryManager.EXTRA_VOLTAGE : 当前电压,通过getIntExtra获取。
BatteryManager.EXTRA_PLUGGED : 当前电源,通过getIntExtra获取。
--0 表示电池
--BATTERY_PLUGGED_AC = 1; 表示充电器
--BATTERY_PLUGGED_USB = 2; 表示USB
--BATTERY_PLUGGED_WIRELESS = 4; 表示无线
BatteryManager.EXTRA_TECHNOLOGY : 当前技术,通过getStringExtra获取。比如返回Li-ion表示锂电池。
BatteryManager.EXTRA_TEMPERATURE : 当前温度,通过getIntExtra获取。
BatteryManager.EXTRA_PRESENT : 是否提供电池,通过getBooleanExtra获取。


除了电量改变事件,还有几个事件与电池有关,如下所示
Intent.ACTION_BATTERY_LOW : 电池电量过低,静态注册时使用android.intent.action.BATTERY_LOW
Intent.ACTION_BATTERY_OKAY : 电池电量恢复,静态注册时使用android.intent.action.BATTERY_OKAY
Intent.ACTION_POWER_CONNECTED : 连上外部电源,静态注册时使用android.intent.action.ACTION_POWER_CONNECTED
Intent.ACTION_POWER_DISCONNECTED : 断开外部电源,静态注册时使用android.intent.action.ACTION_POWER_DISCONNECTED


下面是电池事件的监听截图:



下面是监听电池事件的代码示例:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. import com.example.exmbattery.util.DateUtils;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.BroadcastReceiver;  
  5. import android.content.Context;  
  6. import android.content.Intent;  
  7. import android.content.IntentFilter;  
  8. import android.os.BatteryManager;  
  9. import android.os.Bundle;  
  10. import android.widget.TextView;  
  11.   
  12. public class BatteryActivity extends Activity {  
  13.   
  14.     private TextView tv_battery_change;  
  15.     private static TextView tv_power_status;  
  16.       
  17.     @Override  
  18.     protected void onCreate(Bundle savedInstanceState) {  
  19.         super.onCreate(savedInstanceState);  
  20.         setContentView(R.layout.activity_battery);  
  21.   
  22.         tv_battery_change = (TextView) findViewById(R.id.tv_battery_change);  
  23.         tv_power_status = (TextView) findViewById(R.id.tv_power_status);  
  24.     }  
  25.       
  26.     @Override  
  27.     protected void onStart() {  
  28.         super.onStart();  
  29.         batteryChangeReceiver = new BatteryChangeReceiver();  
  30.         IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);  
  31.         registerReceiver(batteryChangeReceiver, filter);  
  32.     }  
  33.       
  34.     @Override  
  35.     protected void onStop() {  
  36.         super.onStop();  
  37.         unregisterReceiver(batteryChangeReceiver);  
  38.     }  
  39.   
  40.     private BatteryChangeReceiver batteryChangeReceiver;  
  41.     private class BatteryChangeReceiver extends BroadcastReceiver {  
  42.   
  43.         @Override  
  44.         public void onReceive(Context context, Intent intent) {  
  45.             if (intent != null) {  
  46.                 int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);  
  47.                 int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);  
  48.                 int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, 0);  
  49.                 int healthy = intent.getIntExtra(BatteryManager.EXTRA_HEALTH, 0);  
  50.                 int voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0);  
  51.                 int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 3);  
  52.                 String technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);  
  53.                 int temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);  
  54.                 boolean present = intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false);  
  55.   
  56.                 String desc = String.format("%s : 收到广播:%s",  
  57.                         DateUtils.getNowDateTime(), intent.getAction());  
  58.                 desc = String.format("%s\n电量刻度=%d", desc, scale);  
  59.                 desc = String.format("%s\n当前电量=%d", desc, level);  
  60.                 desc = String.format("%s\n当前状态=%s", desc, mStatus[status]);  
  61.                 desc = String.format("%s\n健康程度=%s", desc, mHealthy[healthy]);  
  62.                 desc = String.format("%s\n当前电压=%d", desc, voltage);  
  63.                 desc = String.format("%s\n当前电源=%s", desc, mPlugged[plugged]);  
  64.                 desc = String.format("%s\n当前技术=%s", desc, technology);  
  65.                 desc = String.format("%s\n当前温度=%d", desc, temperature/10);  
  66.                 desc = String.format("%s\n是否提供电池=%s", desc, present?"是":"否");  
  67.                 tv_battery_change.setText(desc);  
  68.             }  
  69.         }  
  70.     }  
  71.       
  72.     private static String[] mStatus = {"不存在""未知""正在充电""正在断电""不在充电""充满"};  
  73.     private static String[] mHealthy = {"不存在""未知""良好""过热""坏了""短路""未知错误""冷却"};  
  74.     private static String[] mPlugged = {"电池""充电器""USB""不存在""无线"};  
  75.   
  76.     private static String mChange = "";   
  77.     public static class PowerChangeReceiver extends BroadcastReceiver {  
  78.   
  79.         @Override  
  80.         public void onReceive(Context context, Intent intent) {  
  81.             if (intent != null) {  
  82.                 mChange = String.format("%s\n%s : 收到广播:%s",   
  83.                         mChange, DateUtils.getNowDateTime(), intent.getAction());  
  84.                 tv_power_status.setText(mChange);  
  85.             }  
  86.         }  
  87.     }  
  88. }  


省电方法/屏幕开关事件

前面说了许多废话,赶快回到本文的主题——省电。app开发与服务器程序开发不同,app所在的移动设备是很缺电的,几天就要充一次电,所以如果你的app特别耗电,一天甚至半天就把用户手机搞没电了,那么通常逃脱不了被卸载的悲惨命运。因此,为人为己,开发者还是尽可能让app运行的时候省电些,绿色环保的低碳生活,从开发app做起。


然而目前尚无法检测每个应用的耗电程度,一般是靠经验判断,基本原则就是:越消耗资源的,耗电就越大。具体到代码编写,主要有以下省电措施:
1、能用整型数计算,就不用浮点数计算。
2、能用json解析,就不用xml解析。
3、能用网络定位,就不用GPS定位。
4、尽量减少大文件的下载(如先压缩再下载,或者缓存已下载的文件)。
5、用完系统资源,要及时回收。占着茅坑不拉屎,用户手机会很蛋疼。相关例子参见《 Android开发笔记(七十五)内存泄漏的处理
6、能用线程处理,就不用进程处理。
7、多用缓存复用对象资源。如屏幕尺寸只需获取一次,其后可到缓存中读取,全局变量技术参见《 Android开发笔记(二十八)利用Application实现内存读写 》。相关例子还可参见《 Android开发笔记(七十六)线程池管理 》、《 Android开发笔记(七十七)图片缓存算法
8、能用定时器广播,就不用后台常驻服务。
9、能用内存存储,就不用文件存储。


省电措施虽多,那要如何得知省电效果呢?在实际开发中,耗电大户其实是在后台默默运行的Service服务,想想看,手机待机的时候,屏幕都不亮了,可是手机里面还有一些不知疲倦的Service在愚公移山,愚公也是要吃饭的呀。我做过实验,一个app在系统待机时仍然满血Service运行,一小时后手机电量消耗4%;同一个app改造后在系统待机时不运行任何Service,一小时后手机电量消耗2%;一小时相差2%,十小时便相差20%啊,原来我们手机的电量就是这样被一点一点耗光的。


既然如此,我们若想避免app在手机待机时仍在做无用功,就要在屏幕关闭时结束指定任务,在屏幕点亮时再开始指定任务。这里用到了下面三个屏幕开关事件:
Intent.ACTION_SCREEN_ON : 屏幕点亮事件
Intent.ACTION_SCREEN_OFF : 屏幕关闭事件
Intent.ACTION_USER_PRESENT : 用户解锁事件,静态注册时使用android.intent.action.USER_PRESENT
使用上述三个事件要注意几点:
1、屏幕点亮事件和屏幕关闭事件必须在代码中动态注册。如果在AndroidManifest.xml中静态注册,则不起任何作用。
2、在关闭屏幕时,系统先暂停所有活动页面,然后才关闭屏幕;同样的,在点亮屏幕时,系统点亮屏幕,然后才恢复活动页面。所以这几个事件不能在Activity中注册/注销,只能在自定义Application的onCreate方法中注册,在onTerminate方法中注销。
3、Activity要想获取屏幕开关事件,得通过自定义的Application类去间接获取。


下面是屏幕开关事件的捕捉截图:



下面是屏幕开关事件的代码:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. import com.example.exmbattery.util.DateUtils;  
  2.   
  3. import android.content.BroadcastReceiver;  
  4. import android.content.Context;  
  5. import android.content.Intent;  
  6. import android.util.Log;  
  7.   
  8. public class LockScreenReceiver extends BroadcastReceiver {  
  9.   
  10.     private static final String TAG = "LockScreenReceiver";  
  11.   
  12.     @Override  
  13.     public void onReceive(Context context, Intent intent) {  
  14.         if (intent != null) {  
  15.             String mChange = "";  
  16.             mChange = String.format("%s\n%s : 收到广播:%s", mChange,  
  17.                     DateUtils.getNowDateTime(), intent.getAction());  
  18.             if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {  
  19.                 mChange = String.format("%s\n这是屏幕点亮事件", mChange);  
  20.             } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {  
  21.                 mChange = String.format("%s\n这是屏幕关闭事件", mChange);  
  22.             } else if (intent.getAction().equals(Intent.ACTION_USER_PRESENT)) {  
  23.                 mChange = String.format("%s\n这是用户解锁事件", mChange);  
  24.             }  
  25.             Log.d(TAG, mChange);  
  26.             MainApplication.getInstance().setChangeDesc(mChange);  
  27.         }  
  28.     }  
  29.   
  30. }  


下面是自定义Application的代码:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. import android.app.Application;  
  2. import android.content.Intent;  
  3. import android.content.IntentFilter;  
  4.   
  5. public class MainApplication extends Application {  
  6.   
  7.     private static MainApplication mApp;  
  8.     private LockScreenReceiver mReceiver;  
  9.     private String mChange = "";  
  10.   
  11.     public static MainApplication getInstance() {  
  12.         return mApp;  
  13.     }  
  14.   
  15.     public String getChangeDesc() {  
  16.         return mApp.mChange;  
  17.     }  
  18.   
  19.     public void setChangeDesc(String change) {  
  20.         mApp.mChange = mApp.mChange + change;  
  21.     }  
  22.   
  23.     @Override  
  24.     public void onCreate() {  
  25.         super.onCreate();  
  26.         mApp = this;  
  27.         mReceiver = new LockScreenReceiver();  
  28.         IntentFilter filter = new IntentFilter();  
  29.         filter.addAction(Intent.ACTION_SCREEN_ON);  
  30.         filter.addAction(Intent.ACTION_SCREEN_OFF);  
  31.         filter.addAction(Intent.ACTION_USER_PRESENT);  
  32.         registerReceiver(mReceiver, filter);  
  33.     }  
  34.       
  35.     @Override  
  36.     public void onTerminate() {  
  37.         unregisterReceiver(mReceiver);  
  38.         super.onTerminate();  
  39.     }  
  40.       
  41. }  


下面是显示屏幕开关事件的页面代码
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. import android.app.Activity;  
  2. import android.os.Bundle;  
  3. import android.widget.TextView;  
  4.   
  5. public class ScreenActivity extends Activity {  
  6.   
  7.     private static TextView tv_screen;  
  8.       
  9.     @Override  
  10.     protected void onCreate(Bundle savedInstanceState) {  
  11.         super.onCreate(savedInstanceState);  
  12.         setContentView(R.layout.activity_screen);  
  13.         tv_screen = (TextView) findViewById(R.id.tv_screen);  
  14.     }  
  15.   
  16.     @Override  
  17.     protected void onStart() {  
  18.         super.onStart();  
  19.         tv_screen.setText(MainApplication.getInstance().getChangeDesc());  
  20.     }  
  21.   
  22. }  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值