電量優化(2) ---- 電量消耗過程及優化

只有明白了電量是怎么消耗的,才能找到優化的方法。

google給出的電量消耗圖。

電量硬件消耗

對于不同的元器件消耗的電量不同。

電量消耗比較大的部分分如下幾種:

1.喚醒屏幕

在待機和屏幕亮起的過程中對電量的消耗有所不同。

待機狀態

屏幕點亮

從圖上可以看出,待機狀態電量使用趨于平衡狀態。
在屏幕點亮的開始就會出現短時間的電量使用峰值。然后趨于平衡。可見屏幕點亮過程中消耗電量比較大。頻繁的點亮屏幕會造成點亮的消耗比較大。

系統在屏幕操作的時候會關閉屏幕。如果我們的應用,在需要看屏幕,但是不需要操作的時候,可以使屏幕一直點亮。(如視頻軟件,行情軟件。視頻軟件我們可以在電影開始的時候讓屏幕一直常亮,結束的時候回顧到系統控制。)

有兩種方法設置屏幕常亮。
1.代碼設置(優點在于,可以控制常亮開關)

 //常亮開關(開)
 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//常亮開關(關)
 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

2.在xml中設置(不能控制關閉)

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

2.CPU處理

CPU喚醒

后續消耗

和屏幕喚醒一樣在喚醒的瞬間電量使用比較大。后續就趨于平穩。在什么情況下我們需要使用喚醒CPU了?主要使用在后臺service上。當我們的service處于后臺的時候,cpu有可能被系統休眠。
google官方引入了低電耗模式。

Android 6.0(API 級別 23)引入了低電耗模式,當用戶設備未插接電源、處于靜止狀態且屏幕關閉時,該模式會推遲 CPU 和網絡活動,從而延長電池壽命。而 Android 7.0 則通過在設備未插接電源且屏幕關閉狀態下、但不一定要處于靜止狀態(例如用戶外出時把手持式設備裝在口袋里)時應用部分 CPU 和網絡限制,進一步增強了低電耗模式。

所以為滿足我們后臺運行必須使CPU喚醒。這就需要引入WakeLock控制。

喚醒鎖可劃分為并識別四種用戶喚醒鎖:

標記值 CPU 屏幕 鍵盤
PARTIAL_WAKE_LOCK 開啟 關閉 關閉
SCREEN_DIM_WAKE_LOCK 開啟 變暗 關閉
SCREEN_BRIGHT_WAKE_LOCK 開啟 變亮 關閉
FULL_WAKE_LOCK 開啟 變亮 變亮

FULL_WAKE_LOCK api以后被棄用 可以使用FLAG_KEEP_SCREEN_ON替代

wakelock使用:
1.添加權限

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

2.成對出現。開啟后,還需在不用的時候關閉。

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

wakeLock.release();

3.蜂窩式無線

蜂窩無線消耗電量

當設備通過無線網發送數據的時候,為了使用硬件,這里會出現一個喚醒好點高峰。接下來還有一個高數值,這是發送數據包消耗的電量,然后接受數據包也會消耗大量電量 也看到一個峰值。

4. 2G/3G/4G網絡

2G/3G/4G網絡請求

手機在不傳輸數據的情況下一般處于空閑狀態(Idle),當有發送數據需求時必須向控制平臺發送申請。只有將手機切換到Active狀態,也就是高耗能模式下才能進行通信。這一切換過程在4G網絡下需要花費100ms的時間。通信完成后,手機不會一直處于高耗能模式下等待數據傳輸,它會切換到低耗能模式(Short sleep)。如果手機處于低耗能模式時接到數據發送請求,那么它又會切換到高耗能模式來發送數據。在頻繁的數據請求中,它會在低耗能模式和高耗能模式不斷的切換,而在不發送數據時,在10s后會再次進入空閑模式下。它會周期性的切換模式來確保資源的有效利用。

總之,為了減少電量的消耗,在蜂窩移動網絡下,最好做到批量執行網絡請求,盡量避免頻繁的間隔網絡請求。

頻繁請求
優化后

網絡請求主要做的事:
1.大量的數據請求
2.網絡的切換
3.數據的解析

處理的方式可以有:
1.批量的請求數據,延時請求可以使用Job Scheduler
2.判斷當前是否是充電狀態,或者電量低的情況。在不同請求做不同的操作。
3.在請求之前判斷網絡連接狀態。
4.使用數據壓縮

  • 判斷充電狀態
