基于基站网络定位的源码分析

Posted 郭霖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于基站网络定位的源码分析相关的知识,希望对你有一定的参考价值。



今日科技快讯


9月24日,路透社曝光了刘强东事件受害人的求救微信,对此刘强东的刑事辩护律师 Jill Brisbois 表示,她曾对路透社表示:这些指控与我们希望在结案后向公众披露的证据不一致,刘强东没有违反任何法律。


作者简介


大家周二好,中秋过后努力一周迎接国庆吧,这周有六天哦!

本篇转自 健身营养爱好者的文章,分享了关于android网络定位源码分析的相关内容,一起来看看!希望大家喜欢。

https://www.jianshu.com/u/673ede4a1b70


开始


App中基本都会用到定位服务,NLP即NetworkLocationProvider,是位置提供服务的一种,谷歌有提供自己的NLP,在国内厂商一般会集成百度或高德的NLP。

下面是我绘制的一张定位服务架构图,一共分为四层,每层都依赖下面一层完成其所需提供的服务:

  • 应用层

是android.location包中包含的内容,主要通过LocationManager来进行方法调用;

  • 框架层

这一层包含了系统服务的实现,LocationManager通过Binder机制来和LocationManagerService进行通讯,LocationManagerService会选择合适的provider来提供位置,其中LocationProviderProxy的一个实现就是NLP,可以理解为LocationProviderProxy和GeocoderProxy都是一个空壳,如果没有第三方实现他们,那么将不提供服务,如果使用了GpsLocationProvider则会去调用硬件来获取位置;

  • 共享库层

GpsLocationProvider通过JNI来调用本层libgps.so中的C++代码;

  • Linux内核层

C++代码最终去调用GPS硬件来获取位置;

基于基站网络定位的源码分析

https://www.jianshu.com/u/673ede4a1b70


正文


为什么要分析源码

在做NLP的时候对于和系统交互这块一直都充满疑惑:

  1. App调用请求是如何到了NLP的?

  2. 不同的App同一时刻发起定位系统是如何处理的?

  3. 为什么取消定位有的时候传off,有的时候传on,off是啥意思?

  4. 为什么请求单次定位会回调2次onSetRequest方法?

  5. NLP上抛的位置又是如何返给App的呢?

  6. onSetRequest到底是该怎么用?

为了对NLP有一个更加深入的理解,我决定带着这些问题去看下安卓定位相关的源码,告别过去对系统行为的种种猜测。所以这篇文章不仅仅是源码解析,更是结合了日常开发NLP时候遇到的各种问题,各种测试现象,从源码里去找出答案。

LocationManager分析

App调用定位接口是通过LocationManager的API,那就先从LocationManager入手,我查看的是安卓8.0的源码,发现它的很多方法都是代理了service的一些方法,这个service的声明类型是ILocationManager,这个对象就是代理对象,很显然是AIDL的调用,具体实现类则是LocationManagerService,LocationManager和LocationManagerService就是通过Binder 机制来进行通讯的。

private void requestLocationUpdates(LocationRequest request, LocationListener listener,
            Looper looper, PendingIntent intent)
 
{
        String packageName = mContext.getPackageName();
        // wrap the listener class
        ListenerTransport transport = wrapListener(listener, looper);
        try {
            mService.requestLocationUpdates(request, transport, intent, packageName);
       } catch (RemoteException e) {
           throw e.rethrowFromSystemServer();
       }
    }

LocationManager提供的主要方法有:

  1. getLastKnownLocation:获取上一次缓存的位置,这个方法不会发起定位请求,返回的是上一次的位置信息,但此前如果没有位置更新的话,返回的位置信息可能是错误的;

  2. requestSingleUpdate:只请求一次定位,会发起位置监听,该方法要在主线程上执行,可以传入Listener或广播来接收位置;

  3. requestLocationUpdates:持续请求定位,根据传入的时间间隔和位置差进行回调,该方法要在主线程上执行,可以传入Listener或广播来接收位置;

  4. removeUpdates:移除定位请求,传入Listener;

  5. addProximityAlert:添加一个地理围栏,这是一个圆形的围栏;

  6. getProvider:获取Provider,可以指定条件,也可以根据名字来获取;

  7. sendExtraCommand:给系统发送辅助指令;

这些方法的最终都是由service来实现的,发起定位时传入的Listener经过包装成AIDL接口传给了服务端,因为它们是需要跨进程来进行通讯的。

这里分析一下requestSingleUpdate方法,这个方法主要是传一个Listener,然后内部创建了一个LocationRequest,最小时间和最小距离都是0,还给singleShot设置为了true,并最终调用requestLocationUpdates方法,所以requestLocationUpdates才是核心,而所有定制的参数都封装成了LocationRequest。

基于基站网络定位的源码分析

那么接下来看下LocationRequest的createFromDeprecatedProvider方法,这里把传来的最小时间频率,最小距离差值存下,设置了定位的精度类型,如果singleShot为true,会设置locationRequest.setNumUpdates(1),numUpdate这个变量的默认值是一个很大的数,Integer.MAX_VALUE = 0x7fffffff,而单次定位g该值就设为了1,这个点在分析service的代码时会用到。

基于基站网络定位的源码分析

LocationManagerService的初始化

