目录
写在前面
一、电量优化介绍及方案选择
1.1、如何正确认识电量优化
1.2、耗电量测试方案
二、Battery Historian实战分析
三、电量辅助监控实战
3.1、获取运行时能耗
3.2、运行时获取使用时长
3.3、获取线程使用时长
四、电量优化套路
上半年刚刚过去,下半年已经开启,时间过的是真快,骚年们,加油吧!
各位小伙伴们早上好!今天继续Android性能优化专题的学习,上一篇中介绍了关于Android网络优化相关的内容,没看过的小伙伴可以去看看哦——《深入探索Android网络优化》,今天咱们继续学习另外一项优化Android的电量优化,一起开始吧!
首先需要说明的是对于一般项目电量的关注度是远远不够的,因为我们开发阶段一般都会将手机和电脑相连接进行调试,这个过程实际上也是在给手机充电的,所以我们根本不知道电量减少的速度和总量。然后是对于线上用户基本上也不可能量化出来究竟消耗了多少电量,因为我们无法收集到用户的设备能耗,耗电统计是一个系统组件,伴随着系统运行的整个过程,是基于软件层面实现的,不同的硬件模块配置了不同的参数,然后使用算法进行估算,具体的参数值根据手机使用不同的硬件都是不一样的,所以电量的消耗在线上是难以量化的。
①、设置——耗电排行
②、注册电量相关广播——ACTION_BATTERY_CHANGED
下面我们使用这种方式在项目中简单的实践一下,我们来打印一下当前手机的电量是多少:
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
Intent intent = registerReceiver(null,filter);
LogUtils.i("battery--->当前电量为:"+intent.getIntExtra(BatteryManager.EXTRA_LEVEL,-1));
通过对比日志中打印出的电量和手机截屏的电量发现是一致的:
该方案作总结如下:
③、Battery Historian
该方案具体的使用方法在下面会具体介绍,现在简单的对这种方案做个总结:
④、关于测试
上面说到了我们并不能拿到精准的耗电量,这就要求我们的测试方法要能针对具体的耗电场景进行测试:
这个工具并没有集成在Android Studio中,需要我们自行安装:
导出电量信息
上传分析
下面来使用网站进行分析,能的小伙伴可以使用这种方式,无需安装Battery Historian,直接上传即可:
上传完成之后会自动进行分析,分析完成之后会生成图形化界面进行显示,左边是列出了相对耗电组件的运行情况,CPU的时间线说明CPU一直处于工作状态耗电几率则比较高,将鼠标移到Top app中这里面可以看到也列出了我们的Demo工程以及电量情况运行时间等信息,如下图所示:
左下角有一个App Selection,这里就是你可以选择特定的App,展示的电量消耗就只有选定的App了,我这里为了演示操作的时间并不长,所以没有明显的数据,这里就随便选择一个支付宝的app,其中Battery Percentage Consumed展示的就是电量消耗情况,Userspace Wakelocks是展示的WakeLock的使用情况:
Android系统中手机的每个组件:比如CPU、GPS、显示屏、WIFI、蓝牙等等,它们运行时的能耗都保存在power_profile.xml这个文件中,可以通过adb pull的方式将这个文件导出,然后进行反编译,通过读取文件就可以获取各个组件的能耗值:
下面我们就来实际操作一下:这个操作不需要额外的权限,通过这个命令就可以直接将这个apk导出到你的命令当前文件夹:
接着来反编译这个apk:关于反编译的方法我这里就不详细说了,毕竟不是本文的重点,不知道的可以去网上百度一下,执行下面的命令:
然后它就开始进行反编译了,耐心等待反编译完成即可:
然后找到反编译完成的文件夹的res/xml文件夹下的power_profile.xml文件:
注意:线上的这个文件我们是获取不到的,这些值我们也就获取不到,并且每个手机厂商所使用的硬件不一样,所以每个厂商设定的值都是不一样的,这也是我们没有办法在线上获取精准电量统计的原因之一。下面来看一下刚刚反编译出的这个文件:
这里面就列出了每个组件的能耗,了解这个过程虽然不能帮助我们精准的统计线上的电量消耗值,但是在平常App耗电量的测试以及开发过程中还是很有帮助的,因为获取到了这些值就可以清楚的知道在这个手机上哪些模块比较耗电,哪些模块在哪种状态下耗电最高,在测试的时候则重点关注调用了这些模块的地方。
下面以WakeLock的使用为例在项目中演示一下具体的统计方法:wakelock是Android中的一种锁机制,主要是相对系统的休眠而言的,如果应用程序给CPU加了这个锁那么系统就不会休眠,这样做可以更好的配合程序的运行。
首先新建一个WakeLock的使用工具类,后续使用wakelock服务就直接调用该类即可:
public class WakeLockUtils {
private static PowerManager.WakeLock sWakeLock;
public static void acquire(Context context){
if(sWakeLock == null){
sWakeLock = createWakeLock(context);
}
if(sWakeLock != null && !sWakeLock.isHeld()){
sWakeLock.acquire();
sWakeLock.acquire(1000);
}
}
public static void release(){
// 一些逻辑
try{
}catch (Exception e){
}finally {
// 为了演示正确的使用方式
if(sWakeLock != null && sWakeLock.isHeld()){
sWakeLock.release();
sWakeLock = null;
}
}
}
@SuppressLint("InvalidWakeLockTag")
private static PowerManager.WakeLock createWakeLock(Context context){
PowerManager pm = (PowerManager) context.getApplicationContext().getSystemService(Context.POWER_SERVICE);
if(pm != null){
return pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"");
}
return null;
}
}
然后找个地方进行调用:
WakeLockUtils.acquire(MainActivity.this);
new Handler().postDelayed(() -> WakeLockUtils.release(),200);
OK,写完了这些,我们就可以来写统计的代码了,这里采用AOP的方式进行统计,AOP框架采用的是Lancet,这是一个轻量级的AOP框架并且不会对包体积有任何的影响,关于Lancet的用法我在Android卡顿优化分析中实现界面秒开率统计的时候已经介绍过了,不了解的可以先去看一下它的用法或者直接去Github官网上看也行:
《Android卡顿优化分析及解决方案之界面秒开率统计》
GitHub地址:https://github.com/eleme/lancet
这里我们直接实现acquire和release方法即可,并且在release方法中统计耗时及堆栈信息:
public static long sStartTime = 0;
public static String trace;
public static Handler mHandler = new Handler();
public static Runnable releaseRun = WakeLockUtils::release;
@Insert(value = "acquire") //需要统计的方法
@TargetClass(value = "com.jarchie.performance.wakelock.WakeLockUtils",scope = Scope.SELF) //需要监控的是哪个类
public static void acquire(Context context){
//获取是哪个业务方调用的
trace = Log.getStackTraceString(new Throwable());
sStartTime = System.currentTimeMillis();
Origin.callVoid();
mHandler.postDelayed(releaseRun,1000);
}
@Insert(value = "release")
@TargetClass(value = "com.jarchie.performance.wakelock.WakeLockUtils",scope = Scope.SELF)
public static void release(){
//打印时间和调用堆栈
LogUtils.i("PowerManager "+(System.currentTimeMillis() - sStartTime)+"/n"+trace);
Origin.callVoid();
}
我们运行程序之后就能够打印出来时间和具体的调用堆栈了:
如果某个线程的使用时长过长,那它可能就是处于死循环的状态,然后就可以将这种信息记录下来进行上报分析确认,这里还是使用AOP的方式去获取时间及堆栈:
public static long runTime = 0;
@Insert(value = "run")
@ImplementedInterface(value = "java.lang.Runnable",scope = Scope.ALL)
public void run(){
trace = Log.getStackTraceString(new Throwable());
runTime = System.currentTimeMillis();
Origin.callVoid();
LogUtils.i("runTime "+(System.currentTimeMillis() - runTime)+"/n"+trace);
}
这样我们就统计了出了每个线程的执行时间,我这里截取了其中一条的日志信息:
①、CPU时间片
如果由于我们的App导致CPU一直处于工作状态,这样肯定是非常消耗电量的,之前介绍过获取CPU信息的方式:
②、网络相关
③、定位相关
④、界面相关
简单的举个栗子吧:
private AlphaAnimation alphaAnimation;
@Override
protected void onResume() {
super.onResume();
alphaAnimation.start();
}
@Override
protected void onPause() {
super.onPause();
alphaAnimation.cancel();
}
⑤、WakeLock相关
⑥、JobScheduler
这里也简单的实践一把:
@SuppressLint("NewApi")
public class JobSchedulerService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
// 此处执行在主线程
// 模拟一些处理:批量网络请求,APM日志上报
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}
在AndroidManifest文件中注册:
在调用处执行:这里设置了任务需要在用户充电并且处于WIFI的情况下执行
private void startJobScheduler(){
//需在Android5.0以上执行
if (Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP){
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(1,new ComponentName(getPackageName(), JobSchedulerService.class.getName()));
builder.setRequiresCharging(true) //任务执行时需要连接电源
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); //WIFI状态下
scheduler.schedule(builder.build()); //开启任务
}
}
好了,关于电量优化相关的内容就先说到这里了,下半年了,大家加油啊!
祝各位:工作顺利,不负韶华!