Android性能优化之电量优化
头条
SEO资讯 2019-02-09 12:00:02 字数:21323

基于V2.0版本的battery historian请先看 battery historian安装与使用

Android性能优化之电量优化

(1).横坐标

横坐标就是一个时间范围,咱们的例子中统计的数据是以重置为起点,获取bugreport内容时刻为终点。我们一共采集了多长时间的数据

(2).纵坐标

关键的数据点我们用表格来汇总一下。

参数名作用
CPU runningCPU的运行状态,是否被唤醒。如果把鼠标放到上面去,还能看到更多的信息,如CPU唤醒的原因。
Screen亮屏状态,可以看到图表中该项着色有间隔,这是因为实验期间我关闭过屏幕,每关闭一次屏幕,着色就被打断。
Top app当前最上层的app
Mobile network type网络类型,其中需要注意的是,“免费网络可能包括wifi、蓝牙网络共享、USB网络共享”
Mobile radio active移动蜂窝信号 BP侧耗电,通常是指SIM卡,数据链接。该栏过多着色,间隔多。表示功耗也会高。
WiFi supplicantwifi是否开启
WiFi signal strengthwifi强度
Wifi Runningwifi连接情况下的耗电情况
Audio音频是否开启
Battery Level电量
Plugged是否正在充电,以及鼠标放在上面的时候可以看到充电类型,包括AC(充电器)、USB、其它(例如无线充电)
Battery Level开始测试时的电量,之前抓取的图可以看到电量是100,满电状态。
Top app前台应用,如果要分析应用的耗电情况,那么在测试期间,就该保证应用一直处于前台。
Userspace wakelock记录wake_lock模块的工作时间

ps:系统为了节省电量,CPU在没有任务忙的时候就会自动进入休眠。有任务需要唤醒CPU高效执行的时候,就会给CPU加wake_lock锁。

android电量统计的原理可以参看这篇文章:http://duanqz.github.io/2015-07-21-batterystats-part1

大致原理摘录如下:

一、电量记录

1. Android在进行电量统计时,并不是采用直接记录电流消耗量的方式,而是跟踪硬件模块在不同状态下的使用时间,收集一些可用信息,用来近似的计算出电池消耗量。

举一个例子,假定某个APK的使用了GPS,使用时间用 t 表示。GPS模块单位时间的耗电量用 w 表示,那么,这个APK使用GPS的耗电量就可以按照如下方式计算:

耗电量 = 单位时间耗电量(w) × 使用时间(t)

frameworks.jar里的frameworks/base/core/res/res/xml/power_profile.xml这个文件,记录着各个模块单位时间的耗电量, 由厂商定义。

以下是Nexus 5(hammerhead)耗电参数配置的代码片段:

  0 ... 3.5 73.24 75.48 ... 2300

2. Android框架层通过一个名为batterystats的系统服务,实现了电量统计的功能。

收集信息被组织起来,在内存中的数据结构是由BatteryStats类描述的。 为了能够从不同维度统计耗电量,这个数据结构设计得比较复杂,我们不在这里展开讨论,仅通过一个收集应用程序前台运行时间的例子,来说明信息收集过程。

记录应用程序中所有Activity从显示状态(Resumed)到消失状态(Paused)的时间,就能够统计应用程序的前台运行时间。Activity状态的切换是由AMS掌控的,因此AMS需要将Activity的状态信息通知给batterystats服务。

当Activity要切换到显示状态(Resumed)时,会调用ActivityStackSupervisor.resumeTopActivitiesLocked()方法,接下来会调用ActivityStack.resumeTopActivityInnerLocked()方法来完成Activity的状态切换,在完成状态切换后, 会调用ActivityStackSupervisor.reportResumedActivityLocked()方法,从这里开始,就开始通报了:“本Activity已经进入了显示状态”。在ActivityStackSupervisor.reportResumedActivityLocked()中得到BatteryStatsImpl对象,并启动一个计时器(StopwatchTimer),记录下了启动时间.在Activity pause时, 再得到结束时间, 这样就得到了应用程序的acitiviy在前台的运行时间了。

除了应用程序前台运行时间,还有很多信息是batterystats服务关注的,包括WakeLock、Sendor、Wifi、Audio、Video等,这些信息的采集方式与上述过程雷同,都会经过以下步骤:

由相应的模块发起状态变更的通知

BatteryStats使用定时器记录起止时间

二、电量信息的储存