获取LocationManager是调用Activity的getSystemService(Context.LOCATION_SERVICE)方法来获得,那么它的初始化是在哪里呢?我们知道Activity是Context,那么这个方法的最终实现就是在ContextImpl类里面,所以我们看下ContextImpl的getSystemService方法,它调用了SystemServiceRegistry的getSystemService方法。

基于基站网络定位的源码分析

在SystemServiceRegistry类里,有LocationManager向ServiceManager注册的代码,这个类是6.0新加的,主要是用来缓存,注册,获取系统服务的,早期安卓版本直接在ComtextImpl里面实现了。

基于基站网络定位的源码分析

ServiceManager是安卓系统专门用来管理系统服务的,它负责注册并管理所有的系统服务。可以把ServiceManager当做一个容器,它里面存储了系统所有的服务,比如PackageManagerService,ActivityManagerService,AlarmManagerService等等,通过对应的Key就可以获得对应的服务。我们可以获取定位服务的实现类对象,然后再通过 ILocationManager.Stub.asInterface(b) 将其转换成服务的代理存放到 LocationManager中。

那 ServiceManager 中所管理的系统服务对象又是从哪里来的呢?在 Android 系统启动过程中,需要完成一系列的初始化动作。在Java层最终会调用到ZygoteInit类中,会调用startSystemServer 方法来启动系统服务。启动的方法是 fork一个新的进程,然后在其中加载SystemServer类。在SystemServer中执行了系统服务的创建和注册。以LocationManagerService为例,在SystemServer的startOtherService中有以下代码:

基于基站网络定位的源码分析

在这个类里面new出了LocationManagerService对象,并把它加入到ServiceManager容器里面,当然还有其他的服务也会被加入到ServiceManager里面,然后走它的systemRunning方法;

基于基站网络定位的源码分析

那么接下来看LocationManagerService这个类,在systemRunning方法里,做了一系列初始化的操作,其中一个重要的方法就是loadProvidersLocked(),它就是来加载provider的;位置服务的提供者是LocationProvider,它包含3种:GPS_PROVIDER,NETWORK_PROVIDER,PASSIVE_PROVIDER,BaiduNLP就是NETWORK_PROVIDER的一种。大概看下这个类的成员变量,就知道这个类的很多工作就是来管理这些provider的,那么来看下loadProvidersLocked()这个方法;

基于基站网络定位的源码分析

首先创建了一个PassiveProvider,并把它加到可用的provider里面,也就是PassiveProvider始终可用,然后根据GPS是否可用,增加一个GpsLocationProvider,这个代码在不同的系统版本上还是有许多差别的,原来是GpsLocationProvider,现在改成了GnssLocationProvider,另外PassiveProvider的创建顺序也发生了改变。

基于基站网络定位的源码分析

接下来就是加载NetworkLocationProvider了, LocationProviderProxy是对NetworkLocationProvider的代理,而第三方NLP才是NetworkLocationProvider的具体实现,这里会根据XML文件中配置的布尔值,包名和字符串数组去绑定指定action的服务,如果bind成功就把它加入到可用provider中。那么实现方必然要创建一个Service来实现LocationProviderProxy中使用的AIDL对象的接口,这个类名没有具体要求,但是Service必须要对指定的action进行绑定并返回binder对象才能被唤醒。所以有的时候会遇到第三方NLP没有被厂商bind上,后续就无法通过第三方NLP来获取位置。

这个action是"com.android.location.service.v3.NetworkLocationProvider",这个action在不同系统上可能会不同,所以需要适配v2,v3,否则可能会出现无法绑定的情况。

基于基站网络定位的源码分析

那么接下来继续看LocationProviderProxy的createAndBind方法,在这里创建了一个ServiceWatcher对象,然后执行了它的start方法。ServiceWatcher是用来连接和监视应用程序实现LocationProvider服务的,成功binder到服务后,会对该服务进行监控,包的卸载,加载、安装都会引起rebinder动作,它实现了ServiceConnection,在构造函数里,把xml中的配置项都传了过来,包括一个boolean值overlay(覆盖),一个字符串数组,一个默认的服务字符串,如果开启覆盖即overlay=true,则使用字符串数组中指定包名的provider,如果不覆盖,则使用包名字符串中的provider来提供服务。

基于基站网络定位的源码分析

在ServiceWatcher的start方法里,执行了bindBestPackageLocked这个关键的方法,在这个方法里,先去给intent设置之前提到的Action,然后根据传来的包名去查询service,如果前面使用了字符串数组,那么包名就是空的,接着对遍历出来的service做签名效验。

基于基站网络定位的源码分析

这里需要注意的是必须要在NLP的service里配置metadata属性,给service_version配置value。因为这段代码会去读这个字段,没有就赋值为Integer.MIN_VALUE,也就是-2147483648,而version的初始值也是-2147483648,所以version > bestVersion条件通不过,那bestComponent就是null,所以无法绑定。

基于基站网络定位的源码分析

找到bestComponent后,就会调用bindToPackageLocked方法,在这里又调用了bindServiceAsUser方法,去绑定第三方NLP里的Service,随后就会回调自己的onServiceConnected方法,因为它本身是个ServiceConnection,在回调方法里会执行mNewServiceWork,它是由LocationProviderProxy提供的一个Runnable对象,在这个方法里执行的是;

