美文网首页
[转]高德地图导航功耗场景优化

[转]高德地图导航功耗场景优化

作者: 程序员Android1 | 来源:发表于2020-05-11 20:21 被阅读0次

    和你一起终身学习,这里是程序员 Android

    本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以下内容:

    一、测试现象
    二、分析
    三、问题根源研究
    四、源码分析并新增日志
    五、问题发现与解决

    一、测试现象

    1.1 EPRODUCING PROCEDURES:

    1.进入高德 Map(data) wifi),起点为自己所在位置,搜索一个位置进行导航;
    2.等待30秒后开始记录电流,持续5分钟;
    3.按Power键,灭屏待机;
    4.手机灭屏15秒后开始记录电流,持续3分钟;

    1.2 Test numberSummary

    二、分析

    2.1 拔屏以扣除LCD影响

    从上述看扣掉 LCD 同样有功耗差异,即LCD 关系不大

    注意:导航场景:一般是带导航语音,即需要考虑Audio的影响

    2.2 设计实验找出问题差异

    以下是我自己设计的实验方法

    1.对比机与测试机的GPS搜星实验

    1.GPS puls 搜星查看,我apk放到附件中
    2.只开启GPS+飞行模式
    3.尽量2台机器相同环境下同时测试
    4.放置5分钟后,apk主界面截图

    实验发现:基本无差异,故 GPS 单体应该是没问题

    2.导航电流测试

    最暗亮度测试,避免扣掉屏幕带来的影响
    静音+插入耳机,去除导航语音带来的影响
    尽量2台机器相同环境下同时测试

    GPS+插卡+WiFi 连接 169.64 153.48
    case 1 目的: 对比相同亮度下是否存在差异
    case 2 目的: 查看是否网络定位有问题
    case 3 目的:查看是否GPS定位存问题
    case 3 目的: 查杀是否与GPS信号强度有关,因为室内是GPS信号比较弱
    上述:

    case 1 说明测试机和对比机亮屏下相同亮度的测试电流是基本一致,即基础的系统功耗是相同的
    case 2 说明网络定位是OK的,说明相同APK在不同机器测试,运行是后台也基本保持一致,且说明如果GPS不开启则是导航是好的;
    case 3、case 4 说明GPS开启,会带来导航功耗影响

    3 .GPS单体功耗

    这块数据没有,故需要让硬件提供:

    测试步骤:

    Wi-Fi Off, GPS ON, BT OFF, NFC OFF, Fly mode ON.
    灭屏,等待1min,记录平均电流,即是打开GPS时的电流。
    在上述的条件时,记录下可以稳定复现的最小电流值,即是 GPS floor current.
    Average current when GPS navigation working:

    • Step1:通过下拉菜单,打开飞行模式,打开位置服务;
    • Step2:进入工程模式 adb shell am start -n com.mediatek.ygps/com.mediatek.ygps.YgpsActivity;
    • Step3:进入Location->YGPS->Enable in BG.(need restart),重启后再次进入该界面;
    • Step4:关闭屏幕,待电流稳定,测试3分钟,记录电流A1;
    • Step5:通过下拉菜单,关闭位置服务;
    • Step6:进入工程模式,进入Location->YGPS,再回到YGPS界面;
    • Step7:关闭屏幕,待电流稳定,测试3分钟,记录电流A2;
      GPS ON=A1-A2

    由于不知道 ygps 源码,通过测试步骤基本猜到,主要是测试GPS开启电流和工作电流,相当于高德地图的定位场景,非导航场景

    测试数据:

    从上面数据看,GPS的工作电流是基本一致的,即GPS单体是OK的

    这就奇怪了,GPS单体是好的,为什么导航一开GPS就存问题呢?还需要再排除干扰。例如以下干扰:

    WiFi 环境因素
    4G 插卡因素
    应用启动后会进行页面、地图数据、配置文件下载
    测试过程中,如果网络发生变化,热点不稳定

    4 .离线导航

    Wi-Fi Off, GPS ON, BT OFF, NFC OFF, Fly mode ON,排除WiFi带来的干扰
    最小亮度
    使用 what_temp apk抓CPU数据
    静音插耳机
    使用高德离线地图导航

    上述查看:

    现象复现,即排除了Wi-Fi Off, GPS ON, BT OFF, NFC OFF, Fly mode影响,即wifi、4G都没问题
    定位场景差异不大,导航场景差异比较大。
    需要查看高德地图定位细节

    2.3 高德地图的dump信息

    dumpsys location|grep -B 2 “com.autonavi.minimap”
    
    Tokyo_Lite:/ $ dumpsys location|grep -B 2 "com.autonavi.minimap"
      Battery Saver Location Mode: NO_CHANGE
      Location Listeners:
        Reciever[35eed63 listener UpdateRecord[passive com.autonavi.minimap(10167 foreground) Request[POWER_NONE passive fastest=0] null], isNeedBgGpsRestrict false monitoring location: true]
        Reciever[fe54b41 listener UpdateRecord[passive android(1000 foreground) Request[POWER_NONE passive fastest=+30m0s0ms] null], isNeedBgGpsRestrict false monitoring location: true]
        Reciever[a523146 listener UpdateRecord[passive com.autonavi.minimap(10167 foreground) Request[POWER_NONE passive fastest=+1s0ms] null], isNeedBgGpsRestrict false monitoring location: true]
        Reciever[4698940 listener UpdateRecord[passive android(1000 foreground) Request[POWER_NONE passive fastest=0] null], isNeedBgGpsRestrict false monitoring location: false]
        Reciever[3e939ea listener monitoring location: false]
        Reciever[a629ca9 listener UpdateRecord[gps com.autonavi.minimap(10167 foreground) Request[ACCURACY_FINE gps requested=+1s0ms fastest=+1s0ms] null], isNeedBgGpsRestrict false monitoring location: true]
      Active Records by Provider:
        gps:
          UpdateRecord[gps com.autonavi.minimap(10167 foreground) Request[ACCURACY_FINE gps requested=+1s0ms fastest=+1s0ms] null], isNeedBgGpsRestrict false
    --
          UpdateRecord[passive android(1000 foreground) Request[POWER_NONE passive fastest=0] null], isNeedBgGpsRestrict false
          UpdateRecord[passive android(1000 foreground) Request[POWER_NONE passive fastest=+30m0s0ms] null], isNeedBgGpsRestrict false
          UpdateRecord[passive com.autonavi.minimap(10167 foreground) Request[POWER_NONE passive fastest=0] null], isNeedBgGpsRestrict false
          UpdateRecord[passive com.autonavi.minimap(10167 foreground) Request[POWER_NONE passive fastest=+1s0ms] null], isNeedBgGpsRestrict false
    --
      Active GnssNavigationMessage Listeners:
      Active GnssStatus Listeners:
        5600 10167 com.autonavi.minimap: false
        5600 10167 com.autonavi.minimap: false
        5600 10167 com.autonavi.minimap: false
        5766 10167 com.autonavi.minimap: false
      Historical Records by Provider:
        android: passive: Min interval 0 seconds: Max interval 1800 seconds: Duration requested 99 total, 99 foreground, out of the last 99 minutes: Currently active
        com.autonavi.minimap: passive: Min interval 0 seconds: Max interval 1 seconds: Duration requested 2 total, 2 foreground, out of the last 98 minutes: Currently active
        com.autonavi.minimap: gps: Interval 1 seconds: Duration requested 2 total, 2 foreground, out of the last 98 minutes: Currently active
    

    查看高德地图的dump信息,我们基本知道高德地图申请了哪些定位:

    GPS 定位,定位间隔1秒一个

    Reciever[a629ca9 listener UpdateRecord[gps com.autonavi.minimap(10167 foreground) Request[ACCURACY_FINE gps requested=+1s0ms fastest=+1s0ms] null], isNeedBgGpsRestrict false monitoring location: true]
    

    passive 定位

    Reciever[a523146 listener UpdateRecord[passive com.autonavi.minimap(10167 foreground) Request[POWER_NONE passive fastest=+1s0ms] null], isNeedBgGpsRestrict false monitoring location: true]
    

    监听了 GnssStatus 数据

    Active GnssStatus Listeners:
    5600 10167 com.autonavi.minimap: false
    5600 10167 com.autonavi.minimap: false
    5600 10167 com.autonavi.minimap: false
    5766 10167 com.autonavi.minimap: false
    

    2.4 分析高德地图定位和导航调用的函数

    我们在dump locaiton已经知道高德地图会申请使用GPS,并且定位间隔为1秒,故不需要在

    1) LocationManager 埋点

    public class LocationManager {
    
        public boolean addNmeaListener(
            @NonNull OnNmeaMessageListener listener, @Nullable Handler handler) {
        boolean result;
    
        // huazhi.su
        if("true".equals(SystemProperties.get("persist.sys.tct.addNmeaListener.debug", "false"))) {
            Log.e(TAG, "skip addNmeaListener");
            return false;
        }
        // huazhi.su
        
        @RequiresPermission(ACCESS_FINE_LOCATION)
        public boolean registerGnssStatusCallback(
                @NonNull GnssStatus.Callback callback, @Nullable Handler handler) {
            boolean result;
            synchronized (mGnssStatusListeners) {
                // huazhi.su
                if("true".equals(SystemProperties.get("persist.sys.tct.registerGnssStatusCallback.debug", "false"))) {
                    if("com.autonavi.minimap".equals(mContext.getPackageName())) {
                        Log.e(TAG, "skip registerGnssStatusCallback  packageName:" + mContext.getPackageName());
                    }
                    return false;
                }
                // huazhi.su
    

    2) 查看导航场景高德地图使用的sdk情况

    场景 API接口

    即导航场景比定位场景多了 addNmeaListener

    12-06 14:10:44.728  3716  3716 W System.err:    at android.location.LocationManager.addNmeaListener(LocationManager.java:2046)
    12-06 14:10:44.736  3716  4034 W System.err:    at android.location.LocationManager.registerGnssStatusCallback(LocationManager.java:1944)
    
        locationManager.registerGnssStatusCallback(new GnssStatus.Callback() {
            @Override
            public void onSatelliteStatusChanged(GnssStatus status) {
                super.onSatelliteStatusChanged(status);
                Log.d(TAG, "onSatelliteStatusChanged");
            }
        });
    
        locationManager.addNmeaListener(new OnNmeaMessageListener() {
            @Override
            public void onNmeaMessage(String message, long timestamp) {
                Log.d(TAG, "onNmeaMessage message :" + message + ", timestamp :" + timestamp);
                sendRequestWithHttpClient();
            }
        });
        
        locationManager.requestLocationUpdates(GPS_PROVIDER, 1000, 0, mMyLocationListener);
    
    

    之前的测试GPS单体的功耗基本是 requestLocationUpdates 这个接口的功能,故 基本问题不大
    且导航和定位场景中,导航场景存在和定位场景不同,故我们可以单独屏蔽掉 registerGnssStatusCallback、addNmeaListener

    三、 问题根源研究

    3.1 禁止监听 GnssStatus 和 Nmea

    首先我们看下异常电流:特征是一秒钟一个波峰,且单个异常波形平均电流就有166mA,基本功耗不被拉大才怪


    上述说明:监听 Nmea + GnssStatus 会带来功耗,但是肯定要给第三方应用正常使用这个数据

    3.2 拦截 GnssStatus 和 Nmea

    拦截位置

    package com.android.server.location;
    
    public class GnssLocationProvider ...{
    
        @NativeEntryPoint // libgnss.so上报
        private void reportNmea(long timestamp) {
            // 新增拦截
            ...
            // 对应上层的:public void onNmeaMessage(String message, long timestamp) 方法
            ...
        }
        
        @NativeEntryPoint // libgnss.so上报
        private void reportSvStatus(int svCount, int[] svidWithFlags, float[] cn0s,
            // 新增拦截
            ...
            // 对应上层的:public void onSatelliteStatusChanged(GnssStatus status) { 方法
            ...
        }
    
    }
    

    电流波形
    拦截测试也说明 Nmea + GnssStatus 有影响,难道高德地图会收到数据做一些耗电操作,还是对比机有优化呢?
    由于高德地图、对比机没有源码,所以我们无法知道,从源码一个函数一个函数地分析。看是否源代码有问题,即了解GPS的Nmea + GnssStatus上报给上层的源码

    四、源码分析并新增日志

    4.1 源码数据通路新增日志

    例如 Nmea 数据如何上报给上层apk,在这个数据传递的通路新增日志,每个函数调用的地方都加

    1.数据源头-------------------------------------------------
    package com.android.server.location;
    
        @NativeEntryPoint
        private void reportNmea(long timestamp) {
            if (!mItarSpeedLimitExceeded) {
                int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length);
                String nmea = new String(mNmeaBuffer, 0 /* offset */, length);
                mGnssStatusListenerHelper.onNmeaReceived(timestamp, nmea);
            }
        }
    
    2.-------------------------------------------------
    public abstract class GnssStatusListenerHelper extends RemoteListenerHelper<IGnssStatusListener> {
            public void onNmeaReceived(final long timestamp, final String nmea) {
            // 遍历 addNmeaListener 监听数量,例如高德地图使用了2个,一个定位注册的,一个导航场景注册的
            foreach((IGnssStatusListener listener, CallerIdentity callerIdentity) -> {
                // 检查是否与定位权限
                if (hasPermission(mContext, callerIdentity)) {
                    logPermissionDisabledEventNotReported(TAG, callerIdentity.mPackageName, "NMEA");
                    return;
                }
                // 消息传递到上层
                listener.onNmeaReceived(timestamp, nmea);
            });
        }
    }
    3.-------------------------------------------------
    public class LocationManager {
    
        @Override
        public void onNmeaReceived(long timestamp, String nmea) {
            ...
            mGnssHandler.obtainMessage(NMEA_RECEIVED).sendToTarget();
            ...
        }
        
        for (Nmea nmea : mNmeaBuffer) {
            // 数据给上层
            mGnssNmeaListener.onNmeaMessage(nmea.mNmea, nmea.mTimestamp);
        }
    

    4.2 现象发现

    1)日志大量打印,这里1秒钟70组数据从底层上报出来,都需要经过上述的函数,简直是太频繁

    01-01 00:04:54.516  3740  4447 E LocationManager: skip onNmeaReceived: nmea$GNGGA,160454.011,8960.0000,N,00000.0000,E,0,0,,137.0,M,13.0,M,,*5F
    01-01 00:04:54.516  3328  4561 E LocationManager: skip onNmeaReceived: nmea$GNGGA,160454.011,8960.0000,N,00000.0000,E,0,0,,137.0,M,13.0,M,,*5F
    01-01 00:04:54.518  3328  4561 E LocationManager: skip onNmeaReceived: nmea$GNGGA,160454.011,8960.0000,N,00000.0000,E,0,0,,137.0,M,13.0,M,,*5F
    01-01 00:04:54.519  3740  4447 E LocationManager: skip onNmeaReceived: nmea$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30
    01-01 00:04:54.520  3328  4561 E LocationManager: skip onNmeaReceived: nmea$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30
    01-01 00:04:54.522  3328  4051 E LocationManager: skip onNmeaReceived: 
    
    

    2)异常发现

    发现只要去掉 hasPermission 的调用,功耗就能掉下来,没想到是这个函数搞得鬼

        public void onNmeaReceived(final long timestamp, final String nmea) {
    
            foreach((IGnssStatusListener listener, CallerIdentity callerIdentity) -> {
                // huazhi.su add start
                if("true".equals(SystemProperties.get("persist.sys.Helper.hasPermission", "false"))) {
                    // 实测1秒中被调用了70次,去除后平均电流减少20mA
                    // 这个判断作用:若用户定位的时候突然关闭GPS权限,则也要相应停止数据上报到上层
                    // 出发点是好:但是1秒钟判断了70次就不厚道了,且不同应用注册的数量不同,故这里的次数也不一样
                    if (!hasPermission(mContext, callerIdentity)) {
                        logPermissionDisabledEventNotReported(TAG, callerIdentity.mPackageName, "NMEA");
                        return;
                    }
                }
                // huazhi.su add end
                listener.onNmeaReceived(timestamp, nmea);
    
            });
            
        protected boolean hasPermission(Context context, CallerIdentity callerIdentity) {
            if (LocationPermissionUtil.doesCallerReportToAppOps(context, callerIdentity)) {
                // The caller is identified as a location provider that will report location
                // access to AppOps. Skip noteOp but do checkOp to check for location permission.
                return mAppOps.checkOpNoThrow(AppOpsManager.OP_FINE_LOCATION, callerIdentity.mUid,
                        callerIdentity.mPackageName) == AppOpsManager.MODE_ALLOWED;
            }
    
            return mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, callerIdentity.mUid,
                    callerIdentity.mPackageName) == AppOpsManager.MODE_ALLOWED;
        }
    

    上述中 reportSvStatus 也使用到了 hasPermission 的判断,但是实测回调的次数没有 reportNmea多,故功耗电流小一些

        public void onSvStatusChanged (
            ...
            hasPermission(mContext, callerIdentity)
            ...
        }
    

    五、问题发现与解决

    5.1. 功耗问题原因

    去掉了 hasPermission 就好,说明系统也经不起频繁执行某个函数的方法

    5.2. 分析

    源码也存在问题,为什么这么频繁调用的地方,每次都是执行一个函数,干嘛不把这个变量的状态保持下来。读状态比去执行函数的效率高多了。
    估计 Google 写这个函数的时候,没考虑的这里实际会被频繁调用到

    5.3. 解决方案

    监听权限变化事件,当权限改变的时候,更新下对应uid的权限,并保存起来

    context.getPackageManager().addOnPermissionsChangeListener(mOnPermissionsChangedListener);
    
        private OnPermissionsChangedListener mOnPermissionsChangedListener = new OnPermissionsChangedListener() {
    
            public void onPermissionsChanged(int uid) {
                updatePermissionsChanged(uid);
            }
        };
    

    若当前保存了uid的权限状态,则读状态,而不是每次执行函数获取,提高效率

        private boolean isHasPermission(Context context, CallerIdentity callerIdentity) {
            if(mPermissionsList.containsKey(callerIdentity.mUid)) {
                return mPermissionsList.get(callerIdentity.mUid);
            } else {
                boolean isHasPermission = hasPermission(mContext, callerIdentity);
                mPermissionsList.put(callerIdentity.mUid, isHasPermission);
                return isHasPermission;
            }
        }
    

    5.4 高德地图导航场景电流优化了 30mA

    优化后电流由 133mA 下降到100mA, 1秒钟一个的异常波形也消失了。
    由于这里数据是我自己测的

    原文作者:法迪
    原文链接:csdn/su749520/java/article/details/103437198

    至此,本篇已结束。转载网络的文章,小编觉得很优秀,欢迎点击阅读原文,支持原创作者,如有侵权,恳请联系小编删除,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!

    相关文章

      网友评论

          本文标题:[转]高德地图导航功耗场景优化

          本文链接:https://www.haomeiwen.com/subject/fmzbvhtx.html