八、Android性能優(yōu)化之電量優(yōu)化(二)

基于V2.0版本的battery historian請先看 battery historian安裝與使用
(1).橫坐標

橫坐標就是一個時間范圍,咱們的例子中統(tǒng)計的數(shù)據(jù)是以重置為起點耸袜,獲取bugreport內(nèi)容時刻為終點。我們一共采集了多長時間的數(shù)據(jù)

(2).縱坐標

關鍵的數(shù)據(jù)點我們用表格來匯總一下噪漾。

參數(shù)名 作用
CPU running CPU的運行狀態(tài)宵喂,是否被喚醒。如果把鼠標放到上面去碎罚,還能看到更多的信息磅废,如CPU喚醒的原因。
Screen 亮屏狀態(tài)荆烈,可以看到圖表中該項著色有間隔拯勉,這是因為實驗期間我關閉過屏幕,每關閉一次屏幕憔购,著色就被打斷宫峦。
Top app 當前最上層的app
Mobile network type 網(wǎng)絡類型,其中需要注意的是玫鸟,“免費網(wǎng)絡可能包括wifi导绷、藍牙網(wǎng)絡共享、USB網(wǎng)絡共享”
Mobile radio active 移動蜂窩信號 BP側(cè)耗電鞋邑,通常是指SIM卡诵次,數(shù)據(jù)鏈接。該欄過多著色枚碗,間隔多逾一。表示功耗也會高。
WiFi supplicant wifi是否開啟
WiFi signal strength wifi強度
Wifi Running wifi連接情況下的耗電情況
Audio 音頻是否開啟
Battery Level 電量
Plugged 是否正在充電肮雨,以及鼠標放在上面的時候可以看到充電類型遵堵,包括AC(充電器)、USB怨规、其它(例如無線充電)
Battery Level 開始測試時的電量陌宿,之前抓取的圖可以看到電量是100,滿電狀態(tài)波丰。
Top app 前臺應用壳坪,如果要分析應用的耗電情況,那么在測試期間掰烟,就該保證應用一直處于前臺爽蝴。
Userspace wakelock 記錄wake_lock模塊的工作時間

ps:系統(tǒng)為了節(jié)省電量沐批,CPU在沒有任務忙的時候就會自動進入休眠。有任務需要喚醒CPU高效執(zhí)行的時候蝎亚,就會給CPU加wake_lock鎖九孩。

電量優(yōu)化建議:

當Android設備空閑時,屏幕會變暗发框,然后關閉屏幕躺彬,最后會停止CPU的運行,這樣可以防止電池電量掉的快梅惯。在休眠過程中自定義的Timer宪拥、Handler、Thread个唧、Service等都會暫停江解。但有些時候我們需要改變Android系統(tǒng)默認的這種狀態(tài):比如玩游戲時我們需要保持屏幕常亮,比如一些下載操作不需要屏幕常亮但需要CPU一直運行直到任務完成徙歼。從而防止因為喚醒的瞬間而耗更多的電犁河。

1、判斷充電狀態(tài)

這里我們就需要思考魄梯,根據(jù)我們自己的業(yè)務桨螺,那些為了省電,可以放當手機插上電源的時候去做酿秸。
往往這樣的情況非常多灭翔。像這些不需要及時地和用戶交互的操作可以放到后面處理。
比如:360手機助手辣苏,當充上電的時候肝箱,才會自動清理手機垃圾,自動備份上傳圖片稀蟋、聯(lián)系人等到云端煌张;再比如我們自己的APP,其中有一塊業(yè)務是相冊備份退客,這個時候有一個選項控制讓用戶選擇是否在低于15%的電量時還繼續(xù)進行備份骏融,從而避免當用戶手機低電量時,任然繼續(xù)進行耗電操作萌狂。

我們可以通過下面的代碼來獲取手機的當前充電狀態(tài):