public boolean chackForPower(){
        //通過廣播的方式獲得充電信息
        public boolean chackForPower(){
        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent batteryStatus = this.registerReceiver(null,intentFilter);

        int chagerPlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED,-1);
        boolean acChager = (chagerPlug == BatteryManager.BATTERY_PLUGGED_AC);
        boolean usbChager = (chagerPlug == BatteryManager.BATTERY_PLUGGED_USB);

        boolean wirelessCharge = false;
        //無線充電是在api 17以后才有的
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            wirelessCharge = (chagerPlug == BatteryManager.BATTERY_PLUGGED_WIRELESS);
        }

        return (acChager|usbChager|wirelessCharge);
    }
    }
  • 判斷電量低

1.直接獲得電池當前的電量, 它介于0和 EXTRA_SCALE之間,自己判斷電量高低

public  int getPowerLevel(){
        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent batteryLow = this.registerReceiver(null,intentFilter);
        return batteryLow.getIntExtra(BatteryManager.EXTRA_LEVEL,0);
    }

2.通過發送系統廣播獲得系統判斷的電量高低。

系統對應電量低的判斷是:沒有充電狀態,電量小于15%。(在config.xml中 <integer name="config_lowBatteryWarningLevel">15</integer>)

/* The ACTION_BATTERY_LOW broadcast is sent in these situations: 
 * - is just un-plugged (previously was plugged) and battery level is 
 *   less than or equal to WARNING, or 
 * - is not plugged and battery level falls to WARNING boundary 
 *   (becomes <= mLowBatteryWarningLevel). 
 */  
final boolean sendBatteryLow = !plugged  
        && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN  
        && mBatteryLevel <= mLowBatteryWarningLevel  
        && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);

這里只有通過廣播進行接收了。

//定義關閉接收
private BatteryChangedReceiver receiver = new BatteryChangedReceiver();
 
private IntentFilter getFilter() {
    IntentFilter filter = new IntentFilter();
    filter.addAction(Intent.ACTION_BATTERY_CHANGED);
    filter.addAction(Intent.ACTION_BATTERY_LOW);
    filter.addAction(Intent.ACTION_BATTERY_OKAY);
    return filter;
}
    
//在需要的地方進行注冊
registerReceiver(receiver, getFilter());
//在退出的時候進行釋放
unregisterReceiver(receiver);

//廣播信息
    class BatteryChangedReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (action.equalsIgnoreCase(Intent.ACTION_BATTERY_CHANGED)) {
                // 當前電池的電壓
                int voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1);
                // 電池的健康狀態
                int health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH, -1);
                switch (health) {
                    case BatteryManager.BATTERY_HEALTH_COLD:
                        System.out.println("BATTERY_HEALTH_COLD");
                        break;
                    case BatteryManager.BATTERY_HEALTH_DEAD:
                        System.out.println("BATTERY_HEALTH_DEAD ");
                        break;
                    case BatteryManager.BATTERY_HEALTH_GOOD:
                        System.out.println("BATTERY_HEALTH_GOOD");
                        break;
                    case BatteryManager.BATTERY_HEALTH_OVERHEAT:
                        System.out.println("BATTERY_HEALTH_OVERHEAT");
                        break;
                    case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
                        System.out.println("BATTERY_HEALTH_COLD");
                        break;
                    case BatteryManager.BATTERY_HEALTH_UNKNOWN:
                        System.out.println("BATTERY_HEALTH_UNKNOWN");
                        break;
                    case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:
                        System.out.println("BATTERY_HEALTH_UNSPECIFIED_FAILURE");
                        break;
                    default:
                        break;
                }
                // 電池當前的電量, 它介于0和 EXTRA_SCALE之間
                int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
                // 電池電量的最大值
                int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
                // 當前手機使用的是哪里的電源
                int pluged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED,-1);
                switch (pluged) {
                    case BatteryManager.BATTERY_PLUGGED_AC:
                        // 電源是AC charger.[應該是指充電器]
                        System.out.println("BATTERY_PLUGGED_AC");
                        break;
                    case BatteryManager.BATTERY_PLUGGED_USB:
                        // 電源是USB port
                        System.out.println("BATTERY_PLUGGED_USB ");
                        break;
                    default:
                        break;
                }
                int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
                switch (status) {
                    case BatteryManager.BATTERY_STATUS_CHARGING:
                        // 正在充電
                        System.out.println("BATTERY_STATUS_CHARGING ");
                        break;
                    case BatteryManager.BATTERY_STATUS_DISCHARGING:
                        System.out.println("BATTERY_STATUS_DISCHARGING  ");
                        break;
                    case BatteryManager.BATTERY_STATUS_FULL:
                        // 充滿
                        System.out.println("BATTERY_STATUS_FULL ");
                        break;
                    case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
                        // 沒有充電
                        System.out.println("BATTERY_STATUS_NOT_CHARGING ");
                        break;
                    case BatteryManager.BATTERY_STATUS_UNKNOWN:
                        // 未知狀態
                        System.out.println("BATTERY_STATUS_UNKNOWN ");
                        break;
                    default:
                        break;
                }
                // 電池使用的技術。比如,對于鋰電池是Li-ion
                String technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);
                // 當前電池的溫度
                int temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1);
                System.out.println("voltage = " + voltage + " technology = "
                        + technology + " temperature = " + temperature
                        + " level = " + level + " scale = " + scale);
            } else if (action.equalsIgnoreCase(Intent.ACTION_BATTERY_LOW)) {
                // 表示當前電池電量低
                System.out.println("BatteryChangedReceiver ACTION_BATTERY_LOW---");
            } else if (action.equalsIgnoreCase(Intent.ACTION_BATTERY_OKAY)) {
                // 表示當前電池已經從電量低恢復為正常
                System.out.println("BatteryChangedReceiver ACTION_BATTERY_OKAY---");
            }
        }
    }
  • 判斷網絡連接狀態