基于基站网络定位的源码分析基于基站网络定位的源码分析

获取属性信息,把这些属性统一封装在类型为ProviderProperties的对象中,并回调enable方法,如果客户端有请求,则回调setRequest方法,这里要注意的是这些回调方法的对象是ILocationProvider,而不是NLP提供商。把NLP添加到可用provider之后,又添加了融合定位的provider和GeocoderProvider,GeocoderProvider和NLP的代理过程类似,至此LocationManagerService的初始化流程就算是结束了,还是比较复杂的,我们可以看到目前的这个过程和NLP提供商还没有任何关联。

基于基站网络定位的源码分析

双重Client-Server模型

那么LocationProviderProxy又是怎么和第三方NLP关联在一起的呢?

在回答这个问题前,我们可以从宏观上看下App是如何从NLP提供商得到位置的。App向OS发请求,OS里接收到请求后向NLP提供商发请求,NLP提供商把位置返给系统,系统再返给App。从Binder机制来看,App-OS是一组Client-Server模型,OS-BaiduNLP是一组Client-Server模型,App通过Binder请求OS的服务,然后OS通过Binder请求NLP提供商的服务,前者通过ILocationManager.aidl,后者通ILocationProvider.aidl,所以OS既是客户端,也是服务端,看你看的视角是哪个。这也是Binder机制一个优秀的点,我们以为系统是服务方,其实有时候它也是个客户端。

基于基站网络定位的源码分析基于基站网络定位的源码分析

那么接下来我来分析下系统和NLP提供商交互的过程,系统有个类已经实现了ILocationProvider.aidl的接口,那就是LocationProviderBase,所以我们只需要继承LocationProviderBase并实现抽象接口就可以了,这里先看下LocationProviderBase里相关的方法;

基于基站网络定位的源码分析

是不是瞬间觉得熟悉了许多,比如onEnable,onDisable,onSetRequest。需要注意的是这个类系统是有的,但是android.jar里面没有,所以我们出APK的时候需要编译依赖,而不能打包进去,可以provided一个jar包,我们在制作jar包的时候不仅要把LocationProviderBase放进去,还要把相关联的类也放进去,既然系统选择了外包的方式来实现NLP,那么关联的类一定不会无限关联下去。

基于基站网络定位的源码分析

另外还需要注意的是早期ILocationProvider.aidl的实现类是com.android.location.provider.LocationProvider,也就是BaiduNetworkLocationProvider所继承的类,BaiduNetworkLocationProvider1继承的是LocationProviderBase,所以BaiduNetworkLocationProvider是兼容旧版本用的,这点从Action上也可以看出来,现在基本上已经不会被调用了。

LocationManagerService相关类梳理

那么至此我们就打通了从App到系统再到NLP提供商的路径,这里对和LocationManagerService相关的类做一个简单的梳理:

  1. ILocationManager.aidl:LocationManagerService在客户端的代理;

  2. ILocationProvider.aidl:NLP提供商如BaiduNLP在系统层的代理;

  3. LocationManager:客户端调用位置服务的类;

  4. LocationManagerService:真正实现LocationManager中方法的类;

  5. LocationProvider:位置服务提供者;

  6. LocationProviderInterface:位置服务提供者的抽象接口,它的实现类有PassiveProvider,GpsLocationProvider,LocationProviderProxy等;

  7. LocationProviderProxy:它是NLP提供商的代理类,通过ILocationProvider.aidl来远程调用NLP提供商的服务;

  8. ServiceWatcher:它是LocationProviderProxy用到的一个类,通过配置xml文件可以读取到指定的包名,然后去绑定对应的服务,并监听包的变更事件;

  9. LocationProviderBase:它是NLP提供商需要继承并实现的抽象类;

  10. GeocoderProxy:是Geocoder的代理类,实现方式和NetworkLocationProvider类似;

  11. LocationRequest:客户端请求定位时传的参数会被封装成这个类传给服务端;

它们之间的简单关系图可以这样表示;

基于基站网络定位的源码分析

一条定位请求之旅

那么接下来继续分析App发起一次定位的过程:

这里先对LocationManagerService2个内部类做一个说明,一个是UpdateRecord,它封装了LocationRequest,Receiver,和是否在前台,在构造函数里会往mRecordsByProvider里存一条记录;

基于基站网络定位的源码分析

另一个是Receiver,它内部封装了客户端经包装后的listener或广播,还有worksource。它还有四个方法,分别对应listener中的4个回调方法:位置变化,provider状态变化,enable和disable方法。

基于基站网络定位的源码分析

基于基站网络定位的源码分析

Receiver实现了Binder的死亡代理。跨进程通讯时,service可能会为bind过来的client分配一些资源,当client调用release或者unbind的时候则会释放资源,但是如果service不知道,则为其分配的资源则不会被释放。Android提供了一种叫做死亡通知的机制,就是用在这个场景下的。当client被杀后,会回调binderDied方法,然后通过removeUpdatesLocked来释放资源。

基于基站网络定位的源码分析

当客户端调用LocationManager的requestLocationUpdates方法时,会把参数拼成LocationRequest这个类,传给LocationManagerService。服务端会调用requestLocationUpdatesLocked方法,这些加Locked的方法是系统封装的带锁的方法。

基于基站网络定位的源码分析

