[原创]Android网络定位源码分析

前言

HI,欢迎来到裴智飞的《每周一博》。今天是九月第二周,我给大家分析一下Android网络定位源码。

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

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

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

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

3.共享库层:GpsLocationProvider通过JNI来调用本层libgps.so中的C++代码;

4.Linux内核层:C++代码最终去调用GPS硬件来获取位置;

《[原创]Android网络定位源码分析》

一. 为什么要分析源码

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

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

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

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

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

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

F. onSetRequest到底是该怎么用?

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

二. LocationManager分析

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

《[原创]Android网络定位源码分析》

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。

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

三. LocationManagerService的初始化

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

接下来就是加载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,否则可能会出现无法绑定的情况。

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

四. 双重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机制一个优秀的点,我们以为系统是服务方,其实有时候它也是个客户端。

《[原创]Android网络定位源码分析》

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

另外还需要注意的是早期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:客户端请求定位时传的参数会被封装成这个类传给服务端;

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

《[原创]Android网络定位源码分析》

六. 一条定位请求之旅

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

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

我们继续分析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的频率,同时标记需要上报位置。

《[原创]Android网络定位源码分析》

这里注意下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进入后台定位频率就变大。

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

接着会判断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。

《[原创]Android网络定位源码分析》

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

《[原创]Android网络定位源码分析》

《[原创]Android网络定位源码分析》

至此一次定位的流程就算是走完了,可以看出一次定位可能会多次回调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(true, false, true, false, false, false, false, 1, 1);

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

《[原创]Android网络定位源码分析》

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

LocationProvider.OUT_OF_SERVICE = 0:无服务
LocationProvider.AVAILABLE = 2:provider可用
LocationProvider.TEMPORARILY_UNAVAILABLE = 1:provider不可用

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

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

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

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

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

(1) mEnabledProviders:可用的Provider集合;
(2) mRecordsByProvider:定位请求Update的集合;
(3) mLastLocation:最近一次的定位信息,以 Location Provider 的名称为键的映射

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

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

(1) requestLocationUpdatesLocked:请求定位的方法,插入一条记录;
(2) removeUpdatesLocked:移除定位请求的方法,移除一条记录;
(3) applyRequirementsLocked:各种配置变更都会走该方法,它会回调setRequest方法;
(4) handleLocationChanged:上报位置时会触发该方法,这里会把结果回调给App;

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

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

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

八. 收尾

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

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

    原文作者:java集合源码分析
    原文地址: https://juejin.im/entry/5b9dbe636fb9a05d0d285f00
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