Android性能优化-电量优化

硬件消耗电量 来执行任务的过程,叫做超时电流消耗
主要消耗:
1.最大的耗电是我们的屏幕
2.蜂窝式无线数据交换(3G4G)
3.叫醒闹钟 wake lock,AlarmManager,JobSchedulerAPI
4.应用耗电

Paste_Image.png

图中主要是CPU唤醒时的高峰线 可以看到在唤醒的时候电量消耗是非常大的
值得注意的是当工作完成后,设备会主动进行休眠,这非常重要,在不使用或者很少使用的情况下,长时间保持屏幕唤醒会迅速消耗电池的电量。
当设备通过无线网发送数据的时候,为了使用硬件,这里会出现一个唤醒好点高峰。
接下来还有一个高数值,这是发送数据包消耗的电量,
然后接受数据包也会消耗大量电量 也看到一个峰值。
另外WiFi连接下,网络传输的电量消耗要比移动网络少很多,应该尽量减少移动网络下的数据传输,多在WiFi环境下传输数据。

而这次我主要分析的是应用耗电的分析。
使用工具:Battery Historian中的Battery History
地址:https://github.com/google/battery-historian
我们看下安装方式,有两种,一种直接使用Docker,Docker上有我们所需要使用的所有的环境
第二种是自己安装所有的环境。(这里我就不做具体的介绍了,只是将一些细节记录下)

电池数据收集,打开电池数据的获取及重置:
adb shell dumpsys batterystats --enable full-wake-history
adb shell dumpsys batterystats --reset
(主要是为了清除干扰数据,然后做测试,注意将数据线拔掉,防止充放电数据干扰)
对生成的bugReport,然后,我们需要将txt转为html
命令:python historian.py -a bugreport.txt > battery.html
当然要执行这句命令也需要将github上的scripts目录下面,需要下载到命令行所在的目录


Paste_Image.png

命令介绍后,然后我们就可以看到bugreport.txt和battery.html两个文件,使用google浏览器打开html文件:

Paste_Image.png

好,现在就上图做一个简要参数记录:(横向的主要是时间,单位为秒)

battery_level:电池水平,可以根据时间找到对应的电量情况
plugged:插入,就是代表充电的状态,是否连接电源
screen:屏幕,这边代表屏幕是否点亮,可以看到睡眠状态和点亮状态下电量的是用情况
top:上,这边的意思是当前是那个程序在最上层
wake_lock*: 唤醒锁 记录的是wake_lock模块的工作时间
running:运行,界面的状态,主要判断是否处于idle的状态。用来判断无操作状态下电量的消耗。
wake_lock_in: 唤醒锁,wake_lock有不同的组件,这个地方记录在某一个时刻,有哪些部件开始工作,以及工作的时间。
GPS:gps是否开启
Phone in Call:是否进行通话
Sync:同步:可以把鼠标停在某一项上面。可以看到何时sync同步 启动的,持续时间Duration多久。电池容量不会显示单一行为消耗的具体电量,这里只能显示使用电池的频率和时长,你可以看分时段的剩余电量来了解具体消耗了多少电量。
Job:后台的工作,比如服务service的运行。
data_conn:数据连接方式,上面的edge是说明采用的gprs的方式连接网络的。此数据可以看出手机是使用2g,3g,4g还是wifi进行数据交换的。这一栏可以看出不同的连接方式对电量使用的影响。
Status:电池状态,有充电,放电,未充电,已充满,未知等不同状态。 这一栏记录了电池状态的改变信息。
phone_signal_strength:手机信号强弱。 这一栏记录手机信号的强弱变化图,依次来判断手机信号对电量的影响。
health:电池健康状态的信息,这个信息一定程度上反映了这块电池使用了多长时间。
这一栏记录电池状态在何时发生改变,上面的图中电池状态一直处于good状态。
plug:充电方式,usb或者插座,以及显示连接的时间。 这一栏显示了不同的充电方式对电量使用的影响。

---------------------------------------------分割线---------------------------------------
我们可以这样来获取手机充电状态:

private boolean checkForPower() {
    // 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);

    // There are currently three ways a device can be plugged in. We should check them all.
    int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
    boolean usbCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_USB);
    boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC);
    boolean wirelessCharge = false;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        wirelessCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS);
    }
    return (usbCharge || acCharge || wirelessCharge);
}

BatteryManager.BATTERY_PLUGGED_AC这个属性代表的是交流充电
BatteryManager.BATTERY_PLUGGED_USB 代表USB充电
BatteryManager.BATTERY_PLUGGED_WIRELESS代表无线充电
我们很多耗电的操作可以考虑放到充电的时候去执行

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

保持常亮有这几种方式:

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)//保持常亮
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)//清理常亮

或者:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true">
  ///
</RelativeLayout>

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

<uses-permission android:name="android.permission.WAKE_LOCK" />//权限


PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag");
wakeLock.acquire();

//wakeLock.release();//记得执行完就释放锁,不然一直保持CPU唤醒也是十分耗电的

这样可以完成保持CPU可以持续唤醒状态但还有一种更好的方式,是使用WakefulBroadcastReceiver;
WakefulBroadcastReceiver是BroadcastReceiver的一种特例。它会为你的APP创建和管理一个PARTIAL_WAKE_LOCK 类型的WakeLock。WakefulBroadcastReceiver把工作交接给service(通常是IntentService),并保证交接过程中设备不会进入休眠状态。如果不持有WakeLock,设备很容易在任务未执行完前休眠。最终结果是你的应用不知道会在什么时候能把工作完成,相信这不是你想要的。

<receiver android:name=".MyWakefulReceiver"></receiver>
//使用startWakefulService()方法来启动服务和唤醒锁
public class MyWakefulReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
         Intent service = new Intent(context, MyIntentService.class);
         startWakefulService(context, service);
    }
}

public class MyIntentService extends IntentService {
    public MyIntentService() {
        super("MyIntentService");
    }

@Override    
    protected void onHandleIntent(Intent intent) {        
         Bundle extras = intent.getExtras(); 
        // 执行耗时任务
        ... 

        // 结束任务时释放唤醒锁
        MyWakefulReceiver.completeWakefulIntent(intent);    
    }
}

好,大概了解下WakefulBroadcastReceiver源码;

Paste_Image.png
Paste_Image.png

我们可以看到这两个静态方法,其实就是一个唤醒锁的使用和释放。
采用定时重复的Service开启:
1、利用Android自带的定时器AlarmManager实现

Intent intent = new Intent(mContext, ServiceTest.class);
PendingIntent pi = PendingIntent.getService(mContext, 1, intent, 0);
AlarmManager alarm = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
if(alarm != null)
{
    alarm.cancel(pi);    // 闹钟在系统睡眠状态下会唤醒系统并执行提示功能
    alarm.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, 2000, pi);// 确切的时间钟
    //alarm.setExact(…);
    //alarm.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), pi);
}

AlarmManager为什么睡眠状态下还能唤醒呢
首先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。
1.关键逻辑的执行过程,就需要Wake Lock来保护。如断线重连重新登陆
2.休眠的情况下如何唤醒来执行任务?用AlarmManager。如推送消息的获取

JobScheduler
先简单介绍使用吧,首先要写一个Service 继承JobService,就需要实例化两个方法onStartJob和onStopJob;

public class JobSchedulerService extends JobService {
 @Override
    public boolean onStartJob(JobParameters params) {
        if (isNetworkConnected()) {
            new SimpleDownloadTask() .execute(params);
            return true;
        } else {
            Log.i(LOG_TAG, "No connection on job " + params.getJobId() + "; sad face");
        }
        return false;
    }
    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }


  private class SimpleDownloadTask extends AsyncTask<JobParameters, Void, String> {

        protected JobParameters mJobParam;

        @Override
        protected String doInBackground(JobParameters... params) {
            mJobParam = params[0];
            try {
                InputStream is = null;
                int len = 50;
                URL url = new URL("https://www.baidu.com");
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(10000); //10sec
                conn.setConnectTimeout(15000); //15sec
                conn.setRequestMethod("GET");
                conn.connect();
                int response = conn.getResponseCode();
                Log.d(LOG_TAG, "The response is: " + response);
                is = conn.getInputStream();
                Reader reader = null;
                reader = new InputStreamReader(is, "UTF-8");
                char[] buffer = new char[len];
                reader.read(buffer);
                return new String(buffer);
            } catch (IOException e) {
                return "Unable to retrieve web page.";
            }
        }
    
        @Override
        protected void onPostExecute(String result) {
            jobFinished(mJobParam, false);
            Log.i(LOG_TAG, result);
        }
    }
}
 public void execute(View view) {
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        
          JobInfo jobInfo = new JobInfo.Builder(i,serviceComponent)
                    .setMinimumLatency(5000)//5秒 最小延时、
                    .setOverrideDeadline(60000)//maximum最多执行时间
//                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)//免费的网络---wifi 蓝牙 USB
                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)//任意网络---