在这里每个发起定位请求的客户端都会插入一条记录UpdateRecord,现在终于知道为什么取消定位的方法叫removeUpdate而不是removeRequest了吧,其实请求定位和取消定位就是插入删除一条记录,如果之前有这条记录,那么就把它移除,相当于App调用了2次定位,那么后面的请求会把前面的覆盖,这种情况一般是发生在持续定位的过程,就像三星测试机,每调用一次requestLocationUpdate方法就会走一次onSetRequest,后面覆盖前面,而vivo测试机如果调用了requestLocationUpdate,就必须removeUpdate才能再次触发requestLocationUpdate,否则调用不生效,可能就是修改了这块的代码。

如果provider是enable状态,就会走applyRequirementsLocked方法,这里先对ProviderRequest类做一个介绍,这个类是对LocationRequest集合的一个封装,可以理解为是现在所有发起的定位的请求集合,比如5个应用向系统要位置,那么ProviderRequest里就有5个LocationRequest。它有2个变量,最小时间间隔默认值是9223372036854775807L,是否需要上报位置默认值是flase。

基于基站网络定位的源码分析

在它的toString方法里可以看到ON和OFF的身影,NLP提供商会接受到它的包装类ProviderRequestUnbundle对象,可以得到ON或OFF的值。

基于基站网络定位的源码分析

我们继续分析applyRequirementsLocked这个方法,先取出了设置里最小的时间间隔DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 60 * 1000,是30分,不过这个变量厂商一般会修改,vivo测试机上目前是10分。全局有一个变量mRecordsByProvider来记录所有的UpdateRecord,所以这里对UpdateRecord集合做了一个遍历,筛选出一个最小的时间作为更新频率。先是判断该应用是否在后台,是的话就选择App传递值和设置值的最大值作为它的更新时间。然后判断当前LocationRequest的频率和ProviderRequest的频率,小于标记为需要上报位置,并把ProviderRequest的频率调低。

这么说可能会没有感知,我举个测试的例子你就懂了,有2个应用在请求位置,频率分别是1min,5min,那么ProviderRequest的频率就是1min,这会生成2条UpdateRecord,这时再来一个10s的定位请求,会先判断如果在后台就设置为30分,然后用第3条请求的频率和ProviderRequest的频率作比较,选择最小的10s作为ProviderRequest的频率,同时标记需要上报位置。

基于基站网络定位的源码分析基于基站网络定位的源码分析

这里注意下ProviderRequest是一个局部变量,每次都会new出来的,它的时间频率默认值是一个大数,所以每次遍历只要有定位请求,频率就会改变,直到找出最小的频率,并且标记为需要上报位置。过去我们理解的是请求定位传ON,移除定位传OFF,这是错误的,OFF的意义应该是当前所有的定位请求全部取消了,也就是最后一个需要定位的请求也取消了,而不是单个App请求请求。

经过测试发现确实如此,如果只有一个APP请求定位,那么调用removeUpdate会收到OFF,如果是多个APP请求位置,那么只有最后一个请求调用了removeUpdate才会接受到OFF事件,当然修改过系统代码的可能不会接受到OFF,比如三星手机,它始终返回ON,但是interval设置的比较大,是12小时,如果三星没有修改过代码的话,那么可以理解为系统默认会要求请求,每半天定位一次,永远不会取消。

这里也看到了8.0系统的新特性,有2个方法isThrottlingExemptLocked和record.mIsForegroundUid做判断,如果是系统和白名单的应用那么不会受限,其他应用如果进入后台定位频率将会被调大到30分,我测试了一下确实是这样,Activity不在前台即执行了onPause之后频率就会降低,还有灭屏,即便开启了service在service里定位也没用,甚至是应用内跨进程的service单独定位也有这个限制,只要唤醒它的Activity进入后台定位频率就变大。

基于基站网络定位的源码分析

遍历完之后判断如果需要上报位置,就把worksource记录下,以便于追查耗电的元凶,worksource包含2个参数,一个是uid,一个是包名。最后会回调setRequest方法,把ProviderRequest和WorkSource参数传递过去,所以App每调一次requestLocationUpdate方法,NLP提供商就会回调onSetRequest方法。

基于基站网络定位的源码分析

接着我们向服务器请求定位,得到结果后调用LocationProviderBase的reportLocation方法来把位置上报。这里又需要注意了,reportLocation并不是ILocationProvider里的接口方法,而是LocationProviderBase里的一个自定义的final方法,它调用的是ILocationManager里定义的reportLocation方法,而前面已经说过LocationManagerService才是ILocationManager真正实现类,所以要去LocationManagerService去找reportLocation究竟做了什么,定位结果是怎么返给APP的。