Android支持历史电量信息的显示的,如果重新启动Android,那内存中的数据就丢失了, 所以需要把这些信息存储到磁盘上,磁盘上的 /data/system/batterystats.bin 文件中就是电量信息的序列化数据。

batterystats服务启动时,会从 batterystats.bin 这个文件中读取数据,来初始化BatteryStats这个数据结构。

三、电量计算

BatteryStatsHelper.refreshStats()承载了电量计算的全部过程,在需要显示电量统计信息的地方,就可以通过BatteryStatsHelper这个类,来获取统计完成的电量信息。 Setting.apk就引用了这个类。电量计算大体可以分为两块:

1. AppUsage:应用程序耗电量计算,是指每一个应用程序使用硬件模块所产生的耗电量

在BatteryStatsHelper.processAppUsage()这个方法中,实现了应用程序的电量计算(实际上统计的粒度是uid,不同的apk可以运行在同一个uid)。

2. MiscUsage:其他杂项耗电量计算

所谓杂项,其实就是用户比较关心的一大类,包括:待机的耗电量、亮屏的耗电量、通话的耗电量、Wifi的耗电量等,这个统计是系统层面的, 作为app的开发人员可以忽略掉这部分内容。

我们来总结一下应用程序的电量计算过程。Android通过一个名为BatteryStats.Uid的数据结构来维护一个应用程序的电量统计信息。 这个数据结构中,又包含很多子结构:

Proc:表示属于Uid的进程,一个Uid中可能会有多个进程,每个进程都有CPU占用时间

WakeLock:表示Uid持有的WakeLock锁的电量统计,一个Uid也可能会持有多个锁

Mobile Radio:表示Uid使用数据流量的电量统计,譬如3G流量、4G流量

Wifi:表示Uid使用wifi的电量统计

Sendor:表示Uid使用传感器的电量统计

Android提供的dumpsys命令用于查看系统服务的信息, 将batterystats作为参数,就能输出完整的电量统计信息。

adb shell dumpsys batterystats

电量优化建议:

当Android设备空闲时,屏幕会变暗,然后关闭屏幕,最后会停止CPU的运行,这样可以防止电池电量掉的快。在休眠过程中自定义的Timer、Handler、Thread、Service等都会暂停。但有些时候我们需要改变Android系统默认的这种状态:比如玩游戏时我们需要保持屏幕常亮,比如一些下载操作不需要屏幕常亮但需要CPU一直运行直到任务完成。从而防止因为唤醒的瞬间而耗更多的电。

1、判断充电状态

这里我们就需要思考,根据我们自己的业务,那些为了省电,可以放当手机插上电源的时候去做。

往往这样的情况非常多。像这些不需要及时地和用户交互的操作可以放到后面处理。

比如:360手机助手,当充上电的时候,才会自动清理手机垃圾,自动备份上传图片、联系人等到云端;再比如我们自己的APP,其中有一块业务是相册备份,这个时候有一个选项控制让用户选择是否在低于15%的电量时还继续进行备份,从而避免当用户手机低电量时,任然继续进行耗电操作。

我们可以通过下面的代码来获取手机的当前充电状态:

// It is very easy to subscribe to changes to the battery state, but you can get the current// state by simply passing null in as your receiver. Nifty, isn't that?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;//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)

3.1、使用wake_lock

系统为了节省电量,CPU在没有任务忙的时候就会自动进入休眠。有任务需要唤醒CPU高效执行的时候,就会给CPU加wake_lock锁。wake_lock锁主要是相对系统的休眠而言的,意思就是我的程序给CPU加了这个锁那系统就不会休眠了,这样做的目的是为了全力配合我们程序的运行。有的情况如果不这么做就会出现一些问题,比如微信等及时通讯的心跳包会在熄屏不久后停止网络访问等问题。所以微信里面是有大量使用到了wake_lock锁。

PowerManager这个系统服务的唤醒锁(wake locks)特征来保持CPU处于唤醒状态。唤醒锁允许程序控制宿主设备的电量状态。创建和持有唤醒锁对电池的续航有较大的影响,所以,除非是真的需要唤醒锁完成尽可能短的时间在后台完成的任务时才使用它。比如在Acitivity中就没必要用了。一种典型的代表就是在屏幕关闭以后,后台服务继续保持CPU运行。

