Android性能優(yōu)化篇之電量?jī)?yōu)化(2)

image

引言

1. Android性能優(yōu)化篇之內(nèi)存優(yōu)化--內(nèi)存泄漏

2.Android性能優(yōu)化篇之內(nèi)存優(yōu)化--內(nèi)存優(yōu)化分析工具

3.Android性能優(yōu)化篇之UI渲染性能優(yōu)化

4.Android性能優(yōu)化篇之計(jì)算性能優(yōu)化

5.Android性能優(yōu)化篇之電量?jī)?yōu)化(1)——電量消耗分析

6.Android性能優(yōu)化篇之電量?jī)?yōu)化(2)

7.Android性能優(yōu)化篇之網(wǎng)絡(luò)優(yōu)化

8.Android性能優(yōu)化篇之Bitmap優(yōu)化

9.Android性能優(yōu)化篇之圖片壓縮優(yōu)化

10.Android性能優(yōu)化篇之多線程并發(fā)優(yōu)化

11.Android性能優(yōu)化篇之?dāng)?shù)據(jù)傳輸效率優(yōu)化

12.Android性能優(yōu)化篇之程序啟動(dòng)時(shí)間性能優(yōu)化

13.Android性能優(yōu)化篇之安裝包性能優(yōu)化

14.Android性能優(yōu)化篇之服務(wù)優(yōu)化

介紹

這篇我們來(lái)分析電量消耗快的原因,電量?jī)?yōu)化的方案。講之前我們先來(lái)了解下如何保持設(shè)備喚醒狀態(tài)?

一.如何保持設(shè)備喚醒狀態(tài)?

當(dāng)Android設(shè)備空閑時(shí),屏幕會(huì)變暗,然后關(guān)閉屏幕,最后會(huì)停止CPU的運(yùn)行,這樣可以防止電池電量掉的快。在休眠過(guò)程中自定義的Timer、Handler、Thread、Service等都會(huì)暫停。

什么情況下需要喚醒設(shè)備?

對(duì)于一些帶通訊功能的應(yīng)用,通訊的心跳包會(huì)在熄屏不久后停止網(wǎng)絡(luò)訪問(wèn),所以需要定時(shí)喚醒cpu。
后臺(tái)關(guān)鍵邏輯代碼執(zhí)行時(shí),防止cpu休眠
后臺(tái)長(zhǎng)連接的狀態(tài)
后臺(tái)定時(shí)任務(wù)執(zhí)行

下面我們來(lái)看下常用的喚醒設(shè)備的方法有哪些
1.保持屏幕常亮

有兩種方式:

(1).activity中設(shè)置
    //保持屏幕常亮
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    //取消屏幕常亮
    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
(2).配置文件中設(shè)置
    <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">

這個(gè)方法的好處是不像喚醒鎖(wake locks),需要一些特定的權(quán)限(permission)。并且能正確管理不同app之間的切換,不用擔(dān)心無(wú)用資源的釋放問(wèn)題。

2.保持CPU運(yùn)行(wake locks)

ake_lock鎖主要是相對(duì)系統(tǒng)的休眠而言的,意思就是我的程序給CPU加了這個(gè)鎖那系統(tǒng)就不會(huì)休眠了,這樣做的目的是為了全力配合我們程序的運(yùn)行。
需要使用PowerManager這個(gè)系統(tǒng)服務(wù)的喚醒鎖(wake locks)特征來(lái)保持CPU處于喚醒狀態(tài)。喚醒鎖允許程序控制宿主設(shè)備的電量狀態(tài)。創(chuàng)建和持有喚醒鎖對(duì)電池的續(xù)航有較大的影響,所以,除非是真的需要喚醒鎖完成盡可能短的時(shí)間在后臺(tái)完成的任務(wù)時(shí)才使用它。

使用場(chǎng)景

在使用后臺(tái)服務(wù)在屏幕關(guān)閉情況下hold住CPU完成一些工作。

不使用wake locks,執(zhí)行長(zhǎng)時(shí)間任務(wù)可能導(dǎo)致的問(wèn)題?

如果不使用喚醒鎖來(lái)執(zhí)行后臺(tái)服務(wù),不能保證因CPU休眠未來(lái)的某個(gè)時(shí)刻任務(wù)會(huì)停止

喚醒鎖可劃分為并識(shí)別四種用戶喚醒鎖:
image1.png
注意: android sdk 大于17 后,F(xiàn)ULL_WAKE_LOCK 將被棄用。 應(yīng)用應(yīng)使用 FLAG_KEEP_SCREEN_ON。
wake locks 使用步驟
(1).添加喚醒鎖權(quán)限
    <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();
    //執(zhí)行任務(wù)
    doJob();
    //釋放鎖
    if (wakeLock.isHeld()) {
        wakeLock.release();
    }
