本篇文章主要介紹 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 乾貨分享
至此,本篇已結束。轉載網絡的文章,小編以爲很優秀,歡迎點擊閱讀原文,支持原創做者,若有侵權,懇請聯繫小編刪除,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!