// 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);
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC);
if (acCharge) {
    Log.v(LOG_TAG,“The phone is charging!”);
}

  private boolean checkForPower() {
        //獲取電池的充電狀態(tài)(注冊一個廣播)
        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent res = this.registerReceiver(null, filter);

        //通過使用BatteryManager的參數(shù)信息判斷充電狀態(tài)
        if (res != null) {
            int chargePlug = res.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
            boolean usb = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;//usb充電
            boolean ac = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;//交流電
            //無線充電档玻,這個需要API>=17
            boolean wireless = false;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                wireless = chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS;
            }
            return (usb || ac || wireless);
        } else {
            return false;
        }
    }

調(diào)用示例

    private void applyFilter() {
        //是否在充電
        if(!checkForPower()){
            mPowerMsg.setText("請充上電,再處理茫藏!");
            return;
        }
        mCheyennePic.setImageResource(R.drawable.pink_cheyenne);
        mPowerMsg.setText(R.string.photo_filter);
    }

2误趴、屏幕保持常亮

為了防止屏幕喚醒一瞬間耗電過多,有一些應用务傲,比如游戲冤留、支付頁面碧囊,需要保持屏幕常亮來節(jié)省電量:

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

也可以在布局文件里面使用,但是沒有那么靈活:

android:keepScreenOn="true"

注意:一般不需要人為的去掉FLAG_KEEP_SCREEN_ON的flag纤怒,windowManager會管理好程序進入后臺回到前臺的的操作。如果確實需要手動清掉常亮的flag天通,使用

getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
android:keepScreenOn = ” true “的作用和FLAG_KEEP_SCREEN_ON一樣泊窘。使用代碼的好處是你允許你在需要的地方關閉屏幕。

注意:一般不需要人為的去掉FLAG_KEEP_SCREEN_ON的flag像寒,windowManager會管理好程序進入后臺回到前臺的的操作烘豹。如果確實需要手動清掉常亮的flag,使用getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

3.1诺祸、使用wake_lock

系統(tǒng)為了節(jié)省電量携悯,CPU在沒有任務忙的時候就會自動進入休眠。有任務需要喚醒CPU高效執(zhí)行的時候筷笨,就會給CPU加wake_lock鎖憔鬼。wake_lock鎖主要是相對系統(tǒng)的休眠而言的,意思就是我的程序給CPU加了這個鎖那系統(tǒng)就不會休眠了胃夏,這樣做的目的是為了全力配合我們程序的運行轴或。有的情況如果不這么做就會出現(xiàn)一些問題,比如微信等及時通訊的心跳包會在熄屏不久后停止網(wǎng)絡訪問等問題仰禀。所以微信里面是有大量使用到了wake_lock鎖照雁。
PowerManager這個系統(tǒng)服務的喚醒鎖(wake locks)特征來保持CPU處于喚醒狀態(tài)。喚醒鎖允許程序控制宿主設備的電量狀態(tài)答恶。創(chuàng)建和持有喚醒鎖對電池的續(xù)航有較大的影響饺蚊,所以,除非是真的需要喚醒鎖完成盡可能短的時間在后臺完成的任務時才使用它悬嗓。比如在Acitivity中就沒必要用了污呼。一種典型的代表就是在屏幕關閉以后,后臺服務繼續(xù)保持CPU運行烫扼。
如果不使用喚醒鎖來執(zhí)行后臺服務曙求,不能保證因CPU休眠未來的某個時刻任務會停止,這不是我們想要的映企。(有的人可能認為以前寫的后臺服務就沒掉過鏈子呀運行得挺好的悟狱,
1.可能是你的任務時間比較短;
2.可能CPU被手機里面很多其他的軟件一直在喚醒狀態(tài))堰氓。
其中挤渐,喚醒鎖有下面幾種類型:

喚醒鎖的類型
wake_lock兩種鎖(從釋放、使用的角度來看的話):計數(shù)鎖和非計數(shù)鎖(鎖了很多次双絮,只需要release一次就可以解除了)
請注意浴麻,自 API 等級17開始得问,F(xiàn)ULL_WAKE_LOCK將被棄用,應使用FLAG_KEEP_SCREEN_ON代替软免。

綜上所述宫纬,為了防止CPU喚醒一瞬間耗電過多,在執(zhí)行關鍵代碼的時候膏萧,為了防止CPU睡眠漓骚,需要使用喚醒鎖來節(jié)省電量:

