Android性能优化篇之电量优化(2)

image

引言

1. Android性能优化篇之内存优化--内存泄漏

2.Android性能优化篇之内存优化--内存优化分析工具

3.Android性能优化篇之UI渲染性能优化

4.Android性能优化篇之计算性能优化

5.Android性能优化篇之电量优化(1)——电量消耗分析

6.Android性能优化篇之电量优化(2)

7.Android性能优化篇之网络优化

8.Android性能优化篇之Bitmap优化

9.Android性能优化篇之图片压缩优化

10.Android性能优化篇之多线程并发优化

11.Android性能优化篇之数据传输效率优化

12.Android性能优化篇之程序启动时间性能优化

13.Android性能优化篇之安装包性能优化

14.Android性能优化篇之服务优化

介绍

这篇我们来分析电量消耗快的原因,电量优化的方案。讲之前我们先来了解下如何保持设备唤醒状态?

一.如何保持设备唤醒状态?

当Android设备空闲时,屏幕会变暗,然后关闭屏幕,最后会停止CPU的运行,这样可以防止电池电量掉的快。在休眠过程中自定义的Timer、Handler、Thread、Service等都会暂停。

什么情况下需要唤醒设备?

对于一些带通讯功能的应用,通讯的心跳包会在熄屏不久后停止网络访问,所以需要定时唤醒cpu。
后台关键逻辑代码执行时,防止cpu休眠
后台长连接的状态
后台定时任务执行

下面我们来看下常用的唤醒设备的方法有哪些
1.保持屏幕常亮

有两种方式:

(1).activity中设置
    //保持屏幕常亮
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    //取消屏幕常亮
    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
(2).配置文件中设置
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_screen_on"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:keepScreenOn="true">

这个方法的好处是不像唤醒锁(wake locks),需要一些特定的权限(permission)。并且能正确管理不同app之间的切换,不用担心无用资源的释放问题。

2.保持CPU运行(wake locks)

ake_lock锁主要是相对系统的休眠而言的,意思就是我的程序给CPU加了这个锁那系统就不会休眠了,这样做的目的是为了全力配合我们程序的运行。
需要使用PowerManager这个系统服务的唤醒锁(wake locks)特征来保持CPU处于唤醒状态。唤醒锁允许程序控制宿主设备的电量状态。创建和持有唤醒锁对电池的续航有较大的影响,所以,除非是真的需要唤醒锁完成尽可能短的时间在后台完成的任务时才使用它。

使用场景

在使用后台服务在屏幕关闭情况下hold住CPU完成一些工作。

不使用wake locks,执行长时间任务可能导致的问题?

如果不使用唤醒锁来执行后台服务,不能保证因CPU休眠未来的某个时刻任务会停止

唤醒锁可划分为并识别四种用户唤醒锁:
image1.png
注意: android sdk 大于17 后,FULL_WAKE_LOCK 将被弃用。 应用应使用 FLAG_KEEP_SCREEN_ON。
wake locks 使用步骤
(1).添加唤醒锁权限
    <uses-permission android:name="android.permission.WAKE_LOCK" />
(2).使用
    PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
    PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "myPartialWakeLock");
    //唤醒
    wakeLock.acquire();
    //执行任务
    doJob();
    //释放锁
    if (wakeLock.isHeld()) {
        wakeLock.release();
    }
注意:在使用该类的时候,必须保证acquire和release是成对出现的。

我们也可以使用带超时的acquire,防止没有手动释放

    //带超时的唤醒锁,超时后自动释放锁
    wakeLock.acquire(timeout);
WakefulBroadcastReceiver

官方推荐使用WakefulBroadcastReceiver。下面来看下定义和使用。
WakefulBroadcastReceiver是BroadcastReceiver的一种特例。它会为你的APP创建和管理一个PARTIAL_WAKE_LOCK 类型的WakeLock。WakefulBroadcastReceiver把工作交接给service(通常是IntentService),并保证交接过程中设备不会进入休眠状态。如果不持有WakeLock,设备很容易在任务未执行完前休眠。最终结果是你的应用不知道会在什么时候能把工作完成,相信这不是你想要的。

WakefulBroadcastReceiver使用:
(1).注册
    <receiver android:name=".battery.MyWakefulReceiver"></receiver>
(2).使用
    public class MyWakefulReceiver extends WakefulBroadcastReceiver {       
        @Override
        public void onReceive(Context context, Intent intent) {
            Intent mIntent = new Intent(context,MyService.class);
            startWakefulService(context,mIntent);
        }
    }
    public class MyService extends IntentService {
        /**
         * Creates an IntentService.  Invoked by your subclass's constructor.
         *
         * @param name Used to name the worker thread, important only for debugging.
         */
        public MyService(String name) {
            super(name);
        }

        @Override
        protected void onHandleIntent(Intent intent) {
            Bundle extras = intent.getExtras();
            //执行任务
            doJob();        
            MyWakefulReceiver.completeWakefulIntent(intent);
        }
3.AlarmManager 唤醒CPU
为什么AM能在cpu休眠时,唤醒它呢?

首先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。

使用:
    public void btn_alarm(View view){
        Intent mIntent = new Intent(view.getContext(),TestService.class);
        PendingIntent pendingIntent = PendingIntent.getService(view.getContext(),mRequestCode,mIntent,mFlags);
        AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
        if(alarmManager != null){
            long mTriggerTime = System.currentTimeMillis() + 1000;
            long mItervalTime = 2000;
            alarmManager.cancel(pendingIntent);
            //闹钟在系统睡眠状态下会唤醒系统并执行提示功能
            alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,mTriggerTime,mItervalTime,pendingIntent);
//            alarmManager.setExact(AlarmManager.RTC_WAKEUP,mTriggerTime,pendingIntent);
//            alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), pendingIntent);
        }
    }

