只有明白了電量是怎么消耗的,才能找到優(yōu)化的方法。
google給出的電量消耗圖。

對于不同的元器件消耗的電量不同。
電量消耗比較大的部分分如下幾種:
1.喚醒屏幕
在待機(jī)和屏幕亮起的過程中對電量的消耗有所不同。


從圖上可以看出,待機(jī)狀態(tài)電量使用趨于平衡狀態(tài)。
在屏幕點亮的開始就會出現(xiàn)短時間的電量使用峰值。然后趨于平衡。可見屏幕點亮過程中消耗電量比較大。頻繁的點亮屏幕會造成點亮的消耗比較大。
系統(tǒng)在屏幕操作的時候會關(guān)閉屏幕。如果我們的應(yīng)用,在需要看屏幕,但是不需要操作的時候,可以使屏幕一直點亮。(如視頻軟件,行情軟件。視頻軟件我們可以在電影開始的時候讓屏幕一直常亮,結(jié)束的時候回顧到系統(tǒng)控制。)
有兩種方法設(shè)置屏幕常亮。
1.代碼設(shè)置(優(yōu)點在于,可以控制常亮開關(guān))
//常亮開關(guān)(開)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//常亮開關(guān)(關(guān))
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
2.在xml中設(shè)置(不能控制關(guān)閉)
<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處理


和屏幕喚醒一樣在喚醒的瞬間電量使用比較大。后續(xù)就趨于平穩(wěn)。在什么情況下我們需要使用喚醒CPU了?主要使用在后臺service上。當(dāng)我們的service處于后臺的時候,cpu有可能被系統(tǒng)休眠。
google官方引入了低電耗模式。
Android 6.0(API 級別 23)引入了低電耗模式,當(dāng)用戶設(shè)備未插接電源、處于靜止?fàn)顟B(tài)且屏幕關(guān)閉時,該模式會推遲 CPU 和網(wǎng)絡(luò)活動,從而延長電池壽命。而 Android 7.0 則通過在設(shè)備未插接電源且屏幕關(guān)閉狀態(tài)下、但不一定要處于靜止?fàn)顟B(tài)(例如用戶外出時把手持式設(shè)備裝在口袋里)時應(yīng)用部分 CPU 和網(wǎng)絡(luò)限制,進(jìn)一步增強(qiáng)了低電耗模式。
所以為滿足我們后臺運行必須使CPU喚醒。這就需要引入WakeLock控制。
喚醒鎖可劃分為并識別四種用戶喚醒鎖:
| 標(biāo)記值 | CPU | 屏幕 | 鍵盤 |
|---|---|---|---|
| PARTIAL_WAKE_LOCK | 開啟 | 關(guān)閉 | 關(guān)閉 |
| SCREEN_DIM_WAKE_LOCK | 開啟 | 變暗 | 關(guān)閉 |
| SCREEN_BRIGHT_WAKE_LOCK | 開啟 | 變亮 | 關(guān)閉 |
| 開啟 | 變亮 | 變亮 |
FULL_WAKE_LOCK api以后被棄用 可以使用FLAG_KEEP_SCREEN_ON替代
wakelock使用:
1.添加權(quán)限
<uses-permission android:name="android.permission.WAKE_LOCK" />
2.成對出現(xiàn)。開啟后,還需在不用的時候關(guān)閉。
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag");
wakeLock.acquire();
wakeLock.release();
3.蜂窩式無線

當(dāng)設(shè)備通過無線網(wǎng)發(fā)送數(shù)據(jù)的時候,為了使用硬件,這里會出現(xiàn)一個喚醒好點高峰。接下來還有一個高數(shù)值,這是發(fā)送數(shù)據(jù)包消耗的電量,然后接受數(shù)據(jù)包也會消耗大量電量 也看到一個峰值。
4. 2G/3G/4G網(wǎng)絡(luò)

