美文网首页1-Android开发知识Android性能优化
Android性能优化篇之电量优化(2)

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

作者: 爱听音乐的小石头 | 来源:发表于2018-06-14 10:53 被阅读201次
    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(有数据发生时才上电, 保持一个频率的与基站交互)等等.
    具体的优化请看关于网络优化的文章。

    相关文章

      网友评论

        本文标题:Android性能优化篇之电量优化(2)

        本文链接:https://www.haomeiwen.com/subject/wlweeftx.html