public abstract class LocationProviderBase {
    private final String TAG;
    protected final ILocationManager mLocationManager;
    private final ProviderProperties mProperties;
    private final IBinder mBinder;


127    /**
128     * Used by the location provider to report new locations.
129     *
130     * @param location new Location to report
131     *
132     * Requires the android.permission.INSTALL_LOCATION_PROVIDER permission.
133     */

134    public final void reportLocation(Location location) {
135        try {
136            mLocationManager.reportLocation(location, false);
137        } catch (RemoteException e) {
138            Log.e(TAG, "RemoteException", e);
139        } catch (Exception e) {
140            // never crash provider, might be running in a system process
141            Log.e(TAG, "Exception", e);
142        }
143    }

LocationManagerService的reportLocation就是用handler发送了一个MSG_LOCATION_CHANGED的消息,还是很符合谷歌的风格的;

2515    @Override
2516    public void reportLocation(Location location, boolean passive) {
2517        checkCallerIsProvider();
2518
2519        if (!location.isComplete()) {
2520            Log.w(TAG, "Dropping incomplete location: " + location);
2521            return;
2522        }
2523
2524        mLocationHandler.removeMessages(MSG_LOCATION_CHANGED, location);
2525        Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location);
2526        m.arg1 = (passive ? 1 : 0);
2527        mLocationHandler.sendMessageAtFrontOfQueue(m);
2528    }

那么进入case分支,查看它到底做了什么,原来是调用了handleLocationChanged方法,而它又调用了handleLocationChangedLocked方法,我们来继续跟进;

2725    private class LocationWorkerHandler extends Handler {
2726        public LocationWorkerHandler(Looper looper) {
2727            super(looper, nulltrue);
2728        }
2729
2730        @Override
2731        public void handleMessage(Message msg) {
2732            switch (msg.what) {
2733                case MSG_LOCATION_CHANGED:
2734                    handleLocationChanged((Location) msg.obj, msg.arg1 == 1);
2735                    break;
2736            }
2737        }
2738    }

在handleLocationChangedLocked方法里先对mLastLocation做了更新,然后遍历所有UpdateRecord,根据LocationRequest请求的精度来确定返回粗略位置还是精确位置。

2659            Location notifyLocation;
2660            if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
2661                notifyLocation = coarseLocation;  // use coarse location
2662            } else {
2663                notifyLocation = lastLocation;  // use fine location
2664            }
2665            if (notifyLocation != null) {
2666                Location lastLoc = r.mLastFixBroadcast;
2667                if ((lastLoc == null) || shouldBroadcastSafe(notifyLocation, lastLoc, r, now)) {
2668                    if (lastLoc == null) {
2669                        lastLoc = new Location(notifyLocation);
2670                        r.mLastFixBroadcast = lastLoc;
2671                    } else {
2672                        lastLoc.set(notifyLocation);
2673                    }
2674                    if (!receiver.callLocationChangedLocked(notifyLocation)) {
2675                        Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver);
2676                        receiverDead = true;
2677                    }
2678                    r.mRealRequest.decrementNumUpdates();
2679                }
2680            }

这里会走一个shouldBroadcastSafe的判断,看看返回的时间间隔和位置差值是否符合用户的传入,这里就把客户端传来的参数用到了,比如App5min要一次位置,位置差在50米以外才回调,这个方法就是判断这个的。

2531    private static boolean shouldBroadcastSafe(
2532            Location loc, Location lastLoc, UpdateRecord record, long now)
 
{
2533        // Always broadcast the first update
2534        if (lastLoc == null) {
2535            return true;
2536        }
2537
2538        // Check whether sufficient time has passed
2539        long minTime = record.mRealRequest.getFastestInterval();
2540        long delta = (loc.getElapsedRealtimeNanos() - lastLoc.getElapsedRealtimeNanos())
2541                / NANOS_PER_MILLI;
2542        if (delta < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) {
2543            return false;
2544        }
2545
2546        // Check whether sufficient distance has been traveled
2547        double minDistance = record.mRealRequest.getSmallestDisplacement();
2548        if (minDistance > 0.0) {
2549            if (loc.distanceTo(lastLoc) <= minDistance) {
2550                return false;
2551            }
2552        }
2553
2554        // Check whether sufficient number of udpates is left
2555        if (record.mRealRequest.getNumUpdates() <= 0) {
2556            return false;
2557        }
2558
2559        // Check whether the expiry date has passed
2560        return record.mRealRequest.getExpireAt() >= now;
2561    }

如果符合条件,就会回调客户端的listener,这个listener是封装在Receiver里的,注意这次回调是跨进程的,会抛出RemoteException,如果抛出该异常,就认为客户端已经被杀,那么这个receiver将被标记为死亡状态并加入一个列表中。在callLocationChangedLocaked方法里就会回调客户端Listener的onLocationChanged方法,并把Location回传回去,如果客户端不是用的Listener而是用广播的形式来接受数据,那么就会发送广播。回调完之后会走一个方法 r.mRealRequest.decrementNumUpdates(),把LocationRequest里的计数器减1。

980        public boolean callLocationChangedLocked(Location location) {
981            if (mListener != null) {
982                try {
983                    synchronized (this) {
984                        // synchronize to ensure incrementPendingBroadcastsLocked()
985                        // is called before decrementPendingBroadcasts()
986                        mListener.onLocationChanged(new Location(location));
987                        // call this after broadcasting so we do not increment
988                        // if we throw an exeption.
989                        incrementPendingBroadcastsLocked();
990                    }
991                } catch (RemoteException e) {
992                    return false;
993                }
994            } else {
995                Intent locationChanged = new Intent();
996                locationChanged.putExtra(LocationManager.KEY_LOCATION_CHANGED, new Location(location));
997                try {
998                    synchronized (this) {
999                        // synchronize to ensure incrementPendingBroadcastsLocked()
1000                        // is called before decrementPendingBroadcasts()
1001                        mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler,
1002                                getResolutionPermission(mAllowedResolutionLevel));
1003                        // call this after broadcasting so we do not increment
1004                        // if we throw an exeption.
1005                        incrementPendingBroadcastsLocked();
1006                    }
1007                } catch (PendingIntent.CanceledException e) {
1008                    return false;
1009                }
1010            }
1011            return true;
1012        }

