本篇文章主要介绍 Android
开发中的部分知识点,经过阅读本篇文章,您将收获如下内容:java
1、测试现象
2、分析
3、问题根源研究
4、源码分析并新增日志
5、问题发现与解决
1.进入高德 Map(data) wifi),起点为本身所在位置,搜索一个位置进行导航;
2.等待30秒后开始记录电流,持续5分钟;
3.按Power键,灭屏待机;
4.手机灭屏15秒后开始记录电流,持续3分钟;android
从上述看扣掉 LCD 一样有功耗差别,即LCD 关系不大程序员
注意:导航场景:通常是带导航语音,即须要考虑Audio的影响shell
如下是我本身设计的实验方法网络
1.GPS puls 搜星查看,我apk放到附件中
2.只开启GPS+飞行模式
3.尽可能2台机器相同环境下同时测试
4.放置5分钟后,apk主界面截图ide
实验发现:基本无差别,故 GPS 单体应该是没问题函数
最暗亮度测试,避免扣掉屏幕带来的影响
静音+插入耳机,去除导航语音带来的影响
尽可能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 三、case 4 说明GPS开启,会带来导航功耗影响测试
这块数据没有,故须要让硬件提供:
测试步骤:
Wi-Fi Off, GPS ON, BT OFF, NFC OFF, Fly mode ON.
灭屏,等待1min,记录平均电流,便是打开GPS时的电流。
在上述的条件时,记录下能够稳定复现的最小电流值,便是 GPS floor current.
Average current when GPS navigation working:
GPS ON=A1-A2
因为不知道 ygps 源码,经过测试步骤基本猜到,主要是测试GPS开启电流和工做电流,至关于高德地图的定位场景,非导航场景
测试数据:
从上面数据看,GPS的工做电流是基本一致的,即GPS单体是OK的
这就奇怪了,GPS单体是好的,为何导航一开GPS就存问题呢?还须要再排除干扰。例如如下干扰:
WiFi 环境因素
4G 插卡因素
应用启动后会进行页面、地图数据、配置文件下载
测试过程当中,若是网络发生变化,热点不稳定
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都没问题
定位场景差别不大,导航场景差别比较大。
须要查看高德地图定位细节
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
咱们在dump locaiton已经知道高德地图会申请使用GPS,而且定位间隔为1秒,故不须要在
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
场景 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
首先咱们看下异常电流:特征是一秒钟一个波峰,且单个异常波形平均电流就有166mA,基本功耗不被拉大才怪
上述说明:监听 Nmea + GnssStatus 会带来功耗,可是确定要给第三方应用正常使用这个数据
拦截位置
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上报给上层的源码
例如 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); }
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:
发现只要去掉 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) ... }
去掉了 hasPermission 就好,说明系统也经不起频繁执行某个函数的方法
源码也存在问题,为何这么频繁调用的地方,每次都是执行一个函数,干吗不把这个变量的状态保持下来。读状态比去执行函数的效率高多了。
估计 Google 写这个函数的时候,没考虑的这里实际会被频繁调用到
监听权限变化事件,当权限改变的时候,更新下对应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; } }
优化后电流由 133mA 降低到100mA, 1秒钟一个的异常波形也消失了。
因为这里数据是我本身测的
原文做者:法迪
原文连接:https://blog.csdn.net/su74952...
友情推荐:
Android 干货分享
至此,本篇已结束。转载网络的文章,小编以为很优秀,欢迎点击阅读原文,支持原创做者,若有侵权,恳请联系小编删除,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!