手機(jī)在不傳輸數(shù)據(jù)的情況下一般處于空閑狀態(tài)(Idle),當(dāng)有發(fā)送數(shù)據(jù)需求時必須向控制平臺發(fā)送申請。只有將手機(jī)切換到Active狀態(tài),也就是高耗能模式下才能進(jìn)行通信。這一切換過程在4G網(wǎng)絡(luò)下需要花費100ms的時間。通信完成后,手機(jī)不會一直處于高耗能模式下等待數(shù)據(jù)傳輸,它會切換到低耗能模式(Short sleep)。如果手機(jī)處于低耗能模式時接到數(shù)據(jù)發(fā)送請求,那么它又會切換到高耗能模式來發(fā)送數(shù)據(jù)。在頻繁的數(shù)據(jù)請求中,它會在低耗能模式和高耗能模式不斷的切換,而在不發(fā)送數(shù)據(jù)時,在10s后會再次進(jìn)入空閑模式下。它會周期性的切換模式來確保資源的有效利用。
總之,為了減少電量的消耗,在蜂窩移動網(wǎng)絡(luò)下,最好做到批量執(zhí)行網(wǎng)絡(luò)請求,盡量避免頻繁的間隔網(wǎng)絡(luò)請求。


網(wǎng)絡(luò)請求主要做的事:
1.大量的數(shù)據(jù)請求
2.網(wǎng)絡(luò)的切換
3.數(shù)據(jù)的解析
處理的方式可以有:
1.批量的請求數(shù)據(jù),延時請求可以使用Job Scheduler
2.判斷當(dāng)前是否是充電狀態(tài),或者電量低的情況。在不同請求做不同的操作。
3.在請求之前判斷網(wǎng)絡(luò)連接狀態(tài)。
4.使用數(shù)據(jù)壓縮
- 判斷充電狀態(tài)
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.直接獲得電池當(dāng)前的電量, 它介于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.通過發(fā)送系統(tǒng)廣播獲得系統(tǒng)判斷的電量高低。
系統(tǒng)對應(yīng)電量低的判斷是:沒有充電狀態(tài),電量小于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);
這里只有通過廣播進(jìn)行接收了。
//定義關(guān)閉接收
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;
}
//在需要的地方進(jìn)行注冊
registerReceiver(receiver, getFilter());
//在退出的時候進(jìn)行釋放
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)) {
// 當(dāng)前電池的電壓
int voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1);
// 電池的健康狀態(tài)
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;
}
// 電池當(dāng)前的電量, 它介于0和 EXTRA_SCALE之間
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
// 電池電量的最大值
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
// 當(dāng)前手機(jī)使用的是哪里的電源
int pluged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED,-1);
switch (pluged) {
case BatteryManager.BATTERY_PLUGGED_AC:
// 電源是AC charger.[應(yīng)該是指充電器]
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:
// 未知狀態(tài)
System.out.println("BATTERY_STATUS_UNKNOWN ");
break;
default:
break;
}
// 電池使用的技術(shù)。比如,對于鋰電池是Li-ion
String technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);
// 當(dāng)前電池的溫度
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)) {
// 表示當(dāng)前電池電量低
System.out.println("BatteryChangedReceiver ACTION_BATTERY_LOW---");
} else if (action.equalsIgnoreCase(Intent.ACTION_BATTERY_OKAY)) {
// 表示當(dāng)前電池已經(jīng)從電量低恢復(fù)為正常
System.out.println("BatteryChangedReceiver ACTION_BATTERY_OKAY---");
}
}
}
- 判斷網(wǎng)絡(luò)連接狀態(tài)
需要加權(quán)權(quán)限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE">
/**
* 1.判斷網(wǎng)絡(luò)連接是否已開 0 true 已打開 false 未打開
* 2.判斷網(wǎng)絡(luò)現(xiàn)在連接的是wifi還是蜂窩網(wǎng)絡(luò)
* 3.對網(wǎng)絡(luò)進(jìn)行設(shè)置
*/
public void setNetConn(Context context) {
boolean bisConnFlag = false;
ConnectivityManager conManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = conManager.getActiveNetworkInfo();
if (activeNetwork != null) {
//網(wǎng)絡(luò)是否連接成功
bisConnFlag = conManager.getActiveNetworkInfo().isAvailable();
if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
// 無線網(wǎng)絡(luò)
} else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
// 2/3/4G網(wǎng)絡(luò)
}
}else{
setNetworkMethod(this);
}
}
/*
* 打開設(shè)置網(wǎng)絡(luò)界面
*/
public void setNetworkMethod(final Context context) {
// 提示對話框
AlertDialog alertDialog = new AlertDialog.Builder(context).setTitle("溫馨提示")
.setMessage("網(wǎng)絡(luò)不可用,建議連接互聯(lián)網(wǎng)在使用!")
.setPositiveButton("設(shè)置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = null;
// 判斷手機(jī)系統(tǒng)的版本 即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();
}
- 數(shù)據(jù)壓縮
這個可以和后臺配合進(jìn)行數(shù)據(jù)的壓縮和解壓
- 延時加載 job Scheduler
使用Job Scheduler,應(yīng)用需要做的事情就是判斷哪些任務(wù)是不緊急的,可以交給Job Scheduler來處理,Job Scheduler集中處理收到的任務(wù),選擇合適的時間,合適的網(wǎng)絡(luò),再一起進(jìn)行執(zhí)行
1.創(chuàng)建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: 該系統(tǒng)假設(shè)任何任務(wù)運行不需要很長時間并且到方法返回時已經(jīng)完成。
* true: 該系統(tǒng)假設(shè)任務(wù)是需要一些時間并且當(dāng)任務(wù)完成時需要調(diào)用jobFinished()告知系統(tǒng)。
*/
@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;
}
/**
* 當(dāng)收到取消請求時,該方法是系統(tǒng)用來取消掛起的任務(wù)的。
* 如果onStartJob()返回false,則系統(tǒng)會假設(shè)沒有當(dāng)前運行的任務(wù),故不會調(diào)用該方法。
*/
@Override
public boolean onStopJob(JobParameters params) {
Log.i(LOG_TAG, "Whelp, something changed, so I'm calling it on job " + params.getJobId());
return false;
}
/**
* 檢查網(wǎng)絡(luò)
*/
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) {
//任務(wù)結(jié)束
jobFinished(mJobParam, false);
Log.i(LOG_TAG, result);
}
}
}
2.使用JobScheduler進(jìn)行調(diào)用
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) {
//在規(guī)定條件下滿足條件
JobInfo jobInfo = new JobInfo.Builder(i, mServiceComponent)
.setMinimumLatency(5000) // 設(shè)置任務(wù)運行最少延遲時間
.setOverrideDeadline(60000) // 設(shè)置deadline,若到期還沒有達(dá)到規(guī)定的條件則會開始執(zhí)行
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) // 設(shè)置網(wǎng)絡(luò)條件
.setRequiresCharging(true)// 設(shè)置是否充電的條件
.setRequiresDeviceIdle(false)// 設(shè)置手機(jī)是否空閑的條件
.build();
mWakeLockMsg.append("Scheduling job " + i + "!\n");
scheduler.schedule(jobInfo);
}
}
}
}
3.設(shè)置權(quán)限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
4.配置服務(wù)
<service
android:name=".MyJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
/>
5.如果你的應(yīng)用程序需要你停止特定或所有工作,你可以通過對JobScheduler 對象調(diào)用cancel(int jobId)或cancelAll()實現(xiàn)。
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對定位更加需要個性化的區(qū)分。選擇合適的Location Provider
Android系統(tǒng)支持多個Location Provider:
GPS_PROVIDER:
GPS定位,利用GPS芯片通過衛(wèi)星獲得自己的位置信息。定位精準(zhǔn)度高,一般在10米左右,耗電量大;但是在室內(nèi),GPS定位基本沒用。NETWORK_PROVIDER:
網(wǎng)絡(luò)定位,利用手機(jī)基站和WIFI節(jié)點的地址來大致定位位置,這種定位方式取決于服務(wù)器,即取決于將基站或WIF節(jié)點信息翻譯成位置信息的服務(wù)器的能力。PASSIVE_PROVIDER:
被動定位,就是用現(xiàn)成的,當(dāng)其他應(yīng)用使用定位更新了定位信息,系統(tǒng)會保存下來,該應(yīng)用接收到消息后直接讀取就可以了。比如如果系統(tǒng)中已經(jīng)安裝了百度地圖,高德地圖(室內(nèi)可以實現(xiàn)精確定位),你只要使用它們定位過后,再使用這種方法在你的程序肯定是可以拿到比較精確的定位信息。