然后看是否需要回调callStatusChangedLocked方法,它内部也是回调Listener或者发送广播。

945        public boolean callStatusChangedLocked(String provider, int status, Bundle extras) {
946            if (mListener != null) {
947                try {
948                    synchronized (this) {
949                        // synchronize to ensure incrementPendingBroadcastsLocked()
950                        // is called before decrementPendingBroadcasts()
951                        mListener.onStatusChanged(provider, status, extras);
952                        // call this after broadcasting so we do not increment
953                        // if we throw an exeption.
954                        incrementPendingBroadcastsLocked();
955                    }
956                } catch (RemoteException e) {
957                    return false;
958                }
959            } else {
960                Intent statusChanged = new Intent();
961                statusChanged.putExtras(new Bundle(extras));
962                statusChanged.putExtra(LocationManager.KEY_STATUS_CHANGED, status);
963                try {
964                    synchronized (this) {
965                        // synchronize to ensure incrementPendingBroadcastsLocked()
966                        // is called before decrementPendingBroadcasts()
967                        mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler,
968                                getResolutionPermission(mAllowedResolutionLevel));
969                        // call this after broadcasting so we do not increment
970                        // if we throw an exeption.
971                        incrementPendingBroadcastsLocked();
972                    }
973                } catch (PendingIntent.CanceledException e) {
974                    return false;
975                }
976            }
977            return true;
978        }

接着会判断LocationRequest的numUpdates的个数,这个在一开始分析LocationMananger的时候说到过,调用requestSingleUpdate会把这个变量设置为1,而它减1是在回调listener那里,所以单次请求到这里就会结束,它被加入到一个deadUpdateRecords的列表中。最后对回调失败的客户端也就是被杀死的客户端进行清理,通过调用removeUpdatesLocked释放资源。然后对加入到deadUpdateRecords里的请求做一个释放,再执行一次applyRequirementsLocked方法。

需要注意的是这里有2个列表,一个是客户端已经被杀死的列表,针对这个列表执行的方法是removeUpdatesLocked,另一个是deadUpdateRecords的列表,对它可以简单理解成单次定位而非持续定位的请求,也就是通过requestSingleUpdate调用产生的请求,这些请求执行完上报位置后要做释放,调用的是Receiver的disposeLocked方法,同时再执行applyRequirementsLocked方法,而这个方法又会回调setRequest方法。

一开始我怎么也看不懂这是啥意思,后来用demo反复测试后终于搞明白了,requestSingleUpdate方法会触发2次onSetRequest方法,这在log里有显示,第一次是App发起的,传入的interval是0(vivo手机经过修改是2ms),上报位置后又回调了一次onSetRequest,这次传的是OFF,并且没有worksource,也就是终止定位了,这正好和源码里一次单次定位回调2次onSetRequest是吻合的。当然这是在只有一个App定位的情形下测试的,如果2个App一起定位,另一个是持续定位,时间间隔是20s,那么requestSingleUpdate的第二次onSetRequest回调传回来的参数就是20,并且传来的ON,不是OFF。

2693            // track expired records
2694            if (r.mRealRequest.getNumUpdates() <= 0 || r.mRealRequest.getExpireAt() < now) {
2695                if (deadUpdateRecords == null) {
2696                    deadUpdateRecords = new ArrayList<>();
2697                }
2698                deadUpdateRecords.add(r);
2699            }
2700            // track dead receivers
2701            if (receiverDead) {
2702                if (deadReceivers == null) {
2703                    deadReceivers = new ArrayList<>();
2704                }
2705                if (!deadReceivers.contains(receiver)) {
2706                    deadReceivers.add(receiver);
2707                }
2708            }
2709        }
2710
2711        // remove dead records and receivers outside the loop
2712        if (deadReceivers != null) {
2713            for (Receiver receiver : deadReceivers) {
2714                removeUpdatesLocked(receiver);
2715            }
2716        }
2717        if (deadUpdateRecords != null) {
2718            for (UpdateRecord r : deadUpdateRecords) {
2719                r.disposeLocked(true);
2720            }
2721            applyRequirementsLocked(provider);
2722        }

接着我们继续看下removeUpdatesLocked方法里做了什么,如果客户端传入的是listener,那么让它死亡,调用receiver.getListener().asBinder().unlinkToDeath(receiver, 0)方法,然后取出UpdateRecord,执行它的disposeLocked(false)方法,这个方法传入false就是从全局的记录UpdateRecord列表对象mRecordsByProvider中移除该记录。这是对死亡的客户端做一次回调。如果是单次定位的请求则调用disposeLocked(true)方法来销毁,boolean值表示是否移除Receiver。

