高德 SDK 偷偷定位?

1,857 阅读2分钟

背景

有用户反馈「智慧互联」APP 后台耗电过快,存在后台频繁定位问题:

问题分析

APP 是由多个开发人员输出的多个模块构建,还有很多第三方依赖,怎么确定是谁频繁进行定位?
需要找到定位总入口,跟踪代码发现最终定位都会调用:

LocationManager#requestLocationUpdates(@NonNull LocationRequest request, @NonNull PendingIntent intent)

接下来我们只要对requestLocationUpdates这个方法hook就可以了。
一般有两条路:

问题定位

动态代理方式过于繁琐,我选择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'

参考

  1. epic
  2. 使用原生的反射 + 动态代理