- 最新高二英语教学反思【优秀5篇】2024-12-13
- 英文推荐信_122024-12-13
- 留学瑞典2024-12-13
- 澳洲出国留学详解!附真实经历2024-12-13
- 移民加拿大最容易的三个省,门槛低,名额充足!2024-12-13
- 出国留学好处多还是坏处多?2024-12-13
- 大学排名_22024-11-26
- 985/211院校均分85能申请英国哪些大学研究生?这八大名校妥妥滴2024-11-26
- 美术生留学哪里好?2024-11-26
- 专科留学去哪个国家比较好?2024-11-26
奇亿娱乐-奇亿注册登录站
邮箱:youweb@admin.com
手机:13899999999
电话:020-88888888
地址:广东省广州市番禺经济开发区
在 Android 应用开发中,我们需要考虑的是如何优化电量使用,让我们的 App 不会因为电量消耗过高被用户排斥,或者被其他安全应用报告,以此确保用户黏性。
开发中一直连接手机,不知道电量消耗有多快。
我们没有办法拿到每一个用户手机的组件能耗,其中不同的硬件模块使用了不同的参数,然后使用了不同的算法来进行估算。但是,具体的参数值根据手机所使用的硬件来说是不一样的。
现在一般手机的电池容量会占用内部组件将近一半的空间。
P=UI(电功率=电压 * 电流)
通常使用充电循环次数衡量。
严格控制电池容量,例如 VOOC 就使用了各种安全检测技术。
分批有效地收集和传递传感器事件。
批处理在合理的相似时间内的所有应用的闹铃,以便系统仅唤醒一次。
对于电量的统计有一个公式,如下所示:
模块电量(mAh)=模块电流(mA)* 模块耗时(h)
Android 系统要求 ROM 厂商必须在 /frameworks/base/core/res/res/xml/power_profile.xml 提供组件的电源配置文件。而 Android 系统的电量计算 PowerProfile 正是通过读取 power_profile.xml 的数据。
获取电池电量、充电状态、电池状态等信息。
IntentFilter filter=new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
Intent intent=registerReceiver(null, filter);
LogUtils.i("battery " + intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1));
batterystats 是 Android 5.0 提供的工具,它可以获取各个 App 的 WakeLock、CPU 时间占用等信息,同时增加了一个 Estimated power use(mAh)功能,预估耗电量。
将电量测量转化为功能模块的使用时间或者次数。
adb shell dumpsys batterystats > battery.txt
在 battery.txt 搜索 ‘Estimated power use’ 关键字,下面粗略统计了各个 Uid 的总耗电量。
Estimated power use (mAh):
Capacity: 3350, Computed drain: 2767, actual drain: 3752-3853
Uid 1000: 1014 ( cpu=999 wake=1.36 radio=11.4 wifi=1.24 gps=0.435 sensor=0.808 ) Excluded from smearing
Unaccounted: 985 ( ) Including smearing: 0 ( ) Excluded from smearing
Uid 0: 416 ( cpu=157 wake=210 radio=38.8 wifi=9.51 ) Excluded from smearing
...
batterystats 所记录的电量统计数据源自于 BatteryStatsService-电量统计服务,其实现类为 BatteryStatsImpl,内部正是使用的 PowerProfile 。
BatteryStatsImpl 为每一个应用创建与之对应的 UID 来监控器系统资源的使用情况,其统计了 12 大模块的电量消耗,如下所示:
如果打不开,可以使用备用网站 bathist
在 Add Metrics 中我们可以增加更多的测量项。
如果一直处于 running,则表明电量消耗比较高。
选中 Job Scheduler 的某一个工作时间片,我们可以查看具体的 发生的时间、耗时以及次数,最重要的是它统计出来了是哪一个进程在使用这个 JobScheduler。
选择多个文件进行上传对比。
在 App 开发中,经常会由于某个需求场景或 代码 bug 而导致大量耗电。
对于耗电优化中,我们最常用的就是 JobScheduler,下面 ,我们来实战一下。
/**
* 开启 JobScheduler
*/
private void startJobScheduler(){
if (Build.VERSION.SDK_INT >=Build.VERSION_CODES.LOLLIPOP){
JobScheduler jobScheduler=(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder=new JobInfo.Builder(1, new ComponentName(getPackageName(), JobSchedulerService.class.getName()));
// 设置仅在 充电和WIFI 下才使用 JobScheduler 进行批量任务处理
builder.setRequiresCharging(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
jobScheduler.schedule(builder.build());
}
}
其中,「JobSchedulerService 就是用于进行批量任务处理的服务」,示例代码如下所示:
/**
* 用于进行批量任务处理的 JobSchedulerService
*/
@RequiresApi(api=Build.VERSION_CODES.LOLLIPOP)
public class JobSchedulerService extends JobService{
@Override
public boolean onStartJob(JobParameters params){
// 此处执行在主线程
// 模拟一些处理:批量网络请求,APM日志上报
return false;
}
@Override
public boolean onStopJob(JobParameters params){
return false;
}
}
符合 Android 规则,手机在充电状态才去做耗电工作。示例代码如下所示:
IntentFilter ifilter=new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus=context.registerReceiver(null, ifilter);
//获取用户是否在充电的状态或者已经充满电了
int status=batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging=status==BatteryManager.BATTERY_STATUS_CHARGING || status==BatteryManager.BATTERY_STATUS_FULL;
避免后台长时间获取 WakeLock、WIFI 和蓝牙的扫描等。
Android P 使用了 Android Vitals 监控后台耗电,其规则如下所示:
「Android 手机保护 AP 和 BP 两个 CPU。AP 即 Application Processor,所有的用户界面以及 App 都是运行在 AP 上的。BP 即 Baseband Processor,手机射频都是运行在这个 CPU 上的。而一般我们所说的耗电,PowerProfile 文件里面的 CPU,指的是 AP」。
CPU 耗电通常有两种情况:
常用优化 CPU 时间片的方式有:
通常情况下,使用 WIFI 连接网络时的功耗要低于使用移动网络的功耗。而使用移动网络传输数据,电量的消耗有以下3种状态:
因此,为了避免网络连接所带来的电量消耗,我们可以采用如下几种方案:
WakeLock 常用于后台播放音视频、录制音视频、下载文件的情况。如果没有合理使用 WakeLock,则会造成严重的耗电问题,为了避免该问题,「我们应该定期针对使用了 WakeLock 的模块进行重点排查」。
我们可以使用 adb shell dumpsys power
命令查看系统当前的耗电信息,其中我们可以看到 WakeLock 列表,它通常会以 「”mLocks.size“ 或者 ”Wake Locks:size“」 开头。关于 WakeLock 的使用我们要着重注意以下几点:
「浮点运算比整数运算更消耗 CPU 时间片,因此耗电也会增加」。避开浮点运算的优化方法如下所示:
「我们可以监听灭屏以及亮屏的广播,在灭屏的时候停止 surfaceView 的动画绘制。在亮屏的时候,恢复动画的绘制」。
以后台耗电监控为主,必须监控的模块有:
「必须监控的现场信息有」 :
最后,我们需要 「提炼规则,将监控内容=> 抽象成规则」。
我们可以通过代理对应的 Service 实现,完成收集 Wakelock、Alarm、GPS 的申请堆栈、释放信息、手机充电状态等等。
这里我们就以 WakeLock 的监控为例,切面代码如下所示:
public static long sStartTime=0;
@Insert(value="acquire")
@TargetClass(value="com.optimize.performance.wakelock.WakeLockUtils",scope=Scope.SELF)
public static void acquire(Context context){
trace=Log.getStackTraceString(new Throwable());
sStartTime=System.currentTimeMillis();
Origin.callVoid();
new Handler().postDelayed(new Runnable(){
@Override
public void run(){
WakeLockUtils.release();
}
},1000);
}
@Insert(value="release")
@TargetClass(value="com.optimize.performance.wakelock.WakeLockUtils",scope=Scope.SELF)
public static void release(){
LogUtils.i("PowerManager "+(System.currentTimeMillis() - sStartTime)+"/n"+trace);
此外,我们也可以利用 epic 来监控每个线程的执行时间,超过阈值则警告,示例代码如下所示:
public static long runTime=0;
@Insert(value="run")
@TargetClass(value="java.lang.Runnable",scope=Scope.ALL)
public void run(){
runTime=System.currentTimeMillis();
Origin.callVoid();
LogUtils.i("runTime "+(System.currentTimeMillis() - runTime));
}
「写一个基础类,然后在统一的调用接口中添加监控逻辑」。这里我们可以参考 Facebook Battery-Metrics
获取、监控数据的方式。其代码如下所示:
public class WakelockMetrics{
/**
* 获取 WakeLock
*
* @param wakeLock WakeLock
* @param timeout 超时时间
*/
public static void acquire(PowerManager.WakeLock wakeLock, long timeout){
wakeLock.acquire(timeout);
// 监控 wakelock 相关信息
Log.e("HOOOOOOOOK", "--acquireWakeLock--");
Log.e("HOOOOOOOOK", Utils.getStackTrace());
// 使用 Battery-Metrics 库统计其它维度的电量信息
}
/**
* 释放 WakeLock
*
* @param wakeLock WakeLock
*/
public static void release(PowerManager.WakeLock wakeLock){
wakeLock.release();
Log.e("HOOOOOOOOK", "--releaseWakeLock--");
Log.e("HOOOOOOOOK", Utils.getStackTrace());
// 使用 Battery-Metrics 库统计其它维度的电量信息
}
}
Gradle 耗电量统计插件中 BatteryCreateMethodVisitor 的核心实现代码如下所示:
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface){
// 监控 Wakelock
String monitorClass="com/ss/android/ugc/bytex/example/battery_monitor/WakelockMetrics";
if (!monitorClass.equals(className)
&& "android/os/PowerManager$WakeLock".equals(owner)
&& opcode==Opcodes.INVOKEVIRTUAL
&& "acquire".equals(name)){
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
monitorClass,
name,
"(Landroid/os/PowerManager$WakeLock;J)V",
isInterface
);
return;
}
if (!monitorClass.equals(className)
&& "android/os/PowerManager$WakeLock".equals(owner)
&& opcode==Opcodes.INVOKEVIRTUAL
&& "release".equals(name)){
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
monitorClass,
name,
"(Landroid/os/PowerManager$WakeLock;)V",
isInterface
);
return;
}
// 监控 Gps
monitorClass="com/ss/android/ugc/bytex/example/battery_monitor/GpsMetrics";
if (!monitorClass.equals(className)
&& "android/location/LocationManager".equals(owner)
&& opcode==Opcodes.INVOKEVIRTUAL
&& "requestLocationUpdates".equals(name)){
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
monitorClass,
name,
"(Landroid/location/LocationManager;Ljava/lang/String;JFLandroid/location/LocationListener;)V",
isInterface
);
return;
}
if (!monitorClass.equals(className)
&& "android/location/LocationManager".equals(owner)
&& opcode==Opcodes.INVOKEVIRTUAL
&& "removeUpdates".equals(name)){
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
monitorClass,
name,
"(Landroid/location/LocationManager;Landroid/location/LocationListener;)V",
isInterface
);
return;
}
// 监控 Alarm Service
monitorClass="com/ss/android/ugc/bytex/example/battery_monitor/AlarmMetrics";
if (!monitorClass.equals(className)
&& "android/app/AlarmManager".equals(owner)
&& opcode==Opcodes.INVOKEVIRTUAL
&& "set".equals(name)){
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
monitorClass,
name,
"(Landroid/app/AlarmManager;IJLandroid/app/PendingIntent;)V",
isInterface
);
return;
}
if (!monitorClass.equals(className)
&& "android/app/AlarmManager".equals(owner)
&& opcode==Opcodes.INVOKEVIRTUAL
&& "cancel".equals(name)){
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
monitorClass,
name,
"(Landroid/app/AlarmManager;Landroid/app/PendingIntent;)V",
isInterface
);
return;
}
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
系统的代码插桩方案无法替换。
电量相关的测试相对来说难度较大,因为 App 在具体手机上的耗电量无法准确统计,每一个手机所使用的硬件不一样,那么它相应的功耗就不一样。而且这个功耗值我们只能在线下通过导出手机的 power_profile.xml 文件拿到。
由于我们无法获取准确的耗电量,所以我们只能增加多个维度来辅助判断 App 是否耗电。
最后,我们可以分场景各个突破。
关于电量测试,我们可以针对各个功能场景进行针对性的专项测试。操作一段时间后,我们可以在手机设置—电量消耗里面,利用其数据作为判断依据。这样虽然直观,但精确度不行。
介绍 Battery Historian:
因为我们不能在线上统计出 App 的电量消耗,因此需要在尽量保证 App 在正常使用下的耗电。对此我们采取了一系列的电量优化措施:
根据场景谨慎地选择传感器使用的模式,比如说在使用 GPS 的时候一般要避免使用高精度的模式,或者是尽量复用上一次的定位结果。
我们在实际项目中使用 WakeLock 有几个注意事项,第一,acquire、release 要成对地释放,第二,尽量使用 acquire 的超时方法来设置超时时间,避免因为异常情况从而导致 WakeLock 而无法释放的情况,第三,关于 WakeLock 的释放一定要写在 try-catch-finally 的 finally 当中,保证 WakeLock 在异常情况下的释放。
JobScheduler 可以允许开发者在符合某些条件下创造执行在后台的任务,我们可以设置执行一些耗电操作的场景,比如说 处于 WIFI 状态下同时连接电源 的情况下。同时,要注意用户在离开界面后,要避免耗电的操作,比如说停止播放动画。通过这些操作,我们的 App 就不会比之前耗电了。
对于电量优化来说,最重要的就是 「建立监控与自动化报警的一整套体系,只有发现了耗电的问题所在,才能使用针对性的解决措施」。
现如今,Android 行业人才已逐渐饱和化,但高级人才依旧很稀缺,我们经常遇到的情况是,100份简历里只有2、3个比较合适的候选人,大部分的人都是疲于业务,没有花时间来好好学习,或是完全不知道学什么来提高自己的技术。对于 Android 开发者来说,尽早建立起一个完整的 Android 知识框架,了解目前大厂高频出现的常考知识点,掌握面试技巧,是一件非常需要重视的事情。
去年,为了进入一线大厂去做更有挑战的事情,拿到更高的薪资,我提前准备了半年的时间,沉淀了一份 「两年磨一剑」 的体系化精品面试题,而后的半年,我都在不断地进行面试,总共面试了二三十家公司,每一场面试完之后,我都将对应的面试题和详细的答案进行了系统化的总结,并更新到了我的面试项目里,现在,在每一个模块之下,我都已经精心整理出了 超高频和高频的常考 知识点。
在我近一年的大厂实战面试复盘中逐渐对原本的内容进行了大幅度的优化,并且新增了很多新的内容。它可以说是一线互联网大厂的面试精华总结,同时后续还会包含如何写简历和面试技巧的内容,能够帮你省时省力地准备面试,大大降低找到一个好工作的难度。
这份面试项目不同于我 Github 上的 Awesome-Android-Interview 面试项目:https://github.com/JsonChao/Awesome-Android-Interview,Awesome-Android-Interview 已经在 2 年前(2020年 10 月停止更新),内容稍显陈旧,里面也有不少点表述不严谨,总体含金量较低。而我今天要分享的这份面试题库,是我在这两年持续总结、细化、沉淀出来的体系化精品面试题,里面很多的核心题答案在面试的压力下,经过了反复的校正与升华,含金量极高。
在分享之前,有一点要注意的是,一定不要将资料泄露出去!细想一下就明白了:
1、如果暴露出去,拿到手的人比你更快掌握,更早进入大厂,拿到高薪,你进大厂的机会就会变小,毕竟现在好公司就那么多,一个萝卜一个坑。
2、两年前我公开分享的简陋版 Awesome-Android-Interview 面试题库现在还在被各个培训机构当做引流资料,加大了现在 Android 内卷。。
所以,这一点一定要切记。
现在,我已经在我的成长社群里修订好了 《体系化高频核心 Android 面试题库》 中的 ”计算机基础高频核心面试题“ 和 ”Java 和 kotlin 高频核心面试题“ 部分,后续还会为你带来我核心题库中的:
获取方法:扫描下方的二维码。
出身普通的人,如何真正改变命运?
这是我过去五、六年一直研究的命题。首先,是为自己研究,因为我是从小城镇出来的,通过持续不断地逆袭立足深圳。越是出身普通的人,就越需要有耐心,去进行系统性地全面提升,这方面,我有非常丰富的实践经验和方法论。因此,我开启了 “JsonChao” 的成长社群,希望和你一起完成系统性地蜕变。
365元每年
每天一元,给自己的成长持续加油
为了回馈 JsonChao 的 CSDN 忠实用户,我申请了少量优惠券,先到者先得,错过再无