如果不使用唤醒锁来执行后台服务,不能保证因CPU休眠未来的某个时刻任务会停止,这不是我们想要的。(有的人可能认为以前写的后台服务就没掉过链子呀运行得挺好的,

1.可能是你的任务时间比较短;

2.可能CPU被手机里面很多其他的软件一直在唤醒状态)。

其中,唤醒锁有下面几种类型:

Android性能优化之电量优化

唤醒锁的类型

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();

需要添加权限:

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(); //执行一些需要CPU保持唤醒的代码 //执行结束,释放唤醒锁 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"));

Tips:注意添加权限

Tips:注意服务与广播的注册

Tips:使用广播来设计,就是为了解耦

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) { // JobService在主线程运行,如果我们这里需要处理比较耗时的业务逻辑需单独开启一条子线程来处理并返回true, // 当给定的任务完成时通过调用jobFinished(JobParameters params, boolean needsRescheduled)告知系统。 //假设开启一个线程去下载文件 new DownloadTask().execute(jobParameters); return true; }else { //如果只是在本方法内执行一些简单的逻辑话返回false就可以了 return false; } } /** * 比如我们的服务设定的约束条件为在WIFI状态下运行,结果在任务运行的过程中WIFI断开了系统 * 就会通过回掉onStopJob()来通知我们停止运行,正常的情况下不会回掉此方法 * * @param jobParameters * @return */ @Override public boolean onStopJob(JobParameters jobParameters) { Log.d(TAG, "onStopJob:" + jobParameters.getJobId()); //如果需要服务在设定的约定条件再次满足时再次执行服务请返回true,反之false return true; } class DownloadTask extends AsyncTask { 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); //如果在onStartJob()中返回true的话,处理完成逻辑后一定要执行jobFinished()告知系统已完成, //如果需要重新安排服务请true,反之false jobFinished(mJobParameters, false); } }}

记得在Manifest文件内配置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来设定触发服务的约束条件,最少设定一个条件 JobInfo.Builder jobBuilder = new JobInfo.Builder(JOB_ID, new ComponentName(this, JobSchedulerService.class)); //循环触发,设置任务每三秒定期运行一次 jobBuilder.setPeriodic(3000); //单次定时触发,设置为三秒以后去触发。这是与setPeriodic(long time)不兼容的, // 并且如果同时使用这两个函数将会导致抛出异常。 jobBuilder.setMinimumLatency(3000); //在约定的时间内设置的条件都没有被触发时三秒以后开始触发。类似于setMinimumLatency(long time), // 这个函数是与 setPeriodic(long time) 互相排斥的,并且如果同时使用这两个函数,将会导致抛出异常。 jobBuilder.setOverrideDeadline(3000); //在设备重新启动后设置的触发条件是否还有效 jobBuilder.setPersisted(false); // 只有在设备处于一种特定的网络状态时,它才触发。 // JobInfo.NETWORK_TYPE_NONE,无论是否有网络均可触发,这个是默认值; // JobInfo.NETWORK_TYPE_ANY,有网络连接时就触发; // JobInfo.NETWORK_TYPE_UNMETERED,非蜂窝网络中触发; // JobInfo.NETWORK_TYPE_NOT_ROAMING,非漫游网络时才可触发; jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); //设置手机充电状态下触发 jobBuilder.setRequiresCharging(true); //设置手机处于空闲状态时触发 jobBuilder.setRequiresDeviceIdle(true); //得到JobInfo对象 JobInfo jobInfo = jobBuilder.build(); //设置开始安排任务,它将返回一个状态码 //JobScheduler.RESULT_SUCCESS,成功 //JobScheduler.RESULT_FAILURE,失败 if (mJobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) { //安排任务失败 } //停止指定JobId的工作服务 mJobScheduler.cancel(JOB_ID); //停止全部的工作服务 mJobScheduler.cancelAll(); }

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。(极光推送就是利用这个来做的。)

总结:

关键逻辑的执行过程,就需要Wake Lock来保护。如断线重连重新登陆

休眠的情况下如何唤醒来执行任务?用AlarmManager。如推送消息的获取

最后,通过Wakelock Detector(WLD)软件可以看到手机中的Wakelock:

Android性能优化之电量优化

WLD

Android性能优化之电量优化

想学习Android开发跟交流学习的可以加306978835群

0人参与回答
  • 未添加任何数据~~

提示信息

联系电话:
1585150508*
QQ交谈:
小编
站长微信:
站长邮箱: service@qingjiuzhubei.com