//           设置重试/退避策略,当一个任务调度失败的时候执行什么样的测量采取重试。
//           initialBackoffMillis:第一次尝试重试的等待时间间隔ms
//           backoffPolicy:对应的退避策略。比如等待的间隔呈指数增长。
//                    .setBackoffCriteria(long initialBackoffMillis, int backoffPolicy)
                    .setBackoffCriteria(JobInfo.MAX_BACKOFF_DELAY_MILLIS, JobInfo.BACKOFF_POLICY_LINEAR)
//                    .setPeriodic (long intervalMillis)//设置执行周期,每隔一段时间间隔任务最多可以执行一次。
//                    .setPeriodic(long intervalMillis,long flexMillis)//在周期执行的末端有一个flexMiliis长度的窗口期,任务就可以在这个窗口期执行。
//                    .setPersisted(boolean isPersisted); //设置设备重启后,这个任务是否还要保留。需要权限:RECEIVE_BOOT_COMPLETED //ctrl+shift+y/u x
//                    .setRequiresCharging(boolean )//是否需要充电
//                    .setRequiresDeviceIdle(boolean)//是否需要等设备出于空闲状态的时候
//                    .addTriggerContentUri(uri)//监听uri对应的数据发生改变,就会触发任务的执行。
//                    .setTriggerContentMaxDelay(long duration)//设置Content发生变化一直到任务被执行中间的最大延迟时间
//                    .setTriggerContentUpdateDelay(long durationMilimms)//设置Content发生变化一直到任务被执行中间的延迟。如果在这个延迟时间内content发生了改变,延迟时间会重写计算。
                    .BUILD();
            jobScheduler.schedule(jobInfo);
}

这段代码中,我们可以看到onStartJob中有个返回值,如果方法执行结束,返回false,否则,就返回true,调用jobFinished来告诉Sevice任务执行完了.当然,这个JobScheduler是5.0以后,而5.0以前,使用的是GCM--Google Cloud Messaging。

其实,现在我们去分析下 JobScheduler,
首先由这样几个文件
JobScheduler.java
->JobSchedulerImpl.java
JobSchedulerService.java
-> JobSchedulerStub schedule()
IJobScheduler.aidl cancel() cancelAll() schedule() getAllPendingJobs()

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • 基于V2.0版本的battery historian请先看 battery historian安装与使用 (1)....
    香沙小熊阅读 1,407评论 0 7
  • 一、了解电量消耗 在电子编程世界,这种硬件消耗电量 来执行任务的过程,叫做超时电流消耗。耗电情况,例如:打开屏幕,...
    Ayres阅读 1,137评论 0 1
  • WakeLock锁 wake_lock锁主要是相对系统的休眠而言的,意思就是我的程序给CPU加了这个锁那系统就不会...
    左上偏右阅读 7,694评论 0 13
  • 【萧红产下的男婴三天后死亡,从女友白朗家回重庆时,笑着对白朗说:“我将孤寂忧郁以终生。”】 历史上,优秀的女作家,...
    玉飞飞阅读 299评论 0 1
  • 关键词:不卑不亢题主:女问:冷爱你好!一直觉得自己挺幸福的,有个各方面都还不错的老公,十年恋爱修成正果,双方都觉得...
    冷爱阅读 491评论 0 0