性能优化-电量优化

1 知识概览

电量优化.png

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会自动进行分析,如图所示:
uploadPage.png
uploading.pn
uploaded1.png
uploaded2.png
uploaded3.png
上传完成后,移动鼠标到分析页面可查看具体电量使用情况,请自己试用吧。

  注意:安装好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'
}

项目结构:
structure.png
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
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值