2100    private void removeUpdatesLocked(Receiver receiver) {
2101        if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver)));
2102
2103        if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) {
2104            receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
2105            synchronized (receiver) {
2106                receiver.clearPendingBroadcastsLocked();
2107            }
2108        }
2109
2110        receiver.updateMonitoring(false);
2111
2112        // Record which providers were associated with this listener
2113        HashSet<String> providers = new HashSet<>();
2114        HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords;
2115        if (oldRecords != null) {
2116            // Call dispose() on the obsolete update records.
2117            for (UpdateRecord record : oldRecords.values()) {
2118                // Update statistics for historical location requests by package/provider
2119                record.disposeLocked(false);
2120            }
2121            // Accumulate providers
2122            providers.addAll(oldRecords.keySet());
2123        }
2124
2125        // update provider
2126        for (String provider : providers) {
2127            // If provider is already disabled, don't need to do anything
2128            if (!isAllowedByCurrentUserSettingsLocked(provider)) {
2129                continue;
2130            }
2131
2132            applyRequirementsLocked(provider);
2133        }
2134    }
1868        /**
1869         * Method to be called when a record will no longer be used.
1870         */

1871        void disposeLocked(boolean removeReceiver) {
1872            mRequestStatistics.stopRequesting(mReceiver.mIdentity.mPackageName, mProvider);
1873
1874            // remove from mRecordsByProvider
1875            ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider);
1876            if (globalRecords != null) {
1877                globalRecords.remove(this);
1878            }
1879
1880            if (!removeReceiver) return;  // the caller will handle the rest
1881
1882            // remove from Receiver#mUpdateRecords
1883            HashMap<String, UpdateRecord> receiverRecords = mReceiver.mUpdateRecords;
1884            if (receiverRecords != null) {
1885                receiverRecords.remove(this.mProvider);
1886
1887                // and also remove the Receiver if it has no more update records
1888                if (receiverRecords.size() == 0) {
1889                    removeUpdatesLocked(mReceiver);
1890                }
1891            }
1892        }

至此一次定位的流程就算是走完了,可以看出一次定位可能会多次回调onSetRequest方法,主要是requestSingleUpdate,它会把numUpdates变量置为1,而默认值则是一个大数Integer.MAX_VALUE = 0x7fffffff,伴随着每次reportLocation计数器减1,单次定位只会执行一次,并且归零后再回调一次onSetRequest方法,根据情况传入OFF。持续定位则不会回调2次,因为这个大数要递减N多次才会为0,我们之前理解的一次定位只回调一次onSetRequest是错误的。

移除定位请求其实上面也说到了,它调用removeUpdates,根据传入的listener来找到对应的Receiver,然后调用removeUpdatesLocked方法,让监听死亡,并调用record.disposeLocked(false)来销毁记录,然后再走一次applyRequirementsLocked方法。也就是说removeUpdates也会回调一次onSetRequest方法,这和log显示的是一致的,之前一直不明白为什么移除定位的时候也要发一次请求并传入OFF,这回看源码就懂了。但这是针对单个App的情形,requestUpdate之后调用removeUpdates会回调onSetRequest并传入OFF,但多个App一起请求下就会有变化,onSetRequest仍然会回调,但是是否传入OFF要看这个请求是否是最后一个请求,否则传入的是ON,频率是ProviderRequest的最小频率。所以OFF是针对所有请求都关闭的时候才会回传。

我们看到其实applyRequirementsLocked这是个关键方法,不论是初始化,还是requestUpdate,removeUpdates,reportLocation都会调用它,它就是一个应用参数标准,每当有变动就去检查,变更一下定位频率,并回调onSetRequest方法。所以onSetRequest方法就是参数(主要就是定位频率)发生了变更后的回调,而不是之前我们所理解的定一次位就回调一次,每当定位频率发生改变这个方法就会回调。

在实际测试过程中,我发现三星测试机不回传OFF,并且如果先调用持续定位,再调用单次定位后,持续定位会被抵消,参数传入了ON并且把时间设置成了12h,而vivo修改了这块逻辑,在持续定位时插入一条单次定位,不会回传OFF,而是传入ON,并且频率是当前最小的频率,这个还是比较合理的。

总结分享

在上面走读源码的过程中,也分析到了很多和第三方NLP相关的点,这里再简单概括一下:

  1. NLP提供商怎么配置,配置的这些参数是如何生效的,系统在哪里调用到了;

  2. NLP提供商要用一个Service去绑定系统的Action,并间接实现ILocationProvider的方法,也就是继成LocationProviderBase类;

  3. 这个Service要配置service_version才能被正常解析;

  4. NLP提供商要在onSetRequest方法里接收参数并实现定位功能,把结果上报回去,考虑到既有单次定位又有持续定位,可以开启一个定时器队列来实现,另外传入OFF的时候是不需要上报位置的;

  5. onEnable可以理解为是一个初始化方法,onDisable可以理解为是销毁方法,可以在这2个方法里做初始化和释放资源的事情;

  6. 当Provider状态发生变更时通过onGetStatus和onGetStatusUpdateTime方法来改变状态和时间;

我把看源码过程中的其他收获也来分享一下;

  1. WorkSource:就是定位来源,它由uid和包名共同组成,如果有多个,他们会以", "进行分割,一个逗号加一个空格,这个在做零秒定位解析worksource时产生过异常,因为那个空格没看出来,源码里确实是有的;

  2. ProviderProperties:做重构的时候看到这个变量不知道是干嘛的,传了一堆boolean值,现在知道他其实是和App层的Criteria类似的。