该定时器可以启动Service服务、发送广播、跳转Activity,并且会在系统睡眠状态下唤醒系统。所以该方法不用获取电源锁和释放电源锁。

注意:在19以上版本,setRepeating中设置的频繁只是建议值(6.0 的源码中最小值是60s),如果要精确一些的用setWindow或者setExact。
注意:由于手机厂商做了心跳对齐,所有的app后台唤醒频率不能太高,不然会无效。
总结:
1.关键逻辑的执行过程,就需要Wake Lock来保护。如断线重连重新登陆
2.休眠的情况下如何唤醒来执行任务?用AlarmManager。如推送消息的获取
注意:如果请求网络很差,会要很长的时间,一般我们谷歌建议一定要设置请求超时时间。

二.电量优化的一些建议

1.充电时执行任务

为了省电,有些工作可以放当手机插上电源的时候去做。往往这样的情况非常多。像这些不需要及时地和用户交互的操作可以放到后面处理。

    if (!checkForPower()) {
        Toast.makeText(view.getContext(), "当前非充电状态", Toast.LENGTH_SHORT).show();
        return;
    }
    /**
         * 是否充电
         * AC --- 交流电
         * USB
         * WireLess -- 无线充电
         *
         * @return
         */
        private boolean checkForPower() {
            IntentFilter mIntentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
            Intent intent = registerReceiver(null, mIntentFilter);
            int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);

            boolean isUsb = plugged == BatteryManager.BATTERY_PLUGGED_USB;
            boolean isAc = plugged == BatteryManager.BATTERY_PLUGGED_AC;
            boolean isWireless = false;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                //api >= 17
                isWireless = plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
            }
            return (isUsb || isAc || isWireless);
        }
2.连接Wifi后执行任务

我们知道wifi网络传输的电量消耗要比移动网络少很多,应该尽量减少移动网络下的数据传输,多在WiFi环境下传输数据,所以我们可以把一些不需要实时性的任务留到连接wifi后在执行

3.wake_lock

系统为了节省电量,CPU在没有任务忙的时候就会自动进入休眠。
有任务需要唤醒CPU高效执行的时候,就会给CPU加wake_lock锁。
但是根据我们上面的讲解,使用wake_lock结束时需要释放锁,如果忘记释放,会使得CPU一直执行消耗电量,所以推荐使用带超时的wake lock或者WakefulBroadcastReceiver

    wakeLock.acquire(timeout);
4.大量高频次的CPU唤醒及操作集中处理

我们希望把频繁的间隔任务集中起来进行批量执行, 这正是JobScheduler API所做的事情。它会根据当前的情况与任务,组合出理想的唤醒时间,例如等到正在充电或者连接到WiFi的时候,或者集中任务一起执行。我们可以通过这个API实现很多免费的调度算法。


image3.png
image2.png
JobScheduler

使用Job Scheduler,应用需要做的事情就是判断哪些任务是不紧急的,可以交给Job Scheduler来处理,Job Scheduler集中处理收到的任务,选择合适的时间,合适的网络,再一起进行执行。
使用(android api >= 21)

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public class JobWakeUpService extends JobService{
        private int mJobID = 100;
        private JobScheduler mJobScheduler;
        private long mIntervalMillis = 6000;
        private long mMinLatencyMillis = 5000;
        private long mMaxExecutionDelayMillis = 10000;

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            JobInfo mJobInfo = new JobInfo.Builder(mJobID,new ComponentName(this,JobWakeUpService.class))
                    .setPeriodic(mIntervalMillis)//设备重启,任务是否保留
                    .setMinimumLatency(mMinLatencyMillis)//最小延时
                    .setOverrideDeadline    (mMaxExecutionDelayMillis)//最大执行时间
                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)//网络类型 NETWORK_TYPE_UNMETERED(wifi 蓝牙)
                    .setRequiresCharging(true) //充电时执行
                    //设置重试/退避策略 (重试时间,重试时间间隔)
                    .setBackoffCriteria(mInitialBackoffMillis,JobInfo.BACKOFF_POLICY_LINEAR)
                    .build();

            mJobScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
            mJobScheduler.schedule(mJobInfo);
            return START_STICKY;
        }

        @Override
        public boolean onStartJob(JobParameters params) {
            if(!isServiceWork(this,TestService.class.getName())){
                startService(new Intent(this,TestService.class));
            }
            return false;
        }

        @Override
        public boolean onStopJob(JobParameters params) {
            mJobScheduler.cancel(mJobID);
    //        mJobScheduler.cancelAll();
            return false;
        }

        /**
         * 查询服务是否开启
         * @param context
         * @param serviceName
         * @return
         */
        private boolean isServiceWork(Context context, String serviceName){
            ActivityManager am= (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            List<ActivityManager.RunningServiceInfo> runningServices = am.getRunningServices(100);
            if(runningServices == null){
                return false;
            }
            for (ActivityManager.RunningServiceInfo service : runningServices) {
                String className = service.service.getClassName();
                if(className.equals(serviceName)){
                    return true;
                }
            }
            return false;
        }
5.定位

定位完成,及时关闭
如果需要实时定位,减少更新频率
根据实际情况,选择gsp定位还是网络定位,降低电量消耗

6.网络优化

手机的通过内置的射频模块和基站几乎, 从而链接上网的, 而这个射频模块(radio)是非常耗电的.
为了控制这个射频模块的耗电, 硬件驱动及Android RIL层做了很多处理. 例如可以单独关闭radio(飞行模式), 间歇性假休眠radio(有数据发生时才上电, 保持一个频率的与基站交互)等等.
具体的优化请看关于网络优化的文章。

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

推荐阅读更多精彩内容