需要加權權限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE">

  /**
     * 1.判斷網絡連接是否已開 0 true 已打開 false 未打開
     * 2.判斷網絡現在連接的是wifi還是蜂窩網絡
     * 3.對網絡進行設置
     */
    public void setNetConn(Context context) {
        boolean bisConnFlag = false;
        ConnectivityManager conManager = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetwork = conManager.getActiveNetworkInfo();
        if (activeNetwork != null) {
            //網絡是否連接成功
            bisConnFlag = conManager.getActiveNetworkInfo().isAvailable();
            if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
                // 無線網絡
            } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
                // 2/3/4G網絡
            }
        }else{
            setNetworkMethod(this);
        }
    }
    /*
     * 打開設置網絡界面
    */
    public void setNetworkMethod(final Context context) {
        // 提示對話框

        AlertDialog alertDialog = new AlertDialog.Builder(context).setTitle("溫馨提示")

                .setMessage("網絡不可用,建議連接互聯網在使用!")
                .setPositiveButton("設置", new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Intent intent = null;
                        // 判斷手機系統的版本 即API大于10 就是3.0或以上版本
                        if (android.os.Build.VERSION.SDK_INT > 10) {
                            intent = new Intent(android.provider.Settings.ACTION_SETTINGS);
                        } else {
                            intent = new Intent();
                            ComponentName component = new ComponentName("com.android.settings",
                                    "com.android.settings.WirelessSettings");
                            intent.setComponent(component);
                            intent.setAction("android.intent.action.VIEW");
                        }
                        dialog.dismiss();
                        context.startActivity(intent);

                    }
                }).setNegativeButton("退出", new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        finish();
                    }
                }).create();
        alertDialog.setCancelable(false);
        alertDialog.show();
    }

  • 數據壓縮

這個可以和后臺配合進行數據的壓縮和解壓

  • 延時加載 job Scheduler

使用Job Scheduler,應用需要做的事情就是判斷哪些任務是不緊急的,可以交給Job Scheduler來處理,Job Scheduler集中處理收到的任務,選擇合適的時間,合適的網絡,再一起進行執行

1.創建jobservice. 必須在android 5以上才能使用

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
    private static final String LOG_TAG = "MyJobService";

    public MyJobService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(LOG_TAG, "MyJobService created");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(LOG_TAG, "MyJobService destroyed");
    }

    /**
     * false: 該系統假設任何任務運行不需要很長時間并且到方法返回時已經完成。
     * true:  該系統假設任務是需要一些時間并且當任務完成時需要調用jobFinished()告知系統。
     */
    @Override
    public boolean onStartJob(JobParameters params) {
        Log.i(LOG_TAG, "Totally and completely working on job " + params.getJobId());
        // First, check the network, and then attempt to connect.
        if (isNetworkConnected()) {
            new SimpleDownloadTask() .execute(params);
            return true;
        } else {
            Log.i(LOG_TAG, "No connection on job " + params.getJobId() + "; sad face");
        }
        return false;
    }

    /**
     * 當收到取消請求時,該方法是系統用來取消掛起的任務的。
     * 如果onStartJob()返回false,則系統會假設沒有當前運行的任務,故不會調用該方法。
     */
    @Override
    public boolean onStopJob(JobParameters params) {
        Log.i(LOG_TAG, "Whelp, something changed, so I'm calling it on job " + params.getJobId());
        return false;
    }

    /**
     * 檢查網絡
     */
    private boolean isNetworkConnected() {
        ConnectivityManager connectivityManager =
                (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
        return (networkInfo != null && networkInfo.isConnected());
    }

    /**
     * 異步請求
     */
    private class SimpleDownloadTask extends AsyncTask<JobParameters, Void, String> {

        protected JobParameters mJobParam;

        @Override
        protected String doInBackground(JobParameters... params) {
            // cache system provided job requirements
            mJobParam = params[0];
            int jobid = mJobParam.getJobId();
            try {
                InputStream is = null;
                // Only display the first 50 characters of the retrieved web page content.
                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");
                //Starts the query
                conn.connect();
                int response = conn.getResponseCode();
                Log.d(LOG_TAG, "jobid:"+jobid + "  The response is: " + response);
                is = conn.getInputStream();

                // Convert the input stream to a string
                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);
        }
    }
}