注意:在使用該類的時(shí)候,必須保證acquire和release是成對(duì)出現(xiàn)的。

我們也可以使用帶超時(shí)的acquire,防止沒有手動(dòng)釋放

    //帶超時(shí)的喚醒鎖,超時(shí)后自動(dòng)釋放鎖
    wakeLock.acquire(timeout);
WakefulBroadcastReceiver

官方推薦使用WakefulBroadcastReceiver。下面來(lái)看下定義和使用。
WakefulBroadcastReceiver是BroadcastReceiver的一種特例。它會(huì)為你的APP創(chuàng)建和管理一個(gè)PARTIAL_WAKE_LOCK 類型的WakeLock。WakefulBroadcastReceiver把工作交接給service(通常是IntentService),并保證交接過(guò)程中設(shè)備不會(huì)進(jìn)入休眠狀態(tài)。如果不持有WakeLock,設(shè)備很容易在任務(wù)未執(zhí)行完前休眠。最終結(jié)果是你的應(yīng)用不知道會(huì)在什么時(shí)候能把工作完成,相信這不是你想要的。

WakefulBroadcastReceiver使用:
(1).注冊(cè)
    <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();
            //執(zhí)行任務(wù)
            doJob();        
            MyWakefulReceiver.completeWakefulIntent(intent);
        }
3.AlarmManager 喚醒CPU
為什么AM能在cpu休眠時(shí),喚醒它呢?

首先Android手機(jī)有兩個(gè)處理器,一個(gè)叫Application Processor(AP),一個(gè)叫Baseband Processor(BP)。AP是ARM架構(gòu)的處理器,用于運(yùn)行Linux+Android系統(tǒng);BP用于運(yùn)行實(shí)時(shí)操作系統(tǒng)(RTOS),通訊協(xié)議棧運(yùn)行于BP的RTOS之上。非通話時(shí)間,BP的能耗基本上在5mA左右,而AP只要處于非休眠狀態(tài),能耗至少在50mA以上,執(zhí)行圖形運(yùn)算時(shí)會(huì)更高。另外LCD工作時(shí)功耗在100mA左右,WIFI也在100mA左右。一般手機(jī)待機(jī)時(shí),AP、LCD、WIFI均進(jìn)入休眠狀態(tài),這時(shí)Android中應(yīng)用程序的代碼也會(huì)停止執(zhí)行。

Android為了確保應(yīng)用程序中關(guān)鍵代碼的正確執(zhí)行,提供了Wake Lock的API,使得應(yīng)用程序有權(quán)限通過(guò)代碼阻止AP進(jìn)入休眠狀態(tài)。但如果不領(lǐng)會(huì)Android設(shè)計(jì)者的意圖而濫用Wake Lock API,為了自身程序在后臺(tái)的正常工作而長(zhǎng)時(shí)間阻止AP進(jìn)入休眠狀態(tài),就會(huì)成為待機(jī)電池殺手。

AlarmManager 是Android 系統(tǒng)封裝的用于管理 RTC 的模塊,RTC (Real Time Clock) 是一個(gè)獨(dú)立的硬件時(shí)鐘,可以在 CPU 休眠時(shí)正常運(yùn)行,在預(yù)設(shè)的時(shí)間到達(dá)時(shí),通過(guò)中斷喚醒 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);
            //鬧鐘在系統(tǒng)睡眠狀態(tài)下會(huì)喚醒系統(tǒng)并執(zhí)行提示功能
            alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,mTriggerTime,mItervalTime,pendingIntent);
//            alarmManager.setExact(AlarmManager.RTC_WAKEUP,mTriggerTime,pendingIntent);
//            alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), pendingIntent);
        }
    }

該定時(shí)器可以啟動(dòng)Service服務(wù)、發(fā)送廣播、跳轉(zhuǎn)Activity,并且會(huì)在系統(tǒng)睡眠狀態(tài)下喚醒系統(tǒng)。所以該方法不用獲取電源鎖和釋放電源鎖。

注意:在19以上版本,setRepeating中設(shè)置的頻繁只是建議值(6.0 的源碼中最小值是60s),如果要精確一些的用setWindow或者setExact。
注意:由于手機(jī)廠商做了心跳對(duì)齊,所有的app后臺(tái)喚醒頻率不能太高,不然會(huì)無(wú)效。
總結(jié):
1.關(guān)鍵邏輯的執(zhí)行過(guò)程,就需要Wake Lock來(lái)保護(hù)。如斷線重連重新登陸
2.休眠的情況下如何喚醒來(lái)執(zhí)行任務(wù)?用AlarmManager。如推送消息的獲取
注意:如果請(qǐng)求網(wǎng)絡(luò)很差,會(huì)要很長(zhǎng)的時(shí)間,一般我們谷歌建議一定要設(shè)置請(qǐng)求超時(shí)時(shí)間。

