背景
有用户反馈「智慧互联」APP 后台耗电过快,存在后台频繁定位问题:
问题分析
APP 是由多个开发人员输出的多个模块构建,还有很多第三方依赖,怎么确定是谁频繁进行定位?
需要找到定位总入口,跟踪代码发现最终定位都会调用:
LocationManager#requestLocationUpdates(@NonNull LocationRequest request, @NonNull PendingIntent intent)
接下来我们只要对requestLocationUpdates这个方法hook就可以了。
一般有两条路:
- 使用原生的反射 + 动态代理(限制接口使用)
- 使用
AOPhook(由于要hook的是Framework层,所以普通的修改APP字节码方式无效)
问题定位
动态代理方式过于繁琐,我选择AOP^_^,有很多开源的免root hook本进程Framework层的库。
这里选择了「太极」作者田维术的epic。
hook requestLocationUpdates:
try {
DexposedBridge.findAndHookMethod(
LocationManager.class,
"requestLocationUpdates",
Class.forName("android.location.LocationRequest"),
LocationListener.class,
Looper.class,
PendingIntent.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Log.i("hook", Thread.currentThread().getName());
Log.i("hook", Log.getStackTraceString(new Throwable()));
}
}
);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
运行观察发现方法调用栈输出都是:
I/hook: amapLocManagerThread
I/hook: java.lang.Throwable
at com.hsae.ag35.remotekey.MainApp$1.beforeHookedMethod(MainApp.java:53)
at de.robv.android.xposed.DexposedBridge.handleHookedArtMethod(DexposedBridge.java:229)
at me.weishu.epic.art.entry.Entry.onHookVoid(Entry.java:73)
at me.weishu.epic.art.entry.Entry.referenceBridge(Entry.java:167)
at me.weishu.epic.art.entry.Entry.voidBridge(Entry.java:87)
at android.location.LocationManager.requestLocationUpdates(LocationManager.java:503)
at com.loc.g.a(Unknown Source:191) <<==== 这个是高德 SDK 定位
at com.loc.d$a.handleMessage(Unknown Source:68)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:173)
at android.os.HandlerThread.run(HandlerThread.java:65)
at com.loc.d$b.run(Unknown Source:0) <<==== 运行在 amapLocManagerThread 线程中
全是高德SDK发起的定位操作,通过打印当前线程名称发现,高德定位都运行在amapLocManagerThread线程中。
为了dump出究竟是谁调用的高德SDK定位,需要再hook高德SDK定位入口,经过跟踪发现是:
com.loc.d#startLocation()
hook startLocation:
try {
DexposedBridge.findAndHookMethod(
Class.forName("com.loc.d"),
"startLocation",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Log.i("hook", Thread.currentThread().getName());
Log.i("hook", Log.getStackTraceString(new Throwable()));
}
}
);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
输出举例:
I/hook: main
I/hook: java.lang.Throwable
at com.hsae.ag35.remotekey.MainApp$1.beforeHookedMethod(MainApp.java:66)
at de.robv.android.xposed.DexposedBridge.handleHookedArtMethod(DexposedBridge.java:229)
at me.weishu.epic.art.entry.Entry.onHookVoid(Entry.java:73)
at me.weishu.epic.art.entry.Entry.referenceBridge(Entry.java:167)
at me.weishu.epic.art.entry.Entry.voidBridge(Entry.java:87)
at com.amap.api.location.AMapLocationClient.startLocation(Unknown Source:6)
at com.amap.api.col.n3.hs.a(Unknown Source:8)
at com.amap.api.col.n3.dd.activate(AMapLocationSource.java:170)
at com.amap.api.col.n3.bn.setMyLocationEnabled(AMapDelegateImp.java:3955)
at com.amap.api.maps.AMap.setMyLocationEnabled(AMap.java:1128)
at com.hsae.ag35.remotekey.nav.map.MyMapFragment.initAMap(MyMapFragment.java:542) <<==== AMap.setMyLocationEnabled(true) 打开定位图层发起的定位
at com.hsae.ag35.remotekey.nav.map.MyMapFragment.access$400(MyMapFragment.java:60)
at com.hsae.ag35.remotekey.nav.map.MyMapFragment$2.run(MyMapFragment.java:211)
at android.os.Handler.handleCallback(Handler.java:793)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:173)
at android.app.ActivityThread.main(ActivityThread.java:6698)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:782)
这次终于把初始调用链全部输出了。
问题结论
发现了几处意料之外的情形(因缺思厅):
AMap.setMyLocationEnabled(true)
设置打开定位图层(myLocationOverlay),APP里设置的LocationStyle是只定位一次,但是经过hook打印调用链发现,每隔 5 分钟AMap.setMyLocationEnabled(true)就会发起一次定位,啊???
AMapNavi.getInstance(Context context)
获取导航对象(单例),这个居然也会发起一次定位操作?
问题解决
针对AMap.setMyLocationEnabled(true)每隔 5 分钟发起定位问题,采用当定位成功时关闭定位图层:AMap.setMyLocationEnabled(false)
备注
本文用例使用的高德版本是 2019-11-01 发出的 V7.1.0:
implementation 'com.amap.api:navi-3dmap:7.1.0_3dmap7.1.0'
implementation 'com.amap.api:search:7.1.0'
implementation 'com.amap.api:location:4.7.2'