//創(chuàng)建喚醒鎖
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "partial_lock");

//獲取喚醒鎖
wakeLock.acquire();

//一些關鍵的代碼

//釋放喚醒鎖
wakeLock.release();

需要添加權限:

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

Tips:獲取與釋放喚醒鎖需要成對出現(xiàn)
Tips:有一些意外的情況,比如小米手機是做了同步心跳包(心跳對齊)(如果超過了這個同步的頻率就會被屏蔽掉或者降頻)榛泛,所有的app后臺喚醒頻率不能太高蝌蹂,這時候就需要降頻,比如每隔2S中去請求曹锨。

3.2孤个、使用WakefulBroadcastReceiver

上面提到,典型的使用場景就是后臺服務需要保持CPU保持運行沛简,但推薦的方式是使用WakefulBroadcastReceiver:使用廣播和Service(典型的IntentService)結合的方式可以讓你很好地管理后臺服務的生命周期齐鲤。

WakefulBroadcastReceiver是BroadcastReceiver的一種特例。它會為你的APP創(chuàng)建和管理一個PARTIAL_WAKE_LOCK 類型的WakeLock覆享。一個WakeBroadcastReceiver接收到廣播后將工作傳遞給Service(一個典型的IntentService)佳遂,直到確保設備沒有休眠。如果你在交接工作給服務的時候沒有保持喚醒鎖撒顿,在工作還沒完成之前就允許設備休眠的話丑罪,將會出現(xiàn)一些你不愿意看到的情況。

public class MyIntentService extends IntentService {

    public MyIntentService(String name) {
        super(name);
    }

    public MyIntentService() {
        super(MyIntentService.class.getSimpleName());
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        if (intent != null) {
            //獲取參數(shù)
            Bundle extras = intent.getExtras();

            //執(zhí)行一些需要CPU保持喚醒的代碼

            //執(zhí)行結束凤壁,釋放喚醒鎖
            MyWakefulReceiver.completeWakefulIntent(intent);
        }
    }
}


廣播接收者:

public class MyWakefulReceiver extends WakefulBroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Intent service = new Intent(context, MyIntentService.class);
        startWakefulService(context, service);
    }

}


需要使用服務的時候吩屹,像一般的方式一樣即可:

Intent intent = new Intent(this, MyIntentService.class);
//傳遞參數(shù)
intent.setData(Uri.parse("xxx"));
分析WakefulBroadcastReceiver 源碼(查看源碼記得引入v4包)
public abstract class WakefulBroadcastReceiver extends BroadcastReceiver {
    private static final String EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid";

    private static final SparseArray<PowerManager.WakeLock> mActiveWakeLocks
            = new SparseArray<PowerManager.WakeLock>();
    private static int mNextId = 1;

    /**
     * Do a {@link android.content.Context#startService(android.content.Intent)
     * Context.startService}, but holding a wake lock while the service starts.
     * This will modify the Intent to hold an extra identifying the wake lock;
     * when the service receives it in {@link android.app.Service#onStartCommand
     * Service.onStartCommand}, it should pass back the Intent it receives there to
     * {@link #completeWakefulIntent(android.content.Intent)} in order to release
     * the wake lock.
     *
     * @param context The Context in which it operate.
     * @param intent The Intent with which to start the service, as per
     * {@link android.content.Context#startService(android.content.Intent)
     * Context.startService}.
     */
    public static ComponentName startWakefulService(Context context, Intent intent) {
        synchronized (mActiveWakeLocks) {
            int id = mNextId;
            mNextId++;
            if (mNextId <= 0) {
                mNextId = 1;
            }

            intent.putExtra(EXTRA_WAKE_LOCK_ID, id);
            ComponentName comp = context.startService(intent);
            if (comp == null) {
                return null;
            }

            PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
            PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                    "wake:" + comp.flattenToShortString());
            wl.setReferenceCounted(false);
            wl.acquire(60*1000);
            mActiveWakeLocks.put(id, wl);
            return comp;
        }
    }

    /**
     * Finish the execution from a previous {@link #startWakefulService}.  Any wake lock
     * that was being held will now be released.
     *
     * @param intent The Intent as originally generated by {@link #startWakefulService}.
     * @return Returns true if the intent is associated with a wake lock that is
     * now released; returns false if there was no wake lock specified for it.
     */
    public static boolean completeWakefulIntent(Intent intent) {
        final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0);
        if (id == 0) {
            return false;
        }
        synchronized (mActiveWakeLocks) {
            PowerManager.WakeLock wl = mActiveWakeLocks.get(id);
            if (wl != null) {
                wl.release();
                mActiveWakeLocks.remove(id);
                return true;
            }
            // We return true whether or not we actually found the wake lock
            // the return code is defined to indicate whether the Intent contained
            // an identifier for a wake lock that it was supposed to match.
            // We just log a warning here if there is no wake lock found, which could
            // happen for example if this function is called twice on the same
            // intent or the process is killed and restarted before processing the intent.
            Log.w("WakefulBroadcastReceiver", "No active wake lock id #" + id);
            return true;
        }
    }
}
我們發(fā)現(xiàn)WakefulBroadcastReceiver 本質(zhì)上還是基于 PowerManager.WakeLock
注意:
1.注意添加權限
2.注意服務與廣播的注冊
3.使用廣播來設計,就是為了解耦