private static ProviderPropertiesUnbundled PROPERTIES = ProviderPropertiesUnbundled.create(truefalsetruefalsefalsefalsefalse11);

Criteria是一组标准,比如是否支持海拔,支持方向,需要付费,高精度等等,App层可以指定一系列条件来获取到适合的Provider(也有可能没有满足条件的),那这些参数传递到NLP后就变成了ProviderProperties这个对象,这个变量是LocationProviderBase的构造函数需要的。

97    public ProviderProperties(boolean mRequiresNetwork,
98            boolean mRequiresSatellite, boolean mRequiresCell, boolean mHasMonetaryCost,
99            boolean mSupportsAltitude, boolean mSupportsSpeed, boolean mSupportsBearing,
100            int mPowerRequirement, int mAccuracy)
 
{
101        this.mRequiresNetwork = mRequiresNetwork;
102        this.mRequiresSatellite = mRequiresSatellite;
103        this.mRequiresCell = mRequiresCell;
104        this.mHasMonetaryCost = mHasMonetaryCost;
105        this.mSupportsAltitude = mSupportsAltitude;
106        this.mSupportsSpeed = mSupportsSpeed;
107        this.mSupportsBearing = mSupportsBearing;
108        this.mPowerRequirement = mPowerRequirement;
109        this.mAccuracy = mAccuracy;
110    }

onGetStatus:这是NLP提供商要实现的一个方法,它就是获取Provider的状态的,一共有3种,在onEnable和onDisable后要更新状态,这样App才会在Listenter中的onStatusChanged方法里接收到正确的状态回调。

  • LocationProvider.OUT_OF_SERVICE = 0:无服务

  • LocationProvider.AVAILABLE = 2:provider可用

  • LocationProvider.TEMPORARILY_UNAVAILABLE = 1:provider不可用

onGetStatusUpdateTime:这个方法和上面是一样的,它是状态发生更新的时间,但要注意同样的状态不要更新2次,所以在上一个方法里要判断本次状态和之前是否相同,不同再去记录一下时间,谷歌让我们使用SystemClock.elapsedRealtime方法来进行设置。

GeocoderProxy的实现方式和LocationProviderProxy类似,也都是通过ServiceWatcher去读取配置,绑定对应的Service,IGeocodeProvider定义的AIDL接口GeocodeProvider已经实现了,我们只需要继承它就可以了,这个类更简单一共2个方法,一个是onGetFromLocationName,一个是onGetFromLocation。同理地理围栏GeofenceProvider也是一样的代理实现。

最后介绍下分析一个类遵循的主要步骤:

明确类的主要作用:顾名思义,LocationManagerService主要是用来提供定位服务的;

分析类的主要字段:LocationManagerService的很多字段都是围绕provider来展开的,所以它主要是用来管理provider的;

  • mEnabledProviders:可用的Provider集合;

  • mRecordsByProvider:定位请求Update的集合;

  • mLastLocation:最近一次的定位信息,以 Location Provider 的名称为键的映射

理解类的初始化过程:LocationManagerService的初始化主要就是加载各种Provider,其中NetworkLocationPrivider和GeocoderProvider是通过ServiceWatcher去配置中读取绑定的;

理解类的主要业务逻辑方法:

  1. requestLocationUpdatesLocked:请求定位的方法,插入一条记录;

  2. removeUpdatesLocked:移除定位请求的方法,移除一条记录;

  3. applyRequirementsLocked:各种配置变更都会走该方法,它会回调setRequest方法;

  4. handleLocationChanged:上报位置时会触发该方法,这里会把结果回调给App;

分析类中的其他成员方法和内部类:

上面的4个方法其实主要是从大方向上去分析用到的,但是真正到了一些具体实现或者细节方面的东西还得看一些辅助的方法和内部类,比如UpdateRecord,Receiver这些关键类的关键方法,真正把位置回调给App的就是在Receiver的callLocationChangedLocaked方法中;

分析与这个类紧密相关的其他类:在第五部分介绍过了,主要有十几个相关的类吧;


总结


在分析源码的过程中,我疏通了很多过去搞不懂的盲点,而且对于Android当中的Binder机制有了更深的理解,一开始真没看出来是双重CS模型,看懂CS之后就很明朗了,另外对于Binder的死亡机制也有了更多理解,还有就是对定位这种架构的设计有了一些体会,其中不乏诸多设计模式的使用,Java的高级编程知识,同步锁的控制,面向抽象接口编程,封装参数等等,收获确实不少。

当然走读源码的过程并不是很顺畅,中间有一段时间卡住怎么也过不去,看不懂,遇到这种情况怎么办?别怕,多读几遍就懂了。


欢迎长按下图 -> 识别图中二维码

以上是关于基于基站网络定位的源码分析的主要内容,如果未能解决你的问题,请参考以下文章

利用层次聚类算法进行基于基站定位数据的商圈分析

定位问题基于matlab chan算法移动基站无源定位含Matlab源码 2097期

定位问题基于matlab chan算法移动基站无源定位含Matlab源码 2097期

定位问题基于matlab chan+taylor算法移动基站无源定位含Matlab源码 2089期

定位问题基于matlab TDOA+taylor算法移动基站无源定位含Matlab源码 2098期

定位问题基于matlab chan+taylor算法移动基站无源定位含Matlab源码 2089期