关于android:高德地图导航功耗场景优化

9次阅读

共计 11312 个字符,预计需要花费 29 分钟才能阅读完成。

和你一起一生学习,这里是程序员 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 秒钟一个的异样波形也隐没了。
因为这里数据是我本人测的

原文作者:法迪
原文链接:https://blog.csdn.net/su74952…

情谊举荐:
Android 干货分享

至此,本篇已完结。转载网络的文章,小编感觉很优良,欢送点击浏览原文,反对原创作者,如有侵权,恳请分割小编删除,欢迎您的倡议与斧正。同时期待您的关注,感谢您的浏览,谢谢!

正文完
 0