網(wǎng)上采集的一些問題坑點及解決如下:

1.向服務器輪詢的代碼不執(zhí)行拧抖。

曾經(jīng)做一個應用煤搜,利用Timer和TimerTask,來設置對服務器進行定時的輪詢唧席,但是發(fā)現(xiàn)機器在某段時間后擦盾,輪詢就不再進行了。查了很久才發(fā) 現(xiàn)是休眠造成的淌哟。后來解決的辦法是迹卢,利用系統(tǒng)的AlarmService來執(zhí)行輪詢。因為雖然系統(tǒng)讓機器休眠徒仓,節(jié)省電量腐碱,但并不是完全的關機,系統(tǒng)有一部 分優(yōu)先級很高的程序還是在執(zhí)行的掉弛,比如鬧鐘症见,利用AlarmService可以定時啟動自己的程序喂走,讓cpu啟動,執(zhí)行完畢再休眠谋作。

解決:利用系統(tǒng)的AlarmService來替代Timer和TimerTask 執(zhí)行輪詢
2.后臺長連接斷開芋肠。

最近遇到的問題。利用Socket長連接實現(xiàn)QQ類似的聊天功能遵蚜,發(fā)現(xiàn)在屏幕熄滅一段時間后业栅,Socket就被斷開。屏幕開啟的時候需進行重連谬晕,但 每次看Log的時候又發(fā)現(xiàn)網(wǎng)絡是鏈接的,后來才發(fā)現(xiàn)是cpu休眠導致鏈接被斷開携取,當你插上數(shù)據(jù)線看log的時候攒钳,網(wǎng)絡cpu恢復,一看網(wǎng)絡確實是鏈接的雷滋, 坑不撑。最后使用了PARTIAL_WAKE_LOCK,保持CPU不休眠晤斩。

解決:使用了PARTIAL_WAKE_LOCK焕檬,保持CPU不休眠。
3.調(diào)試時是不會休眠的澳泵。

讓我非常郁悶的是实愚,在調(diào)試2的時候,就發(fā)現(xiàn)兔辅,有時Socket會斷開腊敲,有時不會斷開,后來才搞明白维苔,因為我有時是插著數(shù)據(jù)線進行調(diào)試碰辅,有時拔掉數(shù)據(jù)線,這 時Android的休眠狀態(tài)是不一樣的介时。而且不同的機器也有不同的表現(xiàn)没宾,比如有的機器,插著數(shù)據(jù)線就會充電沸柔,有的不會循衰,有的機器的設置的充電時屏幕不變暗 等等,把自己都搞暈了勉失。其實搞明白這個休眠機制羹蚣,一切都好說了。

通過Wakelock Detector(WLD)軟件可以看到手機中的Wakelock:
WLD

3.3乱凿、大量高頻次的CPU喚醒及操作使用JobScheduler/GCM

