只有明白了电量是怎么消耗的,才能找到优化的方法。
google给出的电量消耗图。
对于不同的元器件消耗的电量不同。
电量消耗比较大的部分分如下几种:
1.唤醒屏幕
在待机和屏幕亮起的过程中对电量的消耗有所不同。
从图上可以看出,待机状态电量使用趋于平衡状态。
在屏幕点亮的开始就会出现短时间的电量使用峰值。然后趋于平衡。可见屏幕点亮过程中消耗电量比较大。频繁的点亮屏幕会造成点亮的消耗比较大。
系统在屏幕操作的时候会关闭屏幕。如果我们的应用,在需要看屏幕,但是不需要操作的时候,可以使屏幕一直点亮。(如视频软件,行情软件。视频软件我们可以在电影开始的时候让屏幕一直常亮,结束的时候回顾到系统控制。)
有两种方法设置屏幕常亮。
1.代码设置(优点在于,可以控制常亮开关)
//常亮开关(开)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//常亮开关(关)
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
2.在xml中设置(不能控制关闭)
...
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.添加权限
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中
/* 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---");
}
}
}
- 判断网络连接状态
需要加权权限
/**
* 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 {
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.设置权限
4.配置服务
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:
被动定位,就是用现成的,当其他应用使用定位更新了定位信息,系统会保存下来,该应用接收到消息后直接读取就可以了。比如如果系统中已经安装了百度地图,高德地图(室内可以实现精确定位),你只要使用它们定位过后,再使用这种方法在你的程序肯定是可以拿到比较精确的定位信息。