本文介绍使用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/代替。本项目所有资料均在文末的项目地址中。 接下来就是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); } } }项目地址: https://github.com/keyyoo/Android_Advanced_Series/tree/master/专题3-性能优化/Lsn9_电量优化