2.使用JobScheduler進行調用

public class JobActivity extends AppCompatActivity {
    public static final String LOG_TAG = JobActivity.class.getCanonicalName();

    TextView mWakeLockMsg;
    ComponentName mServiceComponent;

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

        mWakeLockMsg = (TextView) findViewById(R.id.mWakeLockMsg);
        mServiceComponent = new ComponentName(this, MyJobService.class);
        Intent startServiceIntent = new Intent(this, MyJobService.class);
        startService(startServiceIntent);

        Button theButtonThatWakelocks = (Button) findViewById(R.id.wakelock_poll);

        theButtonThatWakelocks.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                pollServer();
            }
        });
    }

    public void pollServer() {
        JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        for (int i = 0; i < 10; i++) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                //在規定條件下滿足條件
                JobInfo jobInfo = new JobInfo.Builder(i, mServiceComponent)
                        .setMinimumLatency(5000) // 設置任務運行最少延遲時間
                        .setOverrideDeadline(60000) // 設置deadline,若到期還沒有達到規定的條件則會開始執行
                        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) // 設置網絡條件
                        .setRequiresCharging(true)// 設置是否充電的條件
                        .setRequiresDeviceIdle(false)// 設置手機是否空閑的條件
                        .build();
                mWakeLockMsg.append("Scheduling job " + i + "!\n");
                scheduler.schedule(jobInfo);
            }
        }
    }

}

3.設置權限

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

4.配置服務

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

5.如果你的應用程序需要你停止特定或所有工作,你可以通過對JobScheduler 對象調用cancel(int jobId)或cancelAll()實現。

  Button theButtonThatWakelocksCancel = (Button) findViewById(R.id.wakelock_cancel);
        theButtonThatWakelocksCancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    if (scheduler != null){

                        Random rand = new Random();
                        int randNum = rand.nextInt(10);
                        scheduler.cancel(randNum);
                        Log.i(LOG_TAG,"Cancal id = " + randNum);
                    }
                }
            }
        });

5.GPS

定位是App中常用的功能,但是定位不能千篇一律,不同的場景以及不同類型的App對定位更加需要個性化的區分。選擇合適的Location Provider

Android系統支持多個Location Provider:

  • GPS_PROVIDER:
    GPS定位,利用GPS芯片通過衛星獲得自己的位置信息。定位精準度高,一般在10米左右,耗電量大;但是在室內,GPS定位基本沒用。

  • NETWORK_PROVIDER:
    網絡定位,利用手機基站和WIFI節點的地址來大致定位位置,這種定位方式取決于服務器,即取決于將基站或WIF節點信息翻譯成位置信息的服務器的能力。

  • PASSIVE_PROVIDER:
    被動定位,就是用現成的,當其他應用使用定位更新了定位信息,系統會保存下來,該應用接收到消息后直接讀取就可以了。比如如果系統中已經安裝了百度地圖,高德地圖(室內可以實現精確定位),你只要使用它們定位過后,再使用這種方法在你的程序肯定是可以拿到比較精確的定位信息。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,924評論 18 139
  • 沒有傻白甜的青春片感覺,最后的結局讓自己忍不住哭起來,失去了愛人,失去了唯一的親人,內心該多強大才能活下來?于是男...
    璇醬醬醬閱讀 504評論 0 0
  • 微笑,在臉頰微動嘴角翹起的那一刻你最美。 微風拂過,陽光灑在草地上,那草格外的綠,草下蟲子正在偷吃,鳥兒在銀杏樹上...
    空谷幽蘭AF閱讀 247評論 0 1
  • 網上有個關于初老癥的帖子,里面寫了很多初老癥的癥狀,比如:喜歡出門散步曬太陽,喜歡去有回憶的地方;買東西開始講究性...
    白子玉呀閱讀 441評論 1 1
  • 今天下午和一幫孩子補課,他們別稱為學裸生。媽媽有時候在想,是不是媽媽放棄了她們。是不是以前關注不夠,才讓我們娘倆挺...
    Snow_ning閱讀 243評論 0 0