自 Android 5.0 發(fā)布以來顽素,JobScheduler 已成為執(zhí)行后臺工作的首選方式咽弦,其工作方式有利于用戶。應用可以在安排作業(yè)的同時允許系統(tǒng)基于內(nèi)存胁出、電源和連接情況進行優(yōu)化型型。JobSchedule的宗旨就是把一些不是特別緊急的任務放到更合適的時機批量處理。這樣做有兩個好處:

避免頻繁的喚醒硬件模塊全蝶,造成不必要的電量消耗闹蒜。
避免在不合適的時間(例如低電量情況下、弱網(wǎng)絡或者移動網(wǎng)絡情況下的)執(zhí)行過多的任務消耗電量抑淫;

JobScheduler

JobScheduler的簡單使用绷落,首先自定義一個Service類,繼承自JobService

public class JobSchedulerService extends JobService{
    private String TAG = JobSchedulerService.class.getSimpleName();

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        Log.d(TAG, "onStartJob:" + jobParameters.getJobId());

        if(true) {
            // JobService在主線程運行始苇,如果我們這里需要處理比較耗時的業(yè)務邏輯需單獨開啟一條子線程來處理并返回true砌烁,
            // 當給定的任務完成時通過調(diào)用jobFinished(JobParameters params, boolean needsRescheduled)告知系統(tǒng)。

            //假設開啟一個線程去下載文件
            new DownloadTask().execute(jobParameters);

            return true;

        }else {
            //如果只是在本方法內(nèi)執(zhí)行一些簡單的邏輯話返回false就可以了
            return false;
        }
    }

    /**
     * 比如我們的服務設定的約束條件為在WIFI狀態(tài)下運行催式,結果在任務運行的過程中WIFI斷開了系統(tǒng)
     * 就會通過回掉onStopJob()來通知我們停止運行函喉,正常的情況下不會回掉此方法
     *
     * @param jobParameters
     * @return
     */
    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        Log.d(TAG, "onStopJob:" + jobParameters.getJobId());

        //如果需要服務在設定的約定條件再次滿足時再次執(zhí)行服務請返回true,反之false
        return true;
    }

    class DownloadTask extends AsyncTask<JobParameters, Object, Object> {
        JobParameters mJobParameters;

        @Override
        protected Object doInBackground(JobParameters... jobParameterses) {
            mJobParameters = jobParameterses[0];

            //比如說我們這里處理一個下載任務
            //或是處理一些比較復雜的運算邏輯
            //...

            try {
                Thread.sleep(30*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Object o) {
            super.onPostExecute(o);
            //如果在onStartJob()中返回true的話,處理完成邏輯后一定要執(zhí)行jobFinished()告知系統(tǒng)已完成荣月,
            //如果需要重新安排服務請true管呵,反之false
            jobFinished(mJobParameters, false);
        }
    }
}

記得在Manifest文件內(nèi)配置Service

 <service android:name=".JobSchedulerService" android:permission="android.permission.BIND_JOB_SERVICE"/>

創(chuàng)建工作計劃

public class MainActivity extends Activity{
    private JobScheduler mJobScheduler;
    private final int JOB_ID = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mai_layout);

        mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE );

        //通過JobInfo.Builder來設定觸發(fā)服務的約束條件,最少設定一個條件
        JobInfo.Builder jobBuilder = new JobInfo.Builder(JOB_ID, new ComponentName(this, JobSchedulerService.class));

        //循環(huán)觸發(fā)哺窄,設置任務每三秒定期運行一次
        jobBuilder.setPeriodic(3000);

        //單次定時觸發(fā)捐下,設置為三秒以后去觸發(fā)。這是與setPeriodic(long time)不兼容的堂氯,
        // 并且如果同時使用這兩個函數(shù)將會導致拋出異常蔑担。
        jobBuilder.setMinimumLatency(3000);

        //在約定的時間內(nèi)設置的條件都沒有被觸發(fā)時三秒以后開始觸發(fā)。類似于setMinimumLatency(long time)咽白,
        // 這個函數(shù)是與 setPeriodic(long time) 互相排斥的啤握,并且如果同時使用這兩個函數(shù),將會導致拋出異常晶框。
        jobBuilder.setOverrideDeadline(3000);

        //在設備重新啟動后設置的觸發(fā)條件是否還有效
        jobBuilder.setPersisted(false);

        // 只有在設備處于一種特定的網(wǎng)絡狀態(tài)時排抬,它才觸發(fā)。
        // JobInfo.NETWORK_TYPE_NONE,無論是否有網(wǎng)絡均可觸發(fā)授段,這個是默認值蹲蒲;
        // JobInfo.NETWORK_TYPE_ANY,有網(wǎng)絡連接時就觸發(fā)侵贵;
        // JobInfo.NETWORK_TYPE_UNMETERED届搁,非蜂窩網(wǎng)絡中觸發(fā);
        // JobInfo.NETWORK_TYPE_NOT_ROAMING,非漫游網(wǎng)絡時才可觸發(fā)卡睦;
        jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);


         /**
             設置重試/退避策略宴胧,當一個任務調(diào)度失敗的時候執(zhí)行什么樣的測量采取重試。
             initialBackoffMillis:第一次嘗試重試的等待時間間隔ms
             *backoffPolicy:對應的退避策略表锻。比如等待的間隔呈指數(shù)增長恕齐。
             */
      // jobBuilder .setBackoffCriteria(long initialBackoffMillis, int backoffPolicy)
        jobBuilder .setBackoffCriteria(JobInfo.MAX_BACKOFF_DELAY_MILLIS, JobInfo.BACKOFF_POLICY_LINEAR)
        //設置手機充電狀態(tài)下觸發(fā)
        jobBuilder.setRequiresCharging(true);

        //設置手機處于空閑狀態(tài)時觸發(fā)
        jobBuilder.setRequiresDeviceIdle(true);

        //得到JobInfo對象
        JobInfo jobInfo = jobBuilder.build();

        //設置開始安排任務,它將返回一個狀態(tài)碼
        //JobScheduler.RESULT_SUCCESS瞬逊,成功
        //JobScheduler.RESULT_FAILURE显歧,失敗
        if (mJobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) {
            //安排任務失敗
        }

        //停止指定JobId的工作服務
        mJobScheduler.cancel(JOB_ID);
        //停止全部的工作服務
        mJobScheduler.cancelAll();
    }

