只有明白了電量是怎么消耗的,才能找到優化的方法。
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了?主要使用在后臺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 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網絡
手機在不傳輸數據的情況下一般處于空閑狀態(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:
被動定位,就是用現成的,當其他應用使用定位更新了定位信息,系統會保存下來,該應用接收到消息后直接讀取就可以了。比如如果系統中已經安裝了百度地圖,高德地圖(室內可以實現精確定位),你只要使用它們定位過后,再使用這種方法在你的程序肯定是可以拿到比較精確的定位信息。