####电量优化建议:
当Android设备空闲时,屏幕会变暗,然后关闭屏幕,最后会停止CPU的运行,这样可以防止电池电量掉的快。在休眠过程中自定义的Timer、Handler、Thread、Service等都会暂停。但有些时候我们需要改变Android系统默认的这种状态:比如玩游戏时我们需要保持屏幕常亮,比如一些下载操作不需要屏幕常亮但需要CPU一直运行直到任务完成。从而防止因为唤醒的瞬间而耗更多的电。
####1、判断充电状态
这里我们就需要思考,根据我们自己的业务,那些为了省电,可以放当手机插上电源的时候去做。
往往这样的情况非常多。像这些不需要及时地和用户交互的操作可以放到后面处理。
比如:360手机助手,当充上电的时候,才会自动清理手机垃圾,自动备份上传图片、联系人等到云端;再比如我们自己的APP,其中有一块业务是相册备份,这个时候有一个选项控制让用户选择是否在低于15%的电量时还继续进行备份,从而避免当用户手机低电量时,任然继续进行耗电操作。
我们可以通过下面的代码来获取手机的当前充电状态:
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = this.registerReceiver(null, filter);
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC);
if (acCharge) {
Log.v(LOG_TAG,“The phone is charging!”);
复制代码
private boolean checkForPower() {
//获取电池的充电状态(注册一个广播)
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED)
Intent res = this.registerReceiver(null, filter)
//通过使用BatteryManager的参数信息判断充电状态
if (res != null) {
int chargePlug = res.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
boolean usb = chargePlug == BatteryManager.BATTERY_PLUGGED_USB
boolean ac = chargePlug == BatteryManager.BATTERY_PLUGGED_AC
//无线充电,这个需要API>=17
boolean wireless = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
wireless = chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS
return (usb || ac || wireless)
} else {
return false
复制代码
调用示例
private void applyFilter() {
if(!checkForPower()){
mPowerMsg.setText("请充上电,再处理!");
return;
mCheyennePic.setImageResource(R.drawable.pink_cheyenne);
mPowerMsg.setText(R.string.photo_filter);
复制代码
####2、屏幕保持常亮
为了防止屏幕唤醒一瞬间耗电过多,有一些应用,比如游戏、支付页面,需要保持屏幕常亮来节省电量:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
复制代码
也可以在布局文件里面使用,但是没有那么灵活:
android:keepScreenOn="true"
复制代码
注意:一般不需要人为的去掉FLAG_KEEP_SCREEN_ON的flag,windowManager会管理好程序进入后台回到前台的的操作。如果确实需要手动清掉常亮的flag,使用
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
复制代码
#####android:keepScreenOn = ” true “的作用和FLAG_KEEP_SCREEN_ON一样。使用代码的好处是你允许你在需要的地方关闭屏幕。
注意:一般不需要人为的去掉FLAG_KEEP_SCREEN_ON的flag,windowManager会管理好程序进入后台回到前台的的操作。如果确实需要手动清掉常亮的flag,使用getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
####3.1、使用wake_lock
系统为了节省电量,CPU在没有任务忙的时候就会自动进入休眠。有任务需要唤醒CPU高效执行的时候,就会给CPU加wake_lock锁。wake_lock锁主要是相对系统的休眠而言的,意思就是我的程序给CPU加了这个锁那系统就不会休眠了,这样做的目的是为了全力配合我们程序的运行。有的情况如果不这么做就会出现一些问题,比如微信等及时通讯的心跳包会在熄屏不久后停止网络访问等问题。所以微信里面是有大量使用到了wake_lock锁。
PowerManager这个系统服务的唤醒锁(wake locks)特征来保持CPU处于唤醒状态。唤醒锁允许程序控制宿主设备的电量状态。创建和持有唤醒锁对电池的续航有较大的影响,所以,除非是真的需要唤醒锁完成尽可能短的时间在后台完成的任务时才使用它。比如在Acitivity中就没必要用了。一种典型的代表就是在屏幕关闭以后,后台服务继续保持CPU运行。
如果不使用唤醒锁来执行后台服务,不能保证因CPU休眠未来的某个时刻任务会停止,这不是我们想要的。(有的人可能认为以前写的后台服务就没掉过链子呀运行得挺好的,
1.可能是你的任务时间比较短;
2.可能CPU被手机里面很多其他的软件一直在唤醒状态)。
其中,唤醒锁有下面几种类型:
######wake_lock两种锁(从释放、使用的角度来看的话):计数锁和非计数锁(锁了很多次,只需要release一次就可以解除了)
#####请注意,自 API 等级17开始,FULL_WAKE_LOCK将被弃用,应使用FLAG_KEEP_SCREEN_ON代替。
综上所述,为了防止CPU唤醒一瞬间耗电过多,在执行关键代码的时候,为了防止CPU睡眠,需要使用唤醒锁来节省电量:
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "partial_lock");
wakeLock.acquire();
wakeLock.release();
复制代码
需要添加权限:
<uses-permission android:name="android.permission.WAKE_LOCK"/>
复制代码
Tips:获取与释放唤醒锁需要成对出现
Tips:有一些意外的情况,比如小米手机是做了同步心跳包(心跳对齐)(如果超过了这个同步的频率就会被屏蔽掉或者降频),所有的app后台唤醒频率不能太高,这时候就需要降频,比如每隔2S中去请求。
####3.2、使用WakefulBroadcastReceiver
上面提到,典型的使用场景就是后台服务需要保持CPU保持运行,但推荐的方式是使用WakefulBroadcastReceiver:使用广播和Service(典型的IntentService)结合的方式可以让你很好地管理后台服务的生命周期。
WakefulBroadcastReceiver是BroadcastReceiver的一种特例。它会为你的APP创建和管理一个PARTIAL_WAKE_LOCK 类型的WakeLock。一个WakeBroadcastReceiver接收到广播后将工作传递给Service(一个典型的IntentService),直到确保设备没有休眠。如果你在交接工作给服务的时候没有保持唤醒锁,在工作还没完成之前就允许设备休眠的话,将会出现一些你不愿意看到的情况。
public class MyIntentService extends IntentService {
public MyIntentService(String name) {
super(name);
public MyIntentService() {
super(MyIntentService.class.getSimpleName());
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent != null) {
Bundle extras = intent.getExtras();
MyWakefulReceiver.completeWakefulIntent(intent);
复制代码
广播接收者:
public class MyWakefulReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, MyIntentService.class);
startWakefulService(context, service);
复制代码
需要使用服务的时候,像一般的方式一样即可:
Intent intent = new Intent(this, MyIntentService.class);
intent.setData(Uri.parse("xxx"));
复制代码
#####分析WakefulBroadcastReceiver 源码(查看源码记得引入v4包)
public abstract class WakefulBroadcastReceiver extends BroadcastReceiver {
private static final String EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid";
private static final SparseArray<PowerManager.WakeLock> mActiveWakeLocks
= new SparseArray<PowerManager.WakeLock>();
private static int mNextId = 1;
* Do a {@link android.content.Context#startService(android.content.Intent)
* Context.startService}, but holding a wake lock while the service starts.
* This will modify the Intent to hold an extra identifying the wake lock;
* when the service receives it in {@link android.app.Service#onStartCommand
* Service.onStartCommand}, it should pass back the Intent it receives there to
* {@link #completeWakefulIntent(android.content.Intent)} in order to release
* the wake lock.
* @param context The Context in which it operate.
* @param intent The Intent with which to start the service, as per
* {@link android.content.Context#startService(android.content.Intent)
* Context.startService}.
public static ComponentName startWakefulService(Context context, Intent intent) {
synchronized (mActiveWakeLocks) {
int id = mNextId;
mNextId++;
if (mNextId <= 0) {
mNextId = 1;
intent.putExtra(EXTRA_WAKE_LOCK_ID, id);
ComponentName comp = context.startService(intent);
if (comp == null) {
return null;
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"wake:" + comp.flattenToShortString());
wl.setReferenceCounted(false);
wl.acquire(60*1000);
mActiveWakeLocks.put(id, wl);
return comp;
* Finish the execution from a previous {@link #startWakefulService}. Any wake lock
* that was being held will now be released.
* @param intent The Intent as originally generated by {@link #startWakefulService}.
* @return Returns true if the intent is associated with a wake lock that is
* now released; returns false if there was no wake lock specified for it.
public static boolean completeWakefulIntent(Intent intent) {
final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0);
if (id == 0) {
return false;
synchronized (mActiveWakeLocks) {
PowerManager.WakeLock wl = mActiveWakeLocks.get(id);
if (wl != null) {
wl.release();
mActiveWakeLocks.remove(id);
return true;
Log.w("WakefulBroadcastReceiver", "No active wake lock id #" + id);
return true;
复制代码
######我们发现WakefulBroadcastReceiver 本质上还是基于 PowerManager.WakeLock
######注意:
######1.注意添加权限
######2.注意服务与广播的注册
######3.使用广播来设计,就是为了解耦
网上采集的一些问题坑点及解决如下:
######1.向服务器轮询的代码不执行。
曾经做一个应用,利用Timer和TimerTask,来设置对服务器进行定时的轮询,但是发现机器在某段时间后,轮询就不再进行了。查了很久才发 现是休眠造成的。后来解决的办法是,利用系统的AlarmService来执行轮询。因为虽然系统让机器休眠,节省电量,但并不是完全的关机,系统有一部 分优先级很高的程序还是在执行的,比如闹钟,利用AlarmService可以定时启动自己的程序,让cpu启动,执行完毕再休眠。
#####解决:利用系统的AlarmService来替代Timer和TimerTask 执行轮询
######2.后台长连接断开。
最近遇到的问题。利用Socket长连接实现QQ类似的聊天功能,发现在屏幕熄灭一段时间后,Socket就被断开。屏幕开启的时候需进行重连,但 每次看Log的时候又发现网络是链接的,后来才发现是cpu休眠导致链接被断开,当你插上数据线看log的时候,网络cpu恢复,一看网络确实是链接的, 坑。最后使用了PARTIAL_WAKE_LOCK,保持CPU不休眠。
#####解决:使用了PARTIAL_WAKE_LOCK,保持CPU不休眠。
######3.调试时是不会休眠的。
让我非常郁闷的是,在调试2的时候,就发现,有时Socket会断开,有时不会断开,后来才搞明白,因为我有时是插着数据线进行调试,有时拔掉数据线,这 时Android的休眠状态是不一样的。而且不同的机器也有不同的表现,比如有的机器,插着数据线就会充电,有的不会,有的机器的设置的充电时屏幕不变暗 等等,把自己都搞晕了。其实搞明白这个休眠机制,一切都好说了。
######通过Wakelock Detector(WLD)软件可以看到手机中的Wakelock:
####3.3、大量高频次的CPU唤醒及操作使用JobScheduler/GCM
自 Android 5.0 发布以来,JobScheduler 已成为执行后台工作的首选方式,其工作方式有利于用户。应用可以在安排作业的同时允许系统基于内存、电源和连接情况进行优化。JobSchedule的宗旨就是把一些不是特别紧急的任务放到更合适的时机批量处理。这样做有两个好处:
避免频繁的唤醒硬件模块,造成不必要的电量消耗。
避免在不合适的时间(例如低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量;
JobScheduler的简单使用,首先自定义一个Service类,继承自JobService
public class JobSchedulerService extends JobService{
private String TAG = JobSchedulerService.class.getSimpleName();
@Override
public boolean onStartJob(JobParameters jobParameters) {
Log.d(TAG, "onStartJob:" + jobParameters.getJobId());
if(true) {
new DownloadTask().execute(jobParameters);
return true;
}else {
return false;
* 比如我们的服务设定的约束条件为在WIFI状态下运行,结果在任务运行的过程中WIFI断开了系统
* 就会通过回掉onStopJob()来通知我们停止运行,正常的情况下不会回掉此方法
* @param jobParameters
* @return
@Override
public boolean onStopJob(JobParameters jobParameters) {
Log.d(TAG, "onStopJob:" + jobParameters.getJobId());
return true;
class DownloadTask extends AsyncTask<JobParameters, Object, Object> {
JobParameters mJobParameters;
@Override
protected Object doInBackground(JobParameters... jobParameterses) {
mJobParameters = jobParameterses[0];
try {
Thread.sleep(30*1000);
} catch (InterruptedException e) {
e.printStackTrace();
return null;
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
jobFinished(mJobParameters, false);
复制代码
记得在Manifest文件内配置Service
<service android:name=".JobSchedulerService" android:permission="android.permission.BIND_JOB_SERVICE"/>
复制代码
创建工作计划
public class MainActivity extends Activity{
private JobScheduler mJobScheduler;
private final int JOB_ID = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mai_layout);
mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE );
JobInfo.Builder jobBuilder = new JobInfo.Builder(JOB_ID, new ComponentName(this, JobSchedulerService.class));
jobBuilder.setPeriodic(3000);
jobBuilder.setMinimumLatency(3000);
jobBuilder.setOverrideDeadline(3000);
jobBuilder.setPersisted(false);
jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
设置重试/退避策略,当一个任务调度失败的时候执行什么样的测量采取重试。
initialBackoffMillis:第一次尝试重试的等待时间间隔ms
*backoffPolicy:对应的退避策略。比如等待的间隔呈指数增长。
jobBuilder .setBackoffCriteria(JobInfo.MAX_BACKOFF_DELAY_MILLIS, JobInfo.BACKOFF_POLICY_LINEAR)
jobBuilder.setRequiresCharging(true);
jobBuilder.setRequiresDeviceIdle(true);
JobInfo jobInfo = jobBuilder.build();
if (mJobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) {
mJobScheduler.cancel(JOB_ID);
mJobScheduler.cancelAll();
复制代码
量高频次的CPU唤醒及操作,我们最好把这些操作集中处理。我们可以采取一些算法来解决。
可以借鉴谷歌的精髓,JobScheduler/GCM。
####4、使用AlarmManager来唤醒
当机器一段时间不操作以后,就会进入睡眠状态。向服务器的轮询就会停止、长连接就会断开,为了防止这样的情况,就可以使用AlarmManager:
Intent intent = new Intent(this, TestService.class)
PendingIntent pi = PendingIntent.getService(this, 0, intent, 0)
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE)
am.cancel(pi)
//闹钟在系统睡眠状态下会唤醒系统并执行提示功能
//模糊时间,在API-19中以及以前,setRepeating都是不准确的
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, 2000, pi)
//准确时间,但是需要在API-17之后使用
am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, pi)
am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, pi)
复制代码
该定时器可以启动Service服务、发送广播、跳转Activity,并且会在系统睡眠状态下唤醒系统。所以该方法不用获取电源锁和释放电源锁。
关于AlarmManager的更多信息,请参考其他文章。
在19以上版本,setRepeating中设置的频率只是建议值(6.0 的源码中最小值是60s),如果要精确一些的用setWindow或者setExact。
####5、其他优化
当然,电量优化是包括很多方面的,例如:
#####渲染优化
#####定位策略优化
#####网络优化,例如网络缓存处理,请求方式、次数优化、设置超时时间等等
#####代码执行效率优化
#####防止内存泄漏
#####等等,电量优化无处不在。
####深化
######首先Android手机有两个处理器,一个叫Application Processor(AP),一个叫Baseband Processor(BP)。AP是ARM架构的处理器,用于运行Linux+Android系统;BP用于运行实时操作系统(RTOS),通讯协议栈运行于BP的RTOS之上。非通话时间,BP的能耗基本上在5mA左右,而AP只要处于非休眠状态,能耗至少在50mA以上,执行图形运算时会更高。另外LCD工作时功耗在100mA左右,WIFI也在100mA左右。一般手机待机时,AP、LCD、WIFI均进入休眠状态,这时Android中应用程序的代码也会停止执行。
######Android为了确保应用程序中关键代码的正确执行,提供了Wake Lock的API,使得应用程序有权限通过代码阻止AP进入休眠状态。但如果不领会Android设计者的意图而滥用Wake Lock API,为了自身程序在后台的正常工作而长时间阻止AP进入休眠状态,就会成为待机电池杀手。比如前段时间的某应用,比如现在仍然干着这事的某应用。
AlarmManager 是Android 系统封装的用于管理 RTC 的模块,RTC (Real Time Clock) 是一个独立的硬件时钟,可以在 CPU 休眠时正常运行,在预设的时间到达时,通过中断唤醒 CPU。(极光推送就是利用这个来做的。)
####总结:
######1.关键逻辑的执行过程,就需要Wake Lock来保护。如断线重连重新登陆
######2.休眠的情况下如何唤醒来执行任务?用AlarmManager。如推送消息的获取
其他参考资料:
alarmManager在手机休眠时无法唤醒Service的问题?( 为了对付的频繁唤醒的”流氓”app,有的厂家都开发了心跳对齐。)
https://www.zhihu.com/question/36421849
微信 Android 版 6.2 为什么设置了大量长时间的随机唤醒锁?
https://www.zhihu.com/question/31136645
特别感谢:
动脑学院Ricky
kpioneer123
Java研发工程师 @ HC