量高頻次的CPU喚醒及操作,我們最好把這些操作集中處理确镊。我們可以采取一些算法來解決士骤。
可以借鑒谷歌的精髓,JobScheduler/GCM蕾域。

4敦间、使用AlarmManager來喚醒

當機器一段時間不操作以后,就會進入睡眠狀態(tài)束铭。向服務器的輪詢就會停止、長連接就會斷開厢绝,為了防止這樣的情況契沫,就可以使用AlarmManager:

Intent intent = new Intent(this, TestService.class);
PendingIntent pi = PendingIntent.getService(this, 0, intent, 0);

AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
am.cancel(pi);

//鬧鐘在系統(tǒng)睡眠狀態(tài)下會喚醒系統(tǒng)并執(zhí)行提示功能
//模糊時間,在API-19中以及以前昔汉,setRepeating都是不準確的
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, 2000, pi);
//準確時間懈万,但是需要在API-17之后使用
am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, pi);
am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, pi);

該定時器可以啟動Service服務、發(fā)送廣播靶病、跳轉(zhuǎn)Activity会通,并且會在系統(tǒng)睡眠狀態(tài)下喚醒系統(tǒng)。所以該方法不用獲取電源鎖和釋放電源鎖娄周。
關于AlarmManager的更多信息涕侈,請參考其他文章。
在19以上版本煤辨,setRepeating中設置的頻率只是建議值(6.0 的源碼中最小值是60s)裳涛,如果要精確一些的用setWindow或者setExact。

5众辨、其他優(yōu)化

當然端三,電量優(yōu)化是包括很多方面的,例如:

渲染優(yōu)化
定位策略優(yōu)化
網(wǎng)絡優(yōu)化鹃彻,例如網(wǎng)絡緩存處理郊闯,請求方式、次數(shù)優(yōu)化、設置超時時間等等
代碼執(zhí)行效率優(yōu)化
防止內(nèi)存泄漏
等等团赁,電量優(yōu)化無處不在育拨。

深化

首先Android手機有兩個處理器,一個叫Application Processor(AP)然痊,一個叫Baseband Processor(BP)至朗。AP是ARM架構的處理器,用于運行Linux+Android系統(tǒng)剧浸;BP用于運行實時操作系統(tǒng)(RTOS)锹引,通訊協(xié)議棧運行于BP的RTOS之上。非通話時間唆香,BP的能耗基本上在5mA左右嫌变,而AP只要處于非休眠狀態(tài),能耗至少在50mA以上躬它,執(zhí)行圖形運算時會更高腾啥。另外LCD工作時功耗在100mA左右,WIFI也在100mA左右冯吓。一般手機待機時倘待,AP、LCD组贺、WIFI均進入休眠狀態(tài)凸舵,這時Android中應用程序的代碼也會停止執(zhí)行。
Android為了確保應用程序中關鍵代碼的正確執(zhí)行失尖,提供了Wake Lock的API啊奄,使得應用程序有權限通過代碼阻止AP進入休眠狀態(tài)。但如果不領會Android設計者的意圖而濫用Wake Lock API掀潮,為了自身程序在后臺的正常工作而長時間阻止AP進入休眠狀態(tài)菇夸,就會成為待機電池殺手。比如前段時間的某應用仪吧,比如現(xiàn)在仍然干著這事的某應用庄新。

AlarmManager 是Android 系統(tǒng)封裝的用于管理 RTC 的模塊,RTC (Real Time Clock) 是一個獨立的硬件時鐘薯鼠,可以在 CPU 休眠時正常運行摄咆,在預設的時間到達時,通過中斷喚醒 CPU人断。(極光推送就是利用這個來做的吭从。)

總結:

1.關鍵邏輯的執(zhí)行過程,就需要Wake Lock來保護恶迈。如斷線重連重新登陸
2.休眠的情況下如何喚醒來執(zhí)行任務涩金?用AlarmManager谱醇。如推送消息的獲取

其他參考資料:
alarmManager在手機休眠時無法喚醒Service的問題?( 為了對付的頻繁喚醒的”流氓”app,有的廠家都開發(fā)了心跳對齊步做。)
https://www.zhihu.com/question/36421849
微信 Android 版 6.2 為什么設置了大量長時間的隨機喚醒鎖副渴?
https://www.zhihu.com/question/31136645

特別感謝:
黃俊彬
小楠總
動腦學院Ricky

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市全度,隨后出現(xiàn)的幾起案子煮剧,更是在濱河造成了極大的恐慌,老刑警劉巖将鸵,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勉盅,死亡現(xiàn)場離奇詭異,居然都是意外死亡顶掉,警方通過查閱死者的電腦和手機草娜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痒筒,“玉大人宰闰,你說我怎么就攤上這事〔就福” “怎么了移袍?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長老充。 經(jīng)常有香客問我咐容,道長,這世上最難降的妖魔是什么蚂维? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮路狮,結果婚禮上虫啥,老公的妹妹穿的比我還像新娘。我一直安慰自己奄妨,他們只是感情好涂籽,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著砸抛,像睡著了一般评雌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上直焙,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天景东,我揣著相機與錄音,去河邊找鬼奔誓。 笑死斤吐,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播和措,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼庄呈,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了派阱?” 一聲冷哼從身側(cè)響起诬留,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贫母,沒想到半個月后文兑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡颁独,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年彩届,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片誓酒。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡樟蠕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出靠柑,到底是詐尸還是另有隱情寨辩,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布歼冰,位于F島的核電站靡狞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏隔嫡。R本人自食惡果不足惜甸怕,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腮恩。 院中可真熱鬧梢杭,春花似錦、人聲如沸秸滴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荡含。三九已至咒唆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間释液,已是汗流浹背全释。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留误债,地道東北人恨溜。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓符衔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親糟袁。 傳聞我的和親對象是個殘疾皇子判族,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容