Android性能优化之电量
Understanding Battery Drain (了解电量消耗)
手机各个硬件模块的耗电量是不一样的,有些模块非常耗电,而有些模块则相对显得耗电量小很多。
电量消耗的计算与统计是一件麻烦而且矛盾的事情,记录电量消耗本身也是一个费电量的事情。唯一可行的方案是使用第三方监测电量的设备,这样才能够获取到真实的电量消耗。
Battery Historian
https://developer.android.com/about/versions/android-5.0.html#Power
除了提供新功能外,Android 5.0 还重视电池寿命的改善。可以利用新增的 API 和工具来了解和优化您的应用的功耗。
Android 5.0 新增了一个 JobScheduler API,允许您定义一些系统在稍后或指定条件下(如设备充电时)以异步方式运行的作业,从而优化电池寿命。下列情形下,作业计划排定功能很有用:
-
应用具有不面向用户并且可以推迟的作业
-
应用具有您更愿意在设备插入电源时再进行的作业
-
应用具有一项需要接入网络或连接 WLAN 的任务。
-
应用具有多项您希望定期以批处理方式运行的任务。
一个作业单位由一个 JobInfo 对象封装。该对象指定计划排定标准。
使用 JobInfo.Builder 类可配置应如何运行已排计划的任务。您可以安排任务在特定条件下运行,例如:
-
在设备充电时启动
-
在设备连入无限流量网络时启动
-
在设备空闲时启动
-
在特定期限前或以最低延迟完成
例如,您可以添加一段如下代码,在无限流量网络上运行您的任务:
新增的 dumpsys batterystats 命令可生成值得关注的设备电池使用情况统计数据,这些数据按唯一身份用户 ID (UID) 加以组织
Install Docker
https://github.com/google/battery-historian
-
install Docker
-
docker run -p 6666:9999 blystad/battery-historian --port 9999
-
$ adb shell dumpsys batterystats > xxx.txt //得到整个设备的电量消耗信息
-
$ adb shell dumpsys batterystats > com.package.name > xxx.txt //得到指定app相关的电量消耗信息
-
上传服务器上去分析
http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/index.html
Battery Drain and Networking
对于手机程序,网络操作相对来说是比较耗电的行为。优化网络操作能够显著节约电量的消耗。在性能优化第1季里面有提到过,手机硬件的各个模块的耗电量是不一样的,其中移动蜂窝模块对电量消耗是比较大的,另外蜂窝模块在不同工作强度下,对电量的消耗也是有差异的。
从图示中可以看到,激活瞬间,发送数据的瞬间,接收数据的瞬间都有很大的电量消耗,所以,我们应该从如何传递网络数据以及何时发起网络请求这两个方面来着手优化。
何时发起网络请求
首先我们需要区分哪些网络请求是需要及时返回结果的,哪些是可以延迟执行的。例如,用户主动下拉刷新列表,这种行为需要立即触发网络请求,并等待数据返回。但是对于上传用户操作的数据,同步程序设置等等行为则属于可以延迟的行为。我们可以通过Battery Historian这个工具来查看关于移动蜂窝模块的电量消耗(关于这部分的细节,请点击Android性能优化之电量篇)。在Mobile Radio那一行会显示蜂窝模块的电量消耗情况,红色的部分代表模块正在工作,中间的间隔部分代表模块正在休眠状态,如果看到有一段区间,红色与间隔频繁的出现,那就说明这里有可以优化的行为。
对于上面可以优化的部分,我们可以有针对性的把请求行为捆绑起来,延迟到某个时刻统一发起请求。
如何传递网络数据
关于这部分主要会涉及到Prefetch(预取)与Compressed(压缩)这两个技术。对于Prefetch的使用,我们需要预先判断用户在此次操作之后,后续零散的请求是否很有可能会马上被触发,可以把后面5分钟有可能会使用到的零散请求都一次集中执行完毕。对于Compressed的使用,在上传与下载数据之前,使用CPU对数据进行压缩与解压,可以很大程度上减少网络传输的时间。
想要知道我们的应用程序中网络请求发生的时间,每次请求的数据量等等信息,可以通过Android Studio中的Networking Traffic Tool来查看详细的数据。
AlarmManager JobScheduler PowerManager
电量消耗的全过程分析
耗电情况
-
打开屏幕 所有要使用CPU/GPU工作的动作都会唤醒屏幕,都会消耗电量。
-
应用程序唤醒设备 比如使用 叫醒闹钟(wake clock)、AlarmManager、JobSchedulerAPI 。
-
蜂窝式无线也是耗电量非常可怕的,它比WIFI的耗电量还要大。
蜂窝式无线
当设备通过无线网发送数据的时候,为了使用硬件,这里会出现一个唤醒高峰。接下来还有一个高数值,这是发送数据包消耗的电量,然后接受数据包也会消耗大量电量也看到一个峰值。开启无线模式这个过程非常耗电,那么硬件这块为了防止频繁开启关闭耗电,采取了一个无奈的办法,会在一个小段时间内保持开启模式,防止短时间内还有数据包需要接收。
值得注意的是当工作完成后,设备会主动进行休眠,这非常重要,在不使用或者很少使用的情况下,长时间保持屏幕唤醒会迅速消耗电池的电量。
监控电量状态
可以让一些操作等到充电的情况下再来做,例如上传一些用户本地的数据等等。
管理设备的唤醒状态
http://hukai.me/android-training-course-in-chinese/background-jobs/scheduling/index.html
当一个Android设备闲置时,首先它的屏幕将会变暗,然后关闭屏幕,最后关闭CPU。 这样可以防止设备的电量被迅速消耗殆尽。但是,有时候也会存在一些特例:
-
例如游戏或视频应用或者阅读小说的应用需要保持屏幕常亮;
-
其它应用也许不需要屏幕常亮,但或许会需要CPU保持运行,直到某个关键操作结束。例如音乐播放器,后台下载等等。
保持设备唤醒
为了避免电量过度消耗,Android设备会在被闲置之后迅速进入睡眠状态。然而有时候应用会需要唤醒屏幕或者是唤醒CPU并且保持它们的唤醒状态,直至一些任务被完成。
想要做到这一点, 所采取的方法依赖于应用的具体需求 。但是通常来说,我们应该使用最轻量级的方法,减小其对系统资源的影响。在接下来的部分中,我们将会描述在设备默认的睡眠行为与应用的需求不相符合的情况下,我们应该如何进行对应的处理。
仅仅保持屏幕常亮
某些应用需要保持屏幕常亮,比如游戏与视频应用。最好的方式是在你的Activity中(且仅在Activity中,而不是在Service或其他应用组件里)使用FLAG_KEEP_SCREEN_ON属性 ,例如:
该方法的优点与唤醒锁(Wake Locks)不同(唤醒锁的内容在本章节后半部分),它不需要任何特殊的权限,系统会正确地管理应用之间的切换,且不必关心释放资源的问题。
另外一种方法是在应用的XML布局文件里,使用android:keepScreenOn属性:
使用android:keepScreenOn="true"与使用FLAG_KEEP_SCRRE_ON等效。你可以选择最适合你的应用的方法。在Activity中通过代码设置常亮标识的优点在于:你可以通过代码动态清除这个标示,从而使屏幕可以关闭。
除非你不再希望正在运行的应用长时间点亮屏幕(例如:在一定时间无操作发生后,你想要将屏幕关闭),否则你是不需要清除FLAG_KEEP_SCRRE_ON 标识的。WindowManager会在应用进入后台或者返回前台时,正确管理屏幕的点亮或者关闭。但是如果你想要显式地清除这一标识,从而使得屏幕能够关闭,可以使用getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)方法。
注意:一般不需要人为的去掉FLAG_KEEP_SCREEN_ON的flag,windowManager会管理好程序进入后台回到前台的的操作。如果确实需要手动清掉常亮的flag,使用getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
这种方法是仅仅保持屏幕常量,例如你正在播放视频,正在看小说,或者这个页面仅仅是有一个二维码需要长期点亮这个页面,这个方法不需要权限,也不需要获取CPU的资源锁什么的,是最轻量级的点亮屏幕的方式。
保持CPU运行
如果你需要在设备睡眠之前,保持CPU运行来完成一些工作,你可以使用PowerManager系统服务中的唤醒锁功能。唤醒锁允许应用控制设备的电源状态。
创建和保持唤醒锁会对设备的电源寿命产生巨大影响。因此你应该仅在你确实需要时使用唤醒锁,且使用的时间应该越短越好。 如果想要在Activity中使用唤醒锁就显得没有必要了。如上所述,可以在Activity中使用FLAG_KEEP_SCRRE_ON让屏幕保持常亮。
使用唤醒锁的一种合理情况可能是: 一个后台服务需要在屏幕关闭后利用唤醒锁保持CPU运行。再次强调,应该尽可能规避使用该方法,因为它会影响到电池寿命 。
不必使用唤醒锁的情况:
-
如果你的应用正在执行一个HTTP长连接的下载任务,可以考虑使用DownloadManager。
-
如果你的应用正在从一个外部服务器同步数据,可以考虑创建一个SyncAdapter
-
如果你的应用需要依赖于某些后台服务,可以考虑使用RepeatingAlarm或者Google Cloud Messaging,以此每隔特定的时间,将这些服务激活。
为了使用唤醒锁,首先需要在应用的Manifest清单文件中增加WAKE_LOCK权限:
如果你的应用包含一个BroadcastReceiver并使用Service来完成一些工作,你可以通过 WakefulBroadcastReceiver 管理你唤醒锁。后续章节中将会提到,这是一种推荐的方法。如果你的应用不满足上述情况,可以使用下面的方法直接设置唤醒锁:
可以调用wakelock.release()来释放唤醒锁。当应用使用完毕时,应该释放该唤醒锁,以避免电量过度消耗。
wake_lock锁主要是相对系统的休眠而言的,意思就是我的程序给CPU加了这个锁那系统就不会休眠了,这样做的目的是为了全力配合我们程序的运行。有的情况如果不这么做就会出现一些问题,比如微信等及时通讯的心跳包会在熄屏不久后停止网络访问等问题。所以微信里面是有大量使用到了wake_lock锁。
wake_lock:两种锁,一种计数锁;非计数锁(锁了很多次,只需要release一次就可以解除了) 源码:count++
有的人可能认为我以前写的后台服务就没掉过链子呀运行得挺好的,
-
可能是你的任务时间比较短;
-
可能CPU被手机里面很多其他的软件一直在唤醒状态。
使用WakefulBroadcastReceiver
你可以将BroadcastReceiver和Service结合使用,以此来管理后台任务的生命周期。WakefulBroadcastReceiver是一种特殊的BroadcastReceiver,它专注于创建和管理应用的PARTIAL_WAKE_LOCK(唤醒锁定级别:确保CPU正在运行;屏幕和键盘背光将被允许熄灭)。 WakefulBroadcastReceiver会将任务交付给Service(一般会是一个IntentService),同时确保设备在此过程中不会进入睡眠状态 。如果在该过程当中没有保持住唤醒锁,那么还没等任务完成,设备就有可能进入睡眠状态了。其结果就是:应用可能会在未来的某一个时间节点才把任务完成,这显然不是你所期望的。
要使用WakefulBroadcastReceiver,首先在Manifest文件添加一个标签:
下面的代码通过startWakefulService()启动MyIntentService。该方法和startService()类似,除了 WakeflBroadcastReceiver会在Service启动后将唤醒锁保持住。传递给startWakefulService()的Intent会携带有一个Extra数据,用来标识唤醒锁。
当Service结束之后,它会调用MyWakefulReceiver.completeWakefulIntent()来释放唤醒锁。completeWakefulIntent()方法中的Intent参数是和WakefulBroadcastReceiver传递进来的Intent参数一致的:
制定重复定时任务
闹钟(基于AlarmManager类)给予你一种在应用使用期之外执行与时间相关的操作的方法 。你可以使用闹钟初始化一个长时间的操作,例如每天开启一次后台服务,下载当日的天气预报。
闹钟具有如下特性:
-
允许你通过预设时间或者设定某个时间间隔,来触发Intent;
-
你可以将它与BroadcastReceiver相结合,来启动服务并执行其他操作;
-
可在应用范围之外执行,所以你可以在你的应用没有运行或设备处于睡眠状态的情况下,使用它来触发事件或行为;
-
帮助你的应用最小化资源需求,你可以使用闹钟调度你的任务,来替代计时器或者长时间连续运行的后台服务。
对于那些需要确保 在应用使用期之内发生的定时操作 ,可以使用闹钟替代 使用Handler结合Timer与Thread的方法 。因为它可以让Android系统更好地统筹系统资源。
对于定时操作,在应用使用期间用Timer和Handler的方案,在应用已经关闭的情况下,用AlarmMananger。
最佳实践方法
在设计重复闹钟过程中,你所做出的每一个决定都有可能影响到你的应用将会如何使用系统资源。例如,我们假想一个会从服务器同步数据的应用。 同步操作基于的是时钟时间,具体来说,每一个应用的实例会在下午十一点整进行同步,巨大的服务器负荷会导致服务器响应时间变长,甚至拒绝服务 。因此在我们使用闹钟时,请牢记下面的最佳实践建议:
-
对任何由 重复闹钟触发的网络请求添加一定的随机性 (抖动):
-
在闹钟触发时做一些本地任务。“本地任务”指的是任何不需要访问服务器或者从服务器获取数据的任务;
-
同时对于那些包含有网络请求的闹钟,在调度时机上增加一些随机性。
-
-
尽量让你的闹钟频率最小;
-
如果不是必要的情况,不要唤醒设备(这一点与闹钟的类型有关,本节课后续部分会提到);
-
触发闹钟的时间不必过度精确; 尽量使用setInexactRepeating()方法替代setRepeating()方法。 当你使用setInexactRepeating()方法时,Android系统会集中多个应用的重复闹钟同步请求,并一起触发它们。这可以减少系统将设备唤醒的总次数,以此减少电量消耗。从Android 4.4(API Level19)开始,所有的重复闹钟都将是非精确型的。注意虽然setInexactRepeating()是setRepeating()的改进版本,它依然可能会导致每一个应用的实例在某一时间段内同时访问服务器,造成服务器负荷过重。因此如之前所述,对于网络请求,我们需要为闹钟的触发时机增加随机性。
-
尽量避免让闹钟基于时钟时间。
想要在某一个精确时刻触发重复闹钟是比较困难的。我们应该尽可能使用ELAPSED_REALTIME。不同的闹钟类型会在本节课后半部分展开。
设置重复闹钟
如上所述, 对于定期执行的任务或者数据查询而言,使用重复闹钟是一个不错的选择 。它具有下列属性:
-
闹钟类型(后续章节中会展开讨论);
-
触发时间。如果触发时间是过去的某个时间点,闹钟会立即被触发;
-
闹钟间隔时间。例如,一天一次,每小时一次,每五秒一次,等等;
-
在闹钟被触发时才被发出的Pending Intent。如果你为同一个Pending Intent设置了另一个闹钟,那么它会将第一个闹钟覆盖。
选择闹钟类型
使用重复闹钟要考虑的第一件事情是闹钟的类型。
闹钟类型有两大类: ELAPSED_REALTIME 和 REAL_TIME_CLOCK(RTC) 。ELAPSED_REALTIME从系统启动之后开始计算,REAL_TIME_CLOCK使用的是世界统一时间(UTC)。也就是说由于ELAPSED_REALTIME不受地区和时区的影响,所以它适合于基于时间差的闹钟(例如一个每过30秒触发一次的闹钟)。REAL_TIME_CLOCK适合于那些依赖于地区位置的闹钟。
两种类型的闹钟都还有一个唤醒(WAKEUP)版本,也就是可以在设备屏幕关闭的时候唤醒CPU。 这可以确保闹钟会在既定的时间被激活,这对于那些实时性要求比较高的应用(比如含有一些对执行时间有要求的操作)来说非常有效。 如果你没有使用唤醒版本的闹钟,那么所有的重复闹钟会在下一次设备被唤醒时被激活。
也就是说,如果你使用了带有WAKEUP功能闹钟,那么不管应用有没有在运行期间,它都能把应用唤起,如果没有使用带有WAKEUP功能的闹钟,那么它不会唤起应用,而是会在应用被启动之后才会被激活这个闹钟。
如果你只是简单的希望闹钟在一个特定的时间间隔被激活(例如每半小时一次),那么你可以使用任意一种ELAPSED_REALTIME类型的闹钟,通常这会是一个更好的选择。
如果你的闹钟是在每一天的特定时间被激活,那么你可以选择REAL_TIME_CLOCK类型的闹钟。不过需要注意的是,这个方法会有一些缺陷——如果地区发生了变化,应用可能无法做出正确的改变;另外,如果用户改变了设备的时间设置,这可能会造成应用产生预期之外的行为。使用REAL_TIME_CLOCK类型的闹钟还会有精度的问题,因此我们建议你尽可能使用ELAPSED_REALTIME类型。
下面列出闹钟的具体类型:
-
ELAPSED_REALTIME:从设备启动之后开始算起,度过了某一段特定时间后,激活Pending Intent,但不会唤醒设备。其中设备睡眠的时间也会包含在内。
-
ELAPSED_REALTIME_WAKEUP:从设备启动之后开始算起,度过了某一段特定时间后唤醒设备。
-
RTC:在某一个特定时刻激活Pending Intent,但不会唤醒设备。
-
RTC_WAKEUP:在某一个特定时刻唤醒设备并激活Pending Intent。
ELAPSED_REALTIME_WAKEUP案例
每隔在30分钟后唤醒设备以激活闹钟:
在一分钟后唤醒设备并激活一个一次性(无重复)闹钟:
RTC_WAKEUP案例
在大约下午2点唤醒设备并激活闹钟,并不断重复:
让设备精确地在上午8点半被唤醒并激活闹钟,自此之后每20分钟唤醒一次:
决定闹钟的精确度
如上所述,创建闹钟的第一步是要选择闹钟的类型,然后你需要决定闹钟的精确度。对于大多数应用而言,setInexactRepeating()会是一个正确的选择。当你使用该方法时,Android系统会集中多个应用的重复闹钟同步请求,并一起触发它们。这样可以减少电量的损耗。
对于另一些实时性要求较高的应用——例如,闹钟需要精确地在上午8点半被激活,并且自此之后每隔1小时激活一次——那么可以使用setRepeating()。不过你应该尽量避免使用精确的闹钟。
使用setRepeating()时,你可以制定一个自定义的时间间隔,但在使用setInexactRepeating()时不支持这么做。此时你只能选择一些时间间隔常量,例如:INTERVAL_FIFTEEN_MINUTES ,INTERVAL_DAY等。完整的常量列表,可以查看AlarmManager。
取消闹钟
你可能希望在应用中添加取消闹钟的功能。要取消闹钟,可以调用AlarmManager的cancel()方法,并把你不想激活的PendingIntent传递进去,例如:
在设备启动后启用闹钟
默认情况下,所有的闹钟会在设备关闭时被取消。要防止闹钟被取消,你可以让你的应用在用户重启设备后自动重启一个重复闹钟。这样可以让AlarmManager继续执行它的工作,且不需要用户手动重启闹钟。
具体步骤如下:
-
在应用的Manifest文件中设置RECEIVE_BOOT_CMPLETED权限,这将允许你的应用接收系统启动完成后发出的ACTION_BOOT_COMPLETED广播(只有在用户至少将你的应用启动了一次后,这样做才有效):
-
实现BroadcastReceiver用于接收广播:
-
在你的Manifest文件中添加一个接收器,其Intent-Filter接收ACTION_BOOT_COMPLETED这一Action:
注意Manifest文件中,对接收器设置了android:enabled="false"属性。这意味着除非应用显式地启用它,不然该接收器将不被调用。这可以防止接收器被不必要地调用。你可以像下面这样启动接收器(比如用户设置了一个闹钟):
一旦你像上面那样启动了接收器,它将一直保持启动状态,即使用户重启了设备也不例外。换句话说,通过代码设置的启用配置将会覆盖掉Manifest文件中的现有配置,即使重启也不例外。接收器将保持启动状态,直到你的应用将其禁用。你可以像下面这样禁用接收器(比如用户取消了一个闹钟):
Wakelock
高效的保留更多的电量与不断促使用户使用你的App会消耗电量,这是矛盾的选择题。不过我们可以使用一些更好的办法来平衡两者。
假设你的手机里面装了大量的社交类应用,即使手机处于待机状态,也会经常被这些应用唤醒用来检查同步新的数据信息。Android会不断关闭各种硬件来延长手机的待机时间,首先屏幕会逐渐变暗直至关闭,然后CPU进入睡眠,这一切操作都是为了节约宝贵的电量资源。但是即使在这种睡眠状态下,大多数应用还是会尝试进行工作,他们将不断的唤醒手机。一个最简单的唤醒手机的方法是使用PowerManager.WakeLock的API来保持CPU工作并防止屏幕变暗关闭。这使得手机可以被唤醒,执行工作,然后回到睡眠状态。知道如何获取WakeLock是简单的,可是及时释放WakeLock也是非常重要的,不恰当的使用WakeLock会导致严重错误。例如网络请求的数据返回时间不确定,导致本来只需要10s的事情一直等待了1个小时,这样会使得电量白白浪费了。这也是为何使用带超时参数的wakelock.acquice()方法是很关键的。
在使用一些产品列如微信、QQ之类的,如果有新消息来时,手机屏幕即使在锁屏状态下也会亮起并提示声音,这时用户就知道有新消息来临了。但是,一般情况 下手机锁屏后,Android系统为了省电以及减少CPU消耗,在一段时间后会使系统进入休眠状态,这时,Android系统中CPU会保持在一个相对较 低的功耗状态。针对前面的例子,收到新消息必定有网络请求,而网络请求是消耗CPU的操作,那么如何在锁屏状态乃至系统进入休眠后,仍然保持系统的网络状 态以及通过程序唤醒手机呢?答案就是Android中的WakeLock机制。 首先看看官方的解释:
PowerManager:This class gives you control of the power state of the device. PowerManager.WakeLock: lets you say that you need to have the device on.
PowerManager 负责对Android设备电源相关进行管理,而系统通过各种锁对电源进行控制,WakeLock是一种锁机制,只要有人拿着这把锁,系统就无法进入休眠阶 段。既然要保持应用程序一直在后台运行,那自然要获得这把锁才可以保证程序始终在后台运行。之前我做过一个需求是要在后台跑一个Service执行轮询, 但发现一段时间以后,轮询就中断了(我测试是二十分钟后请求停止),但重新解锁屏幕后,轮询请求又开始了,后来在Stackoverflow上找到的 WakeLock的用法,试了一下,还挺管用。
有一些意外的情况,比如小米手机是做了同步心跳包(心跳对齐)(如果超过了这个同步的频率就会被屏蔽掉或者降频),所有的app后台唤醒频率不能太高,比如每隔2S中去请求。也就是说,很多应用会有心跳包,如果太频繁会导致电量快速下滑,所以小米手机会有这个限制。
-
PARTIAL_WAKE_LOCK:保持CPU 运转,屏幕和键盘灯有可能是关闭的。
-
SCREEN_DIM_WAKE_LOCK:保持CPU 运转,允许保持屏幕显示但有可能是灰的,允许关闭键盘灯
-
SCREEN_BRIGHT_WAKE_LOCK:保持CPU 运转,允许保持屏幕高亮显示,允许关闭键盘灯
-
FULL_WAKE_LOCK:保持CPU 运转,保持屏幕高亮显示,键盘灯也保持亮度
-
ACQUIRE_CAUSES_WAKEUP:强制使屏幕亮起,这种锁主要针对一些必须通知用户的操作.
-
ON_AFTER_RELEASE:当锁被释放时,保持屏幕亮起一段时间
首先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进入休眠状态,就会成为待机电池杀手。比如前段时间的某应用,比如现在仍然干着这事的某应用。
那么Wake Lock API有啥用呢?比如心跳包从请求到应答,比如断线重连重新登陆这些关键逻辑的执行过程,就需要Wake Lock来保护。而一旦一个关键逻辑执行成功,就应该立即释放掉Wake Lock了。两次心跳请求间隔5到10分钟,基本不会怎么耗电。除非网络不稳定,频繁断线重连,那种情况办法不多。
AlarmManager 是Android 系统封装的用于管理 RTC 的模块,RTC (Real Time Clock) 是一个独立的硬件时钟,可以在 CPU 休眠时正常运行,在预设的时间到达时,通过中断唤醒 CPU。(极光推送就是利用这个来做的。)
Android为了确保应用程序中关键代码的正确执行,提供了WakeLock的API,使得应用程序有权限通过代码阻止AP进入休眠状态。 Wake Lock是一种锁的机制, 只要有人拿着这个锁,系统就无法进入休眠,可以被用户态程序和内核获得. 这个锁可以是有超时的或者是没有超时的,超时的锁会在时间过去以后自动解锁. 如果没有锁了或者超时了, 内核就会启动休眠的那套机制来进入休眠. WakeLock阻止应用处理器(ApplicationProcessor)挂起,确保关键代码的运行,通过中断唤起应用处理器(ApplicationProcessor),可以阻止屏幕变暗。所有的WakeLock被释放后,系统会挂起
WakefulBroadcastReceiver是BroadcastReceiver的一种特例。它会为你的APP创建和管理一个PARTIAL_WAKE_LOCK 类型的WakeLock。WakefulBroadcastReceiver把工作交接给service(通常是IntentService),并保证交接过程中设备不会进入休眠状态。如果不持有WakeLock,设备很容易在任务未执行完前休眠。最终结果是你的应用不知道会在什么时候能把工作完成,相信这不是你想要的。
用广播做这个事的优势是可以解耦。
JobScheduler
但是仅仅设置超时并不足够解决问题,例如设置多长的超时比较合适?什么时候进行重试等等?解决上面的问题,正确的方式可能是使用非精准定时器。通常情况下,我们会设定一个时间进行某个操作,但是动态修改这个时间也许会更好。例如,如果有另外一个程序需要比你设定的时间晚5分钟唤醒,最好能够等到那个时候,两个任务捆绑一起同时进行,这就是非精确定时器的核心工作原理。我们可以定制计划的任务,可是系统如果检测到一个更好的时间,它可以推迟你的任务,以节省电量消耗。
这正是JobScheduler API所做的事情。它会根据当前的情况与任务,组合出理想的唤醒时间,例如等到正在充电或者连接到WiFi的时候,或者集中任务一起执行。我们可以通过这个API实现很多免费的调度算法。
https://developer.android.com/topic/performance/scheduling.html
http://wiki.jikexueyuan.com/project/android-weekly/issue-146/using-jobscheduler.html
dozn android 这个在安卓5.0 以后越来越重视这个性能优化,主要是对网络性能省电优化
这个JobScheduler可以用来搞那个联系人缓存和更新。
当使用Android进行工作时,会遇到这样的情况——你会想在将来的某个时间或在一定条件下运行任务,例如当一个设备接入电源或连接到Wi-Fi网络。值得庆幸的是有API21,因为Android Lollipop而被大多数人所知,谷歌已经提供了被称为JobScheduler API的新组件来处理这样的情况。
当一组预定义的条件得到满足时,JobScheduler API的应用程序执行一项操作。不像 AlarmManager 类,该时间测定时不准确的。此外,该 JobScheduler API 能够一同批处理各种工作。这允许应用程序执行特定的任务,同时考虑设备的电池在定时控制上的成本。
创建Job Service
首先,你需要创建一个API最低为21的Android项目,因此JobScheduler是最近的版本才加入Android的,在写这篇文章的时候,它还没有兼容库支持。
假定你使用的是Android Studio,当你点击了创建项目的完成按钮之后,你会得到一个”hello world”的应用骨架。你要做的第一步就是创建一个新的Java类。为了简单起见,让我们创建一个继承自JobService且名字为JobSchedulerService的类,这个类必须实现两个方法,分别是onStartJob(JobParameters params)和 onStopJob(JobParameters params);
当任务开始时会执行onStartJob(JobParameters params)方法,因为这是系统用来触发已经被执行的任务。正如你所看到的,这个方法返回一个boolean值。如果返回值是false,系统假设这个方法返回时任务已经执行完毕。如果返回值是true,那么系统假定这个任务正要被执行,执行任务的重担就落在了你的肩上。当任务执行完毕时你需要调用jobFinished(JobParameters params, boolean needsRescheduled)来通知系统。
当系统接收到一个取消请求时,系统会调用onStopJob(JobParameters params)方法取消正在等待执行的任务。很重要的一点是如果onStartJob(JobParameters params)返回false,那么系统假定在接收到一个取消请求时已经没有正在运行的任务。换句话说,onStopJob(JobParameters params)在这种情况下不会被调用。
需要注意的是这个job service运行在你的主线程,这意味着你需要使用子线程,handler, 或者一个异步任务来运行耗时的操作以防止阻塞主线程。因为多线程技术已经超出了我们这篇文章的范围,让我们简单实现一个Handlder来执行我们在JobSchedulerService定义的任务吧。
当任务执行完毕之后,你需要调用jobFinished(JobParameters params, boolean needsRescheduled)来让系统知道这个任务已经结束,系统可以将下一个任务添加到队列中。如果你没有调用jobFinished(JobParameters params, boolean needsRescheduled),你的任务只会执行一次,而应用中的其他任务就不会被执行。
jobFinished(JobParameters params, boolean needsRescheduled)的两个参数中的params参数是从JobService的onStartJob(JobParameters params)的params传递过来的,needsRescheduled参数是让系统知道这个任务是否应该在最处的条件下被重复执行。这个boolean值很有用,因为它指明了你如何处理由于其他原因导致任务执行失败的情况,例如一个失败的网络请求调用。
创建一个JobScheduler对象
当你想创建定时任务时,你可以使用JobInfo.Builder来构建一个JobInfo对象,然后传递给你的Service。JobInfo.Builder接收两个参数,第一个参数是你要运行的任务的标识符,第二个是这个Service组件的类名。
-
setMinimumLatency(long minLatencyMillis): 这个函数能让你设置任务的延迟执行时间(单位是毫秒),这个函数与setPeriodic(long time)方法不兼容,如果这两个方法同时调用了就会引起异常;
-
setOverrideDeadline(long maxExecutionDelayMillis):
-
这个方法让你可以设置任务最晚的延迟时间。如果到了规定的时间时其他条件还未满足,你的任务也会被启动。与setMinimumLatency(long time)一样,这个方法也会与setPeriodic(long time),同时调用这两个方法会引发异常。
-
setPersisted(boolean isPersisted): 这个方法告诉系统当你的设备重启之后你的任务是否还要继续执行。
-
setRequiredNetworkType(int networkType):
-
这个方法让你这个任务只有在满足指定的网络条件时才会被执行。默认条件是JobInfo.NETWORK_TYPE_NONE,这意味着不管是否有网络这个任务都会被执行。另外两个可选类型,一种是JobInfo.NETWORK_TYPE_ANY,它表明需要任意一种网络才使得任务可以执行。另一种是JobInfo.NETWORK_TYPE_UNMETERED,它表示设备不是蜂窝网络( 比如在WIFI连接时 )时任务才会被执行。
-
setRequiresCharging(boolean requiresCharging): 这个方法告诉你的应用,只有当设备在充电时这个任务才会被执行。
-
setRequiresDeviceIdle(boolean requiresDeviceIdle): 这个方法告诉你的任务只有当用户没有在使用该设备且有一段时间没有使用时才会启动该任务
需要注意的是setRequiredNetworkType(int networkType), setRequiresCharging(boolean requireCharging) and setRequiresDeviceIdle(boolean requireIdle)者几个方法可能会使得你的任务无法执行,除非调用setOverrideDeadline(long time)设置了最大延迟时间,使得你的任务在为满足条件的情况下也会被执行。一旦你预置的条件被设置,你就可以构建一个JobInfo对象,然后通过如下所示的代码将它发送到你的JobScheduler中。
你可能注意到了,这个schedule方法会返回一个整型。如果schedule方法失败了,它会返回一个小于0的错误码。否则它会我们在JobInfo.Builder中定义的标识id。
如果你的应用想停止某个任务,你可以调用JobScheduler对象的cancel(int jobId)来实现;如果你想取消所有的任务,你可以调用JobScheduler对象的cancelAll()来实现。