1 知识概览
2 用法简介
本文介绍使用Google开源项目Battery Hostoriany来进行电量分析,需要读者掌握Docker和CDN的使用,请自行搜索。
使用Battery Hostoriany需要安装Docker,Docker在Windows上使用有两种方式,一是利用VirtualBox建立linux虚拟机,在linux虚拟机中安装docker服务端和客户端,二是利用Windows的Hyper-v虚拟化技术,直接在Windows上安装docker服务端和客户端。WIndows7不支持Hyper-v,所以只能采用Docker Toolbox的方式使用Docker,具体安装过程请自行搜索。
此外,还需要借助Chrome浏览器插件CDN,安装该插件后,在Chrome地址栏输入chrome://extensions/进入到扩展程序浏览页面,开启CDN,电量分析完毕后再关闭。
使用此方法进行电量分析需要导出bugreport.txt文件(只要在终端<linux或者win>执行:adb bugreport > D:\bugreport.txt,即可生成bugreport文件,注意,需是运行着的7.0以上安卓),然后点击图中Browse按钮选中导出的bugreport.txt文件,再点击Submit提交, Battery Hostoriany会自动进行分析,如图所示:
上传完成后,移动鼠标到分析页面可查看具体电量使用情况,请自己试用吧。
注意:安装好Docker和CDN后,才能用Battery Hostoriany进行电量分析,将示图中的地址栏的地址https://bathist.ef.lc/改为使用http://47.52.175.85:9999/代替。
3 案例
接下来就是Demo了,我们使用高德定位SDK来建立一个Demo,然后导出bugreport.txt文件去做耗电分析,找到耗电的点,开发者即可回到代码中去相应的优化。
该Demo需要接入高德定位SDK(有关高德的使用是你应该早就掌握的哦):
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.0.0-beta1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:0.5'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
implementation 'com.amap.api:location:3.3.0'
}
项目结构:
LocationManager.java代码:
package com.dongnao.battery.location;
import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
import com.amap.api.location.AMapLocation;
import com.amap.api.location.AMapLocationClient;
import com.amap.api.location.AMapLocationClientOption;
import com.amap.api.location.AMapLocationListener;
import com.dongnao.battery.JobManager;
/**
* 主要代码复制自高德定位SDK:
* https://lbs.amap.com/api/android-location-sdk/locationsummary/
*/
public class LocationManager {
/**
* 声明AMapLocationClient类对象
*/
private AMapLocationClient mLocationClient;
private static LocationManager instance;
private Context applicationContext;
public LocationManager() {
}
public static LocationManager getInstance() {
if (null == instance) {
instance = new LocationManager();
}
return instance;
}
/**
* 声明定位回调监听器
*/
public AMapLocationListener mLocationListener = new AMapLocationListener() {
@Override
public void onLocationChanged(AMapLocation aMapLocation) {
if (aMapLocation != null) {
if (aMapLocation.getErrorCode() == 0) {
//获得json
String location = aMapLocation.toStr();
//启动IntentService 上传坐标数据
// 通过闹钟来开启定位服务,主要不是实时需要的,延迟执行
/*UploadService.UploadLocation(applicationContext, location);*/
//使用JobScheduler开启服务
JobManager.getInstance().addJob(location);
} else {
//定位失败时,可通过ErrCode(错误码)信息来确定失败的原因,errInfo是错误信息,详见错误码表。
Log.e("AmapError", "location Error, ErrCode:"
+ aMapLocation.getErrorCode() + ", errInfo:"
+ aMapLocation.getErrorInfo());
}
}
}
};
public void startLocation(Context context) {
if (null != mLocationClient) {
mLocationClient.startLocation();
return;
}
applicationContext = context.getApplicationContext();
//初始化定位
mLocationClient = new AMapLocationClient(applicationContext);
//设置定位回调监听
mLocationClient.setLocationListener(mLocationListener);
//声明AMapLocationClientOption对象
AMapLocationClientOption mLocationOption = null;
//初始化AMapLocationClientOption对象
mLocationOption = new AMapLocationClientOption();
mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);
//设置定位间隔,单位毫秒,默认为2000ms,最低1000ms。
mLocationOption.setInterval(5000);
//设置是否返回地址信息(默认返回地址信息)
mLocationOption.setNeedAddress(true);
//设置是否允许模拟位置,默认为true,允许模拟位置
mLocationOption.setMockEnable(true);
//单位是毫秒,默认30000毫秒,建议超时时间不要低于8000毫秒。
mLocationOption.setHttpTimeOut(20000);
//关闭缓存机制
mLocationOption.setLocationCacheEnable(false);
//给定位客户端对象设置定位参数
mLocationClient.setLocationOption(mLocationOption);
//启动定位
mLocationClient.startLocation();
}
public void stopLocation() {
if (null != mLocationClient) {
mLocationClient.stopLocation();//停止定位后,本地定位服务并不会被销毁
}
}
public void destoryLocation() {
if (null != mLocationClient) {
mLocationClient.unRegisterLocationListener(mLocationListener);
mLocationClient.onDestroy();//销毁定位客户端,同时销毁本地定位服务。
mLocationClient = null;
}
}
}
Demo中,我们使用一个闹钟服务开启定位,LocationService.java代码如下:
package com.dongnao.battery.location;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.app.job.JobScheduler;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.dongnao.battery.JobManager;
public class LocationService extends Service {
private PowerManager.WakeLock locationLock;
private Intent alarmIntent;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
JobManager.getInstance().init(this);
LocationManager.getInstance().startLocation(this);
//使用WeakLock
/*PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
//判断是否支持
pm.isWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK);
//只唤醒cpu
locationLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "location_lock");
locationLock.acquire();*/
//使用闹钟
alarmKeep();
}
@Override
public void onDestroy() {
super.onDestroy();
//释放
LocationManager.getInstance().destoryLocation();
//注销广播接收者
unregisterReceiver(alarmReceiver);
// if (null != locationLock) {
// locationLock.release();
// }
}
/**
* 维持一个闹钟服务。开启定位。
* 注意:使用闹钟做轮询适用于实时性要求不高的场景
*/
private void alarmKeep() {
alarmIntent = new Intent();
alarmIntent.setAction("LOCATION");
//创建延迟意图
PendingIntent broadcast = PendingIntent.getBroadcast(this, 0, alarmIntent, 0);
//获得闹钟管理器
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
//动态注册广播接受者
IntentFilter filter = new IntentFilter();
filter.addAction("LOCATION");
registerReceiver(alarmReceiver, filter);
//设置一个 每隔 5s 发送一个广播
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), 5_000, broadcast);
}
BroadcastReceiver alarmReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (TextUtils.equals(intent.getAction(), "LOCATION")) {
LocationManager.getInstance().startLocation(LocationService.this);
}
}
};
}
Battery.java文件是一个电量管理工具类,判断是否在充电、使用使用网络、是否添加到电量优化白名单等:
package com.dongnao.battery;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Build;
import android.os.PowerManager;
import android.provider.Settings;
/**
* 电量工具类
*/
public class Battery {
/**
* 添加到白名单,要求用户允许应用忽略电池优化
*/
public static void addWhite(Activity activity) {
PowerManager packageManager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
//应用是否在 白名单中
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
assert packageManager != null;
if (!packageManager.isIgnoringBatteryOptimizations(activity.getPackageName())) {
//方法1、启动一个 ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS Intent
// Intent intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
// activity.startActivity(intent);
//方法2、触发系统对话框
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + activity.getPackageName()));
activity.startActivity(intent);
}
}
}
/**
* 是否正在充电
*/
public static boolean isPlugged(Context context) {
//发送个包含充电状态的广播,并且是一个持续的广播
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent intent = context.registerReceiver(null, filter);
//获取充电状态
assert intent != null;
int isPlugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean acPlugged = isPlugged == BatteryManager.BATTERY_PLUGGED_AC;
boolean usbPlugged = isPlugged == BatteryManager.BATTERY_PLUGGED_USB;
boolean wifiPlugged = isPlugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
return acPlugged || usbPlugged || wifiPlugged;
}
/**
* 是否正在使用wifi
*/
public static boolean isWifi(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//获得当前活动的网络信息
assert cm != null;
NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
if (null != activeNetworkInfo && activeNetworkInfo.isConnected() && activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
return true;
}
return false;
}
}
MyJobService.java,使用到JobService类,为该项目重点:
package com.dongnao.battery;
import android.annotation.TargetApi;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.os.AsyncTask;
import android.os.Build;
import android.os.PersistableBundle;
import android.util.Log;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
public static final String TAG = "MyJobService";
@Override
public boolean onStartJob(JobParameters params) {
//如果返回值是false,这个方法返回时任务已经执行完毕。
//如果返回值是true,那么这个任务正要被执行,我们就需要开始执行任务。
//当任务执行完毕时你需要调用jobFinished(JobParameters params, boolean needsRescheduled)来通知系统
new MyAsyncTask().execute(params);
return true;
}
/**
* 当系统接收到一个取消请求时
*/
@Override
public boolean onStopJob(JobParameters params) {
//如果onStartJob返回false,那么onStopJob不会被调用
// 返回 true 则会重新计划这个job
return false;
}
/**
* Params:启动任务时输入的参数类型.
* <p>
* Progress:后台任务执行中返回进度值的类型.
* <p>
* Result:后台任务执行完成后返回结果的类型.
*/
class MyAsyncTask extends AsyncTask<JobParameters, Void, Void> {
JobParameters jobParameters;
@Override
protected Void doInBackground(JobParameters[] objects) {
jobParameters = objects[0];
Log.i(TAG, jobParameters.getJobId() + " 任务开始执行......");
PersistableBundle extras = jobParameters.getExtras();
String location = extras.getString("DATA");
Log.i(TAG, jobParameters.getJobId() + " 上传:" + location);
HttpURLConnection conn = null;
OutputStream os = null;
try {
conn = (HttpURLConnection) new URL("https://www.baidu.com/").openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
os = conn.getOutputStream();
os.write(location.getBytes());
os.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
Utils.safeColose(os);
if (null != conn) {
conn.disconnect();
}
}
return null;
}
/**
* doInBackground:必须重写,异步执行后台线程要完成的任务,耗时操作将在此方法中完成.
* <p>
* onPreExecute:执行后台耗时操作前被调用,通常用于进行初始化操作.
* <p>
* onPostExecute:当doInBackground方法完成后,系统将自动调用此方法,并将doInBackground方法返回的值传入此方法.通过此方法进行UI的更新.
* <p>
* onProgressUpdate:当在doInBackground方法中调用publishProgress方法更新任务执行进度后,将调用此方法.通过此方法我们可以知晓任务的完成进度.
*/
@Override
protected void onPostExecute(Void s) {
//当任务执行完毕之后,需要调用jobFinished来让系统知道这个任务已经结束,
//系统可以将下一个任务添加到队列中
//true表示需要重复执行
//false反之
jobFinished(jobParameters, false);
Log.i(TAG, jobParameters.getJobId() + "任务执行完成......");
}
}
}
最后是JobManager.java,对JobScheduler、JobInfo的使用,管理Job:
package com.dongnao.battery;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
import android.os.PersistableBundle;
import java.util.List;
/**
* JobScheduler、JobInfo的使用
*/
public class JobManager {
private static JobManager instance;
/**
* 把一些不是特别紧急(实时)的任务放到更合适的时机批量处理
* 1、避免频繁的唤醒硬件模块
* 2、避免在不合适的时候执行一些耗电的任务
* <p>
* JobScheduler适用于API 21及以上
*/
private JobScheduler jobScheduler;
private Context context;
private static final int JOB_ID = 0;
public static JobManager getInstance() {
if (null == instance) {
instance = new JobManager();
}
return instance;
}
public void init(Context context) {
this.context = context.getApplicationContext();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
}
}
/**
* 添加一个任务
*/
public void addJob(String location) {
if (null == jobScheduler) {
return;
}
JobInfo pendingJob = null;
//整合多个job
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//查找id是0的job
pendingJob = jobScheduler.getPendingJob(JOB_ID);
} else {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
List<JobInfo> allPendingJobs = jobScheduler.getAllPendingJobs();
for (JobInfo info : allPendingJobs) {
if (info.getId() == JOB_ID) {
pendingJob = info;
break;
}
}
}
}
//找到待执行的job
if (null != pendingJob) {
//多个坐标信息拼到一起 上传
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//数据 与Intent 一样
PersistableBundle extras = pendingJob.getExtras();
//获得上一次设置的location数据
String data = extras.getString("DATA");
//比如 多条坐标数据用@隔开
location = data + "@" + location;
jobScheduler.cancel(JOB_ID);
}
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
// jobid :0
PersistableBundle extras = new PersistableBundle();
extras.putString("DATA", location);
//创建一个job
JobInfo jobInfo = new JobInfo.Builder(JOB_ID, new ComponentName(context, MyJobService.class))
//只在充电的时候
.setRequiresCharging(true)
//不是蜂窝网络
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.setExtras(extras)
.build();
//提交任务
jobScheduler.schedule(jobInfo);
}
}
}
微信公众号: TechU