二.電量?jī)?yōu)化的一些建議

1.充電時(shí)執(zhí)行任務(wù)

為了省電,有些工作可以放當(dāng)手機(jī)插上電源的時(shí)候去做。往往這樣的情況非常多。像這些不需要及時(shí)地和用戶交互的操作可以放到后面處理。

    if (!checkForPower()) {
        Toast.makeText(view.getContext(), "當(dāng)前非充電狀態(tài)", Toast.LENGTH_SHORT).show();
        return;
    }
    /**
         * 是否充電
         * AC --- 交流電
         * USB
         * WireLess -- 無(wú)線充電
         *
         * @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后執(zhí)行任務(wù)

我們知道wifi網(wǎng)絡(luò)傳輸?shù)碾娏肯囊纫苿?dòng)網(wǎng)絡(luò)少很多,應(yīng)該盡量減少移動(dòng)網(wǎng)絡(luò)下的數(shù)據(jù)傳輸,多在WiFi環(huán)境下傳輸數(shù)據(jù),所以我們可以把一些不需要實(shí)時(shí)性的任務(wù)留到連接wifi后在執(zhí)行

3.wake_lock

系統(tǒng)為了節(jié)省電量,CPU在沒有任務(wù)忙的時(shí)候就會(huì)自動(dòng)進(jìn)入休眠。
有任務(wù)需要喚醒CPU高效執(zhí)行的時(shí)候,就會(huì)給CPU加wake_lock鎖。
但是根據(jù)我們上面的講解,使用wake_lock結(jié)束時(shí)需要釋放鎖,如果忘記釋放,會(huì)使得CPU一直執(zhí)行消耗電量,所以推薦使用帶超時(shí)的wake lock或者WakefulBroadcastReceiver

    wakeLock.acquire(timeout);
4.大量高頻次的CPU喚醒及操作集中處理

我們希望把頻繁的間隔任務(wù)集中起來(lái)進(jìn)行批量執(zhí)行, 這正是JobScheduler API所做的事情。它會(huì)根據(jù)當(dāng)前的情況與任務(wù),組合出理想的喚醒時(shí)間,例如等到正在充電或者連接到WiFi的時(shí)候,或者集中任務(wù)一起執(zhí)行。我們可以通過(guò)這個(gè)API實(shí)現(xiàn)很多免費(fèi)的調(diào)度算法。


image3.png
image2.png
JobScheduler

使用Job Scheduler,應(yīng)用需要做的事情就是判斷哪些任務(wù)是不緊急的,可以交給Job Scheduler來(lái)處理,Job Scheduler集中處理收到的任務(wù),選擇合適的時(shí)間,合適的網(wǎng)絡(luò),再一起進(jìn)行執(zhí)行。
使用(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)//設(shè)備重啟,任務(wù)是否保留
                    .setMinimumLatency(mMinLatencyMillis)//最小延時(shí)
                    .setOverrideDeadline    (mMaxExecutionDelayMillis)//最大執(zhí)行時(shí)間
                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)//網(wǎng)絡(luò)類型 NETWORK_TYPE_UNMETERED(wifi 藍(lán)牙)
                    .setRequiresCharging(true) //充電時(shí)執(zhí)行
                    //設(shè)置重試/退避策略 (重試時(shí)間,重試時(shí)間間隔)
                    .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;
        }

        /**
         * 查詢服務(wù)是否開啟
         * @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.定位

定位完成,及時(shí)關(guān)閉
如果需要實(shí)時(shí)定位,減少更新頻率
根據(jù)實(shí)際情況,選擇gsp定位還是網(wǎng)絡(luò)定位,降低電量消耗

6.網(wǎng)絡(luò)優(yōu)化

手機(jī)的通過(guò)內(nèi)置的射頻模塊和基站幾乎, 從而鏈接上網(wǎng)的, 而這個(gè)射頻模塊(radio)是非常耗電的.
為了控制這個(gè)射頻模塊的耗電, 硬件驅(qū)動(dòng)及Android RIL層做了很多處理. 例如可以單獨(dú)關(guān)閉radio(飛行模式), 間歇性假休眠radio(有數(shù)據(jù)發(fā)生時(shí)才上電, 保持一個(gè)頻率的與基站交互)等等.
具體的優(yōu)化請(qǐng)看關(guān)于網(wǎng)絡(luò)